From 9af8f4bb31c94a25780a4189bffa406906249626 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 21 Nov 2016 02:16:20 +0100 Subject: [PATCH] organize examples This commit is largely based on work by Thiago Arrais (@thiagoarrais) and Shane Bradfield (@l33tLumberjack). I wasn't really able to get their PR reasonably merged onto the latest master, so I reapplied their changes manually here and did some further improvements on that. --- docs/features/responsestreaming.rst | 8 +- docs/scripting/overview.rst | 22 ++--- examples/README | 31 ------- examples/README.md | 15 ++++ examples/complex/README.md | 19 ++++ .../{ => complex}/change_upstream_proxy.py | 0 examples/{ => complex}/dns_spoofing.py | 0 examples/{ => complex}/dup_and_replay.py | 0 examples/{flowbasic => complex/flowbasic.py} | 1 + .../{ => complex}/full_transparency_shim.c | 0 examples/{ => complex}/har_dump.py | 0 examples/{ => complex}/mitmproxywrapper.py | 0 examples/{ => complex}/nonblocking.py | 0 examples/{ => complex}/remote_debug.py | 0 examples/{ => complex}/sslstrip.py | 6 +- examples/{ => complex}/stickycookies | 2 +- examples/{ => complex}/stream.py | 1 + examples/{ => complex}/stream_modify.py | 0 examples/{ => complex}/tcp_message.py | 0 examples/{ => complex}/tls_passthrough.py | 0 examples/context_logging.py | 6 -- examples/custom_contentviews.py | 70 --------------- examples/fail_with_500.py | 3 - examples/redirect_requests.py | 18 ---- examples/simple/README.md | 18 ++++ examples/{ => simple}/add_header.py | 0 .../add_header_class.py} | 0 examples/simple/custom_contentview.py | 28 ++++++ .../{flowfilter.py => simple/filter_flows.py} | 8 +- .../io_read_dumpfile.py} | 2 +- .../io_write_dumpfile.py} | 7 ++ examples/simple/logging.py | 12 +++ .../modify_body_inject_iframe.py} | 0 examples/{ => simple}/modify_form.py | 2 + examples/{ => simple}/modify_querystring.py | 0 examples/simple/redirect_requests.py | 11 +++ .../script_arguments.py} | 0 examples/simple/send_reply_from_proxy.py | 17 ++++ examples/simple/upsidedownternet.py | 16 ++++ .../{proxapp.py => simple/wsgi_flask_app.py} | 0 examples/stub.py | 87 ------------------- examples/upsidedownternet.py | 15 ---- test/mitmproxy/test_examples.py | 39 +++++---- 43 files changed, 196 insertions(+), 268 deletions(-) delete mode 100644 examples/README create mode 100644 examples/README.md create mode 100644 examples/complex/README.md rename examples/{ => complex}/change_upstream_proxy.py (100%) rename examples/{ => complex}/dns_spoofing.py (100%) rename examples/{ => complex}/dup_and_replay.py (100%) rename examples/{flowbasic => complex/flowbasic.py} (99%) mode change 100755 => 100644 rename examples/{ => complex}/full_transparency_shim.c (100%) rename examples/{ => complex}/har_dump.py (100%) rename examples/{ => complex}/mitmproxywrapper.py (100%) rename examples/{ => complex}/nonblocking.py (100%) rename examples/{ => complex}/remote_debug.py (100%) rename examples/{ => complex}/sslstrip.py (92%) rename examples/{ => complex}/stickycookies (96%) mode change 100755 => 100644 rename examples/{ => complex}/stream.py (65%) rename examples/{ => complex}/stream_modify.py (100%) rename examples/{ => complex}/tcp_message.py (100%) rename examples/{ => complex}/tls_passthrough.py (100%) delete mode 100644 examples/context_logging.py delete mode 100644 examples/custom_contentviews.py delete mode 100644 examples/fail_with_500.py delete mode 100644 examples/redirect_requests.py create mode 100644 examples/simple/README.md rename examples/{ => simple}/add_header.py (100%) rename examples/{classes.py => simple/add_header_class.py} (100%) create mode 100644 examples/simple/custom_contentview.py rename examples/{flowfilter.py => simple/filter_flows.py} (75%) rename examples/{read_dumpfile => simple/io_read_dumpfile.py} (95%) rename examples/{flowwriter.py => simple/io_write_dumpfile.py} (62%) create mode 100644 examples/simple/logging.py rename examples/{iframe_injector.py => simple/modify_body_inject_iframe.py} (100%) rename examples/{ => simple}/modify_form.py (69%) rename examples/{ => simple}/modify_querystring.py (100%) create mode 100644 examples/simple/redirect_requests.py rename examples/{arguments.py => simple/script_arguments.py} (100%) create mode 100644 examples/simple/send_reply_from_proxy.py create mode 100644 examples/simple/upsidedownternet.py rename examples/{proxapp.py => simple/wsgi_flask_app.py} (100%) delete mode 100644 examples/stub.py delete mode 100644 examples/upsidedownternet.py diff --git a/docs/features/responsestreaming.rst b/docs/features/responsestreaming.rst index 1d5726c4e..6fa93271d 100644 --- a/docs/features/responsestreaming.rst +++ b/docs/features/responsestreaming.rst @@ -40,8 +40,8 @@ You can also use a script to customize exactly which responses are streamed. Responses that should be tagged for streaming by setting their ``.stream`` attribute to ``True``: -.. literalinclude:: ../../examples/stream.py - :caption: examples/stream.py +.. literalinclude:: ../../examples/complex/stream.py + :caption: examples/complex/stream.py :language: python Implementation Details @@ -59,8 +59,8 @@ Modifying streamed data If the ``.stream`` attribute is callable, ``.stream`` will wrap the generator that yields all chunks. -.. literalinclude:: ../../examples/stream_modify.py - :caption: examples/stream_modify.py +.. literalinclude:: ../../examples/complex/stream_modify.py + :caption: examples/complex/stream_modify.py :language: python .. seealso:: diff --git a/docs/scripting/overview.rst b/docs/scripting/overview.rst index 7e399c9ca..7df5532df 100644 --- a/docs/scripting/overview.rst +++ b/docs/scripting/overview.rst @@ -17,8 +17,8 @@ appropriate points of mitmproxy's operation. Here's a complete mitmproxy script that adds a new header to every HTTP response before it is returned to the client: -.. literalinclude:: ../../examples/add_header.py - :caption: :src:`examples/add_header.py` +.. literalinclude:: ../../examples/simple/add_header.py + :caption: :src:`examples/simple/add_header.py` :language: python All events that deal with an HTTP request get an instance of `HTTPFlow @@ -42,8 +42,8 @@ called before anything else happens. You can replace the current script object by returning it from this handler. Here's how this looks when applied to the example above: -.. literalinclude:: ../../examples/classes.py - :caption: :src:`examples/classes.py` +.. literalinclude:: ../../examples/simple/add_header_class.py + :caption: :src:`examples/simple/add_header_class.py` :language: python So here, we're using a module-level script to "boot up" into a class instance. @@ -62,13 +62,13 @@ sophisticated - replace one value with another in all responses. Mitmproxy's `_ method that takes care of all the details for us. -.. literalinclude:: ../../examples/arguments.py - :caption: :src:`examples/arguments.py` +.. literalinclude:: ../../examples/simple/script_arguments.py + :caption: :src:`examples/simple/script_arguments.py` :language: python We can now call this script on the command-line like this: ->>> mitmdump -dd -s "./arguments.py html faketml" +>>> mitmdump -dd -s "./script_arguments.py html faketml" Whenever a handler is called, mitpmroxy rewrites the script environment so that it sees its own arguments as if it was invoked from the command-line. @@ -85,8 +85,8 @@ and mitmproxy console can place script output in the event buffer. Here's how this looks: -.. literalinclude:: ../../examples/context_logging.py - :caption: :src:`examples/context_logging.py` +.. literalinclude:: ../../examples/simple/logging.py + :caption: :src:`examples/simple/logging.py` :language: python The ``ctx`` module also exposes the mitmproxy master object at ``ctx.master`` @@ -126,8 +126,8 @@ It's possible to implement a concurrent mechanism on top of the blocking framework, and mitmproxy includes a handy example of this that is fit for most purposes. You can use it as follows: -.. literalinclude:: ../../examples/nonblocking.py - :caption: :src:`examples/nonblocking.py` +.. literalinclude:: ../../examples/complex/nonblocking.py + :caption: :src:`examples/complex/nonblocking.py` :language: python diff --git a/examples/README b/examples/README deleted file mode 100644 index 90edf4687..000000000 --- a/examples/README +++ /dev/null @@ -1,31 +0,0 @@ -Some inline scripts may require additional dependencies, which can be installed using -`pip install mitmproxy[examples]`. - - -# inline script examples -add_header.py Simple script that just adds a header to every request. -change_upstream_proxy.py Dynamically change the upstream proxy -dns_spoofing.py Use mitmproxy in a DNS spoofing scenario. -dup_and_replay.py Duplicates each request, changes it, and then replays the modified request. -fail_with_500.py Turn every response into an Internal Server Error. -filt.py Use mitmproxy's filter expressions in your script. -flowwriter.py Only write selected flows into a mitmproxy dumpfile. -iframe_injector.py Inject configurable iframe into pages. -modify_form.py Modify all form submissions to add a parameter. -modify_querystring.py Modify all query strings to add a parameters. -modify_response_body.py Replace arbitrary strings in all responses -nonblocking.py Demonstrate parallel processing with a blocking script. -proxapp.py How to embed a WSGI app in a mitmproxy server -redirect_requests.py Redirect requests or directly reply to them. -stub.py Script stub with a method definition for every event. -upsidedownternet.py Rewrites traffic to turn images upside down. - - -# mitmproxy examples -flowbasic Basic use of mitmproxy as a library. -stickycookies An example of writing a custom proxy with mitmproxy. - - -# misc -read_dumpfile Read a dumpfile generated by mitmproxy. -mitmproxywrapper.py Bracket mitmproxy run with proxy enable/disable on OS X diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..f46f322d0 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,15 @@ +# Mitmproxy Scripting API + +Mitmproxy has a powerful scripting API that allows you to control almost any aspect of traffic being +proxied. In fact, much of mitmproxy’s own core functionality is implemented using the exact same API +exposed to scripters (see [mitmproxy/addons](../mitmproxy/addons)). + +This directory contains some examples of the scripting API. We recommend to start with the +ones in [simple/](./simple). + +| :warning: | If you are browsing this on GitHub, make sure to select the git tag matching your mitmproxy version. | +|------------|------------------------------------------------------------------------------------------------------| + + +Some inline scripts may require additional dependencies, which can be installed using +`pip install mitmproxy[examples]`. \ No newline at end of file diff --git a/examples/complex/README.md b/examples/complex/README.md new file mode 100644 index 000000000..d3b2e77ae --- /dev/null +++ b/examples/complex/README.md @@ -0,0 +1,19 @@ +## Complex Examples + +| Filename | Description | +|:-------------------------|:----------------------------------------------------------------------------------------------| +| change_upstream_proxy.py | Dynamically change the upstream proxy. | +| dns_spoofing.py | Use mitmproxy in a DNS spoofing scenario. | +| dup_and_replay.py | Duplicates each request, changes it, and then replays the modified request. | +| flowbasic.py | Basic use of mitmproxy's FlowMaster directly. | +| full_transparency_shim.c | Setuid wrapper that can be used to run mitmproxy in full transparency mode, as a normal user. | +| har_dump.py | Dump flows as HAR files. | +| mitmproxywrapper.py | Bracket mitmproxy run with proxy enable/disable on OS X | +| nonblocking.py | Demonstrate parallel processing with a blocking script | +| remote_debug.py | This script enables remote debugging of the mitmproxy _UI_ with PyCharm. | +| sslstrip.py | sslstrip-like funtionality implemented with mitmproxy | +| stickycookies | An advanced example of using mitmproxy's FlowMaster directly. | +| stream | Enable streaming for all responses. | +| stream_modify.py | Modify a streamed response body. | +| tcp_message.py | Modify a raw TCP connection | +| tls_passthrough.py | Use conditional TLS interception based on a user-defined strategy. | \ No newline at end of file diff --git a/examples/change_upstream_proxy.py b/examples/complex/change_upstream_proxy.py similarity index 100% rename from examples/change_upstream_proxy.py rename to examples/complex/change_upstream_proxy.py diff --git a/examples/dns_spoofing.py b/examples/complex/dns_spoofing.py similarity index 100% rename from examples/dns_spoofing.py rename to examples/complex/dns_spoofing.py diff --git a/examples/dup_and_replay.py b/examples/complex/dup_and_replay.py similarity index 100% rename from examples/dup_and_replay.py rename to examples/complex/dup_and_replay.py diff --git a/examples/flowbasic b/examples/complex/flowbasic.py old mode 100755 new mode 100644 similarity index 99% rename from examples/flowbasic rename to examples/complex/flowbasic.py index cb1e4ea4f..25b0b1a91 --- a/examples/flowbasic +++ b/examples/complex/flowbasic.py @@ -35,6 +35,7 @@ class MyMaster(master.Master): def log(self, l): print("log", l.msg) + opts = options.Options(cadir="~/.mitmproxy/") config = ProxyConfig(opts) server = ProxyServer(config) diff --git a/examples/full_transparency_shim.c b/examples/complex/full_transparency_shim.c similarity index 100% rename from examples/full_transparency_shim.c rename to examples/complex/full_transparency_shim.c diff --git a/examples/har_dump.py b/examples/complex/har_dump.py similarity index 100% rename from examples/har_dump.py rename to examples/complex/har_dump.py diff --git a/examples/mitmproxywrapper.py b/examples/complex/mitmproxywrapper.py similarity index 100% rename from examples/mitmproxywrapper.py rename to examples/complex/mitmproxywrapper.py diff --git a/examples/nonblocking.py b/examples/complex/nonblocking.py similarity index 100% rename from examples/nonblocking.py rename to examples/complex/nonblocking.py diff --git a/examples/remote_debug.py b/examples/complex/remote_debug.py similarity index 100% rename from examples/remote_debug.py rename to examples/complex/remote_debug.py diff --git a/examples/sslstrip.py b/examples/complex/sslstrip.py similarity index 92% rename from examples/sslstrip.py rename to examples/complex/sslstrip.py index 9a090c0ca..2f60c8b98 100644 --- a/examples/sslstrip.py +++ b/examples/complex/sslstrip.py @@ -1,3 +1,7 @@ +""" +This script implements an sslstrip-like attack based on mitmproxy. +https://moxie.org/software/sslstrip/ +""" import re import urllib @@ -28,7 +32,7 @@ def response(flow): flow.response.headers.pop('Public-Key-Pins', None) # strip links in response body - flow.response.content = flow.response.content.replace('https://', 'http://') + flow.response.content = flow.response.content.replace(b'https://', b'http://') # strip meta tag upgrade-insecure-requests in response body csp_meta_tag_pattern = b'' diff --git a/examples/stickycookies b/examples/complex/stickycookies old mode 100755 new mode 100644 similarity index 96% rename from examples/stickycookies rename to examples/complex/stickycookies index a0ee90ff8..4631fa738 --- a/examples/stickycookies +++ b/examples/complex/stickycookies @@ -6,7 +6,7 @@ implement functionality similar to the "sticky cookies" option. Heads Up: In the majority of cases, you want to use inline scripts. """ import os -from mitmproxy import controller, proxy +from mitmproxy import controller, proxy, master from mitmproxy.proxy.server import ProxyServer diff --git a/examples/stream.py b/examples/complex/stream.py similarity index 65% rename from examples/stream.py rename to examples/complex/stream.py index 8598f329c..1993cf7f3 100644 --- a/examples/stream.py +++ b/examples/complex/stream.py @@ -1,5 +1,6 @@ def responseheaders(flow): """ Enables streaming for all responses. + This is equivalent to passing `--stream 0` to mitmproxy. """ flow.response.stream = True diff --git a/examples/stream_modify.py b/examples/complex/stream_modify.py similarity index 100% rename from examples/stream_modify.py rename to examples/complex/stream_modify.py diff --git a/examples/tcp_message.py b/examples/complex/tcp_message.py similarity index 100% rename from examples/tcp_message.py rename to examples/complex/tcp_message.py diff --git a/examples/tls_passthrough.py b/examples/complex/tls_passthrough.py similarity index 100% rename from examples/tls_passthrough.py rename to examples/complex/tls_passthrough.py diff --git a/examples/context_logging.py b/examples/context_logging.py deleted file mode 100644 index dccfd8b2f..000000000 --- a/examples/context_logging.py +++ /dev/null @@ -1,6 +0,0 @@ -from mitmproxy import ctx - - -def start(): - ctx.log.info("This is some informative text.") - ctx.log.error("This is an error.") diff --git a/examples/custom_contentviews.py b/examples/custom_contentviews.py deleted file mode 100644 index 3558eacad..000000000 --- a/examples/custom_contentviews.py +++ /dev/null @@ -1,70 +0,0 @@ -import string -import lxml.html -import lxml.etree -from mitmproxy import contentviews -from mitmproxy.utils import strutils - - -class ViewPigLatin(contentviews.View): - name = "pig_latin_HTML" - prompt = ("pig latin HTML", "l") - content_types = ["text/html"] - - def __call__(self, data, **metadata): - if strutils.is_xml(data): - parser = lxml.etree.HTMLParser( - strip_cdata=True, - remove_blank_text=True - ) - d = lxml.html.fromstring(data, parser=parser) - docinfo = d.getroottree().docinfo - - def piglify(src): - words = src.split() - ret = '' - for word in words: - idx = -1 - while word[idx] in string.punctuation and (idx * -1) != len(word): - idx -= 1 - if word[0].lower() in 'aeiou': - if idx == -1: - ret += word[0:] + "hay" - else: - ret += word[0:len(word) + idx + 1] + "hay" + word[idx + 1:] - else: - if idx == -1: - ret += word[1:] + word[0] + "ay" - else: - ret += word[1:len(word) + idx + 1] + word[0] + "ay" + word[idx + 1:] - ret += ' ' - return ret.strip() - - def recurse(root): - if hasattr(root, 'text') and root.text: - root.text = piglify(root.text) - if hasattr(root, 'tail') and root.tail: - root.tail = piglify(root.tail) - - if len(root): - for child in root: - recurse(child) - - recurse(d) - - s = lxml.etree.tostring( - d, - pretty_print=True, - doctype=docinfo.doctype - ) - return "HTML", contentviews.format_text(s) - - -pig_view = ViewPigLatin() - - -def start(): - contentviews.add(pig_view) - - -def done(): - contentviews.remove(pig_view) diff --git a/examples/fail_with_500.py b/examples/fail_with_500.py deleted file mode 100644 index 9710f74a0..000000000 --- a/examples/fail_with_500.py +++ /dev/null @@ -1,3 +0,0 @@ -def response(flow): - flow.response.status_code = 500 - flow.response.content = b"" diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py deleted file mode 100644 index c28042db1..000000000 --- a/examples/redirect_requests.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -This example shows two ways to redirect flows to other destinations. -""" -from mitmproxy import http - - -def request(flow): - # pretty_host takes the "Host" header of the request into account, - # which is useful in transparent mode where we usually only have the IP - # otherwise. - - # Method 1: Answer with a locally generated response - if flow.request.pretty_host.endswith("example.com"): - flow.response = http.HTTPResponse.make(200, b"Hello World", {"Content-Type": "text/html"}) - - # Method 2: Redirect the request to a different server - if flow.request.pretty_host.endswith("example.org"): - flow.request.host = "mitmproxy.org" diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 000000000..52f15627e --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,18 @@ +## Simple Examples + +| Filename | Description | +|:-----------------------------|:---------------------------------------------------------------------------| +| add_header.py | Simple script that just adds a header to every request. | +| custom_contentview.py | Add a custom content view to the mitmproxy UI. | +| filter_flows.py | This script demonstrates how to use mitmproxy's filter pattern in scripts. | +| io_read_dumpfile.py | Read a dumpfile generated by mitmproxy. | +| io_write_dumpfile.py | Only write selected flows into a mitmproxy dumpfile. | +| logging.py | Use mitmproxy's logging API. | +| modify_body_inject_iframe.py | Inject configurable iframe into pages. | +| modify_form.py | Modify HTTP form submissions. | +| modify_querystring.py | Modify HTTP query strings. | +| redirect_requests.py | Redirect a request to a different server. | +| script_arguments.py | Add arguments to a script. | +| send_reply_from_proxy.py | Send a HTTP response directly from the proxy. | +| upsidedownternet.py | Turn all images upside down. | +| wsgi_flask_app.py | Embed a WSGI app into mitmproxy. | \ No newline at end of file diff --git a/examples/add_header.py b/examples/simple/add_header.py similarity index 100% rename from examples/add_header.py rename to examples/simple/add_header.py diff --git a/examples/classes.py b/examples/simple/add_header_class.py similarity index 100% rename from examples/classes.py rename to examples/simple/add_header_class.py diff --git a/examples/simple/custom_contentview.py b/examples/simple/custom_contentview.py new file mode 100644 index 000000000..352163970 --- /dev/null +++ b/examples/simple/custom_contentview.py @@ -0,0 +1,28 @@ +""" +This example shows how one can add a custom contentview to mitmproxy. +The content view API is explained in the mitmproxy.contentviews module. +""" +from mitmproxy import contentviews + + +class ViewSwapCase(contentviews.View): + name = "swapcase" + + # We don't have a good solution for the keyboard shortcut yet - + # you manually need to find a free letter. Contributions welcome :) + prompt = ("swap case text", "p") + content_types = ["text/plain"] + + def __call__(self, data: bytes, **metadata): + return "case-swapped text", contentviews.format_text(data.swapcase()) + + +view = ViewSwapCase() + + +def start(): + contentviews.add(view) + + +def done(): + contentviews.remove(view) diff --git a/examples/flowfilter.py b/examples/simple/filter_flows.py similarity index 75% rename from examples/flowfilter.py rename to examples/simple/filter_flows.py index 34d97275b..29d0a9b83 100644 --- a/examples/flowfilter.py +++ b/examples/simple/filter_flows.py @@ -1,6 +1,8 @@ -# This scripts demonstrates how to use mitmproxy's filter pattern in scripts. -# Usage: mitmdump -s "flowfilter.py FILTER" - +""" +This scripts demonstrates how to use mitmproxy's filter pattern in scripts. +Usage: + mitmdump -s "flowfilter.py FILTER" +""" import sys from mitmproxy import flowfilter diff --git a/examples/read_dumpfile b/examples/simple/io_read_dumpfile.py similarity index 95% rename from examples/read_dumpfile rename to examples/simple/io_read_dumpfile.py index e0e9064a5..edbbe2dd2 100644 --- a/examples/read_dumpfile +++ b/examples/simple/io_read_dumpfile.py @@ -3,7 +3,7 @@ # Simple script showing how to read a mitmproxy dump file # -from mitmproxy import flow +from mitmproxy import io from mitmproxy.exceptions import FlowReadException import pprint import sys diff --git a/examples/flowwriter.py b/examples/simple/io_write_dumpfile.py similarity index 62% rename from examples/flowwriter.py rename to examples/simple/io_write_dumpfile.py index a9768542e..ff1fd0f4b 100644 --- a/examples/flowwriter.py +++ b/examples/simple/io_write_dumpfile.py @@ -1,3 +1,10 @@ +""" +This script how to generate a mitmproxy dump file, +as it would also be generated by passing `-w` to mitmproxy. +In contrast to `-w`, this gives you full control over which +flows should be saved and also allows you to rotate files or log +to multiple files in parallel. +""" import random import sys from mitmproxy import io diff --git a/examples/simple/logging.py b/examples/simple/logging.py new file mode 100644 index 000000000..ab1baf750 --- /dev/null +++ b/examples/simple/logging.py @@ -0,0 +1,12 @@ +""" +It is recommended to use `ctx.log` for logging within a script. +This goes to the event log in mitmproxy and to stdout in mitmdump. + +If you want to help us out: https://github.com/mitmproxy/mitmproxy/issues/1530 :-) +""" +from mitmproxy import ctx + + +def start(): + ctx.log.info("This is some informative text.") + ctx.log.error("This is an error.") diff --git a/examples/iframe_injector.py b/examples/simple/modify_body_inject_iframe.py similarity index 100% rename from examples/iframe_injector.py rename to examples/simple/modify_body_inject_iframe.py diff --git a/examples/modify_form.py b/examples/simple/modify_form.py similarity index 69% rename from examples/modify_form.py rename to examples/simple/modify_form.py index b63a15860..b425efb08 100644 --- a/examples/modify_form.py +++ b/examples/simple/modify_form.py @@ -1,7 +1,9 @@ def request(flow): if flow.request.urlencoded_form: + # If there's already a form, one can just add items to the dict: flow.request.urlencoded_form["mitmproxy"] = "rocks" else: + # One can also just pass new form data. # This sets the proper content type and overrides the body. flow.request.urlencoded_form = [ ("foo", "bar") diff --git a/examples/modify_querystring.py b/examples/simple/modify_querystring.py similarity index 100% rename from examples/modify_querystring.py rename to examples/simple/modify_querystring.py diff --git a/examples/simple/redirect_requests.py b/examples/simple/redirect_requests.py new file mode 100644 index 000000000..51876df74 --- /dev/null +++ b/examples/simple/redirect_requests.py @@ -0,0 +1,11 @@ +""" +This example shows two ways to redirect flows to another server. +""" + + +def request(flow): + # pretty_host takes the "Host" header of the request into account, + # which is useful in transparent mode where we usually only have the IP + # otherwise. + if flow.request.pretty_host == "example.org": + flow.request.host = "mitmproxy.org" diff --git a/examples/arguments.py b/examples/simple/script_arguments.py similarity index 100% rename from examples/arguments.py rename to examples/simple/script_arguments.py diff --git a/examples/simple/send_reply_from_proxy.py b/examples/simple/send_reply_from_proxy.py new file mode 100644 index 000000000..bef2e7e78 --- /dev/null +++ b/examples/simple/send_reply_from_proxy.py @@ -0,0 +1,17 @@ +""" +This example shows how to send a reply from the proxy immediately +without sending any data to the remote server. +""" +from mitmproxy import http + + +def request(flow): + # pretty_url takes the "Host" header of the request into account, which + # is useful in transparent mode where we usually only have the IP otherwise. + + if flow.request.pretty_url == "http://example.com/path": + flow.response = http.HTTPResponse.make( + 200, # (optional) status code + b"Hello World", # (optional) content + {"Content-Type": "text/html"} # (optional) headers + ) diff --git a/examples/simple/upsidedownternet.py b/examples/simple/upsidedownternet.py new file mode 100644 index 000000000..8ba450abd --- /dev/null +++ b/examples/simple/upsidedownternet.py @@ -0,0 +1,16 @@ +""" +This script rotates all images passing through the proxy by 180 degrees. +""" +import io + +from PIL import Image + + +def response(flow): + if flow.response.headers.get("content-type", "").startswith("image"): + s = io.BytesIO(flow.response.content) + img = Image.open(s).rotate(180) + s2 = io.BytesIO() + img.save(s2, "png") + flow.response.content = s2.getvalue() + flow.response.headers["content-type"] = "image/png" diff --git a/examples/proxapp.py b/examples/simple/wsgi_flask_app.py similarity index 100% rename from examples/proxapp.py rename to examples/simple/wsgi_flask_app.py diff --git a/examples/stub.py b/examples/stub.py deleted file mode 100644 index 4f5061e28..000000000 --- a/examples/stub.py +++ /dev/null @@ -1,87 +0,0 @@ -import mitmproxy -""" - This is a script stub, with definitions for all events. -""" - - -def start(): - """ - Called once on script startup before any other events - """ - mitmproxy.ctx.log("start") - - -def configure(options, updated): - """ - Called once on script startup before any other events, and whenever options changes. - """ - mitmproxy.ctx.log("configure") - - -def clientconnect(root_layer): - """ - Called when a client initiates a connection to the proxy. Note that a - connection can correspond to multiple HTTP requests - """ - mitmproxy.ctx.log("clientconnect") - - -def request(flow): - """ - Called when a client request has been received. - """ - mitmproxy.ctx.log("request") - - -def serverconnect(server_conn): - """ - Called when the proxy initiates a connection to the target server. Note that a - connection can correspond to multiple HTTP requests - """ - mitmproxy.ctx.log("serverconnect") - - -def responseheaders(flow): - """ - Called when the response headers for a server response have been received, - but the response body has not been processed yet. Can be used to tell mitmproxy - to stream the response. - """ - mitmproxy.ctx.log("responseheaders") - - -def response(flow): - """ - Called when a server response has been received. - """ - mitmproxy.ctx.log("response") - - -def error(flow): - """ - Called when a flow error has occured, e.g. invalid server responses, or - interrupted connections. This is distinct from a valid server HTTP error - response, which is simply a response with an HTTP error code. - """ - mitmproxy.ctx.log("error") - - -def serverdisconnect(server_conn): - """ - Called when the proxy closes the connection to the target server. - """ - mitmproxy.ctx.log("serverdisconnect") - - -def clientdisconnect(root_layer): - """ - Called when a client disconnects from the proxy. - """ - mitmproxy.ctx.log("clientdisconnect") - - -def done(): - """ - Called once on script shutdown, after any other events. - """ - mitmproxy.ctx.log("done") diff --git a/examples/upsidedownternet.py b/examples/upsidedownternet.py deleted file mode 100644 index d4de7e253..000000000 --- a/examples/upsidedownternet.py +++ /dev/null @@ -1,15 +0,0 @@ -import io -from PIL import Image - - -def response(flow): - if flow.response.headers.get("content-type", "").startswith("image"): - try: - s = io.StringIO(flow.response.content) - img = Image.open(s).rotate(180) - s2 = io.StringIO() - img.save(s2, "png") - flow.response.content = s2.getvalue() - flow.response.headers["content-type"] = "image/png" - except: # Unknown image types etc. - pass diff --git a/test/mitmproxy/test_examples.py b/test/mitmproxy/test_examples.py index 7f194a186..946373506 100644 --- a/test/mitmproxy/test_examples.py +++ b/test/mitmproxy/test_examples.py @@ -40,30 +40,29 @@ def tscript(cmd, args=""): class TestScripts(mastertest.MasterTest): def test_add_header(self): - m, _ = tscript("add_header.py") + m, _ = tscript("simple/add_header.py") f = tflow.tflow(resp=tutils.tresp()) m.response(f) assert f.response.headers["newheader"] == "foo" def test_custom_contentviews(self): - m, sc = tscript("custom_contentviews.py") - pig = contentviews.get("pig_latin_HTML") - _, fmt = pig(b"test!") - assert any(b'esttay!' in val[0][1] for val in fmt) - assert not pig(b"gobbledygook") + m, sc = tscript("simple/custom_contentview.py") + swapcase = contentviews.get("swapcase") + _, fmt = swapcase(b"Test!") + assert any(b'tEST!' in val[0][1] for val in fmt) def test_iframe_injector(self): with tutils.raises(ScriptError): - tscript("iframe_injector.py") + tscript("simple/modify_body_inject_iframe.py") - m, sc = tscript("iframe_injector.py", "http://example.org/evil_iframe") + m, sc = tscript("simple/modify_body_inject_iframe.py", "http://example.org/evil_iframe") f = tflow.tflow(resp=tutils.tresp(content=b"mitmproxy")) m.response(f) content = f.response.content assert b'iframe' in content and b'evil_iframe' in content def test_modify_form(self): - m, sc = tscript("modify_form.py") + m, sc = tscript("simple/modify_form.py") form_header = Headers(content_type="application/x-www-form-urlencoded") f = tflow.tflow(req=tutils.treq(headers=form_header)) @@ -76,7 +75,7 @@ class TestScripts(mastertest.MasterTest): assert list(f.request.urlencoded_form.items()) == [(b"foo", b"bar")] def test_modify_querystring(self): - m, sc = tscript("modify_querystring.py") + m, sc = tscript("simple/modify_querystring.py") f = tflow.tflow(req=tutils.treq(path="/search?q=term")) m.request(f) @@ -87,17 +86,23 @@ class TestScripts(mastertest.MasterTest): assert f.request.query["mitmproxy"] == "rocks" def test_arguments(self): - m, sc = tscript("arguments.py", "mitmproxy rocks") + m, sc = tscript("simple/script_arguments.py", "mitmproxy rocks") f = tflow.tflow(resp=tutils.tresp(content=b"I <3 mitmproxy")) m.response(f) assert f.response.content == b"I <3 rocks" def test_redirect_requests(self): - m, sc = tscript("redirect_requests.py") + m, sc = tscript("simple/redirect_requests.py") f = tflow.tflow(req=tutils.treq(host="example.org")) m.request(f) assert f.request.host == "mitmproxy.org" + def test_send_reply_from_proxy(self): + m, sc = tscript("simple/send_reply_from_proxy.py") + f = tflow.tflow(req=tutils.treq(host="example.com", port=80)) + m.request(f) + assert f.response.content == b"Hello World" + class TestHARDump: @@ -115,13 +120,13 @@ class TestHARDump: def test_no_file_arg(self): with tutils.raises(ScriptError): - tscript("har_dump.py") + tscript("complex/har_dump.py") def test_simple(self): with tutils.tmpdir() as tdir: path = os.path.join(tdir, "somefile") - m, sc = tscript("har_dump.py", shlex.quote(path)) + m, sc = tscript("complex/har_dump.py", shlex.quote(path)) m.addons.invoke(m, "response", self.flow()) m.addons.remove(sc) @@ -134,7 +139,7 @@ class TestHARDump: with tutils.tmpdir() as tdir: path = os.path.join(tdir, "somefile") - m, sc = tscript("har_dump.py", shlex.quote(path)) + m, sc = tscript("complex/har_dump.py", shlex.quote(path)) m.addons.invoke(m, "response", self.flow(resp_content=b"foo" + b"\xFF" * 10)) m.addons.remove(sc) @@ -144,7 +149,7 @@ class TestHARDump: assert har["log"]["entries"][0]["response"]["content"]["encoding"] == "base64" def test_format_cookies(self): - m, sc = tscript("har_dump.py", "-") + m, sc = tscript("complex/har_dump.py", "-") format_cookies = sc.ns.ns["format_cookies"] CA = cookies.CookieAttrs @@ -174,7 +179,7 @@ class TestHARDump: with tutils.tmpdir() as tdir: path = os.path.join(tdir, "somefile") - m, sc = tscript("har_dump.py", shlex.quote(path)) + m, sc = tscript("complex/har_dump.py", shlex.quote(path)) m.addons.invoke(m, "response", f) m.addons.remove(sc)