mitmproxy/libmproxy/cmdline.py

437 lines
14 KiB
Python
Raw Normal View History

from __future__ import absolute_import
import re
import argparse
from argparse import ArgumentTypeError
from netlib import http
2014-07-21 19:06:55 +00:00
from . import filt, utils
2014-04-12 05:57:19 +00:00
from .proxy import config
APP_HOST = "mitm.it"
APP_PORT = 80
class ParseException(Exception):
pass
def _parse_hook(s):
sep, rem = s[0], s[1:]
parts = rem.split(sep, 2)
if len(parts) == 2:
patt = ".*"
a, b = parts
elif len(parts) == 3:
patt, a, b = parts
else:
2014-07-21 19:06:55 +00:00
raise ParseException("Malformed hook specifier - too few clauses: %s" % s)
if not a:
2014-07-21 19:06:55 +00:00
raise ParseException("Empty clause: %s" % str(patt))
if not filt.parse(patt):
2014-07-21 19:06:55 +00:00
raise ParseException("Malformed filter pattern: %s" % patt)
return patt, a, b
def parse_replace_hook(s):
"""
Returns a (pattern, regex, replacement) tuple.
The general form for a replacement hook is as follows:
/patt/regex/replacement
The first character specifies the separator. Example:
:~q:foo:bar
If only two clauses are specified, the pattern is set to match
universally (i.e. ".*"). Example:
/foo/bar/
Clauses are parsed from left to right. Extra separators are taken to be
part of the final clause. For instance, the replacement clause below is
"foo/bar/":
/one/two/foo/bar/
Checks that pattern and regex are both well-formed. Raises
ParseException on error.
"""
patt, regex, replacement = _parse_hook(s)
try:
re.compile(regex)
except re.error, e:
2014-07-21 19:06:55 +00:00
raise ParseException("Malformed replacement regex: %s" % str(e.message))
return patt, regex, replacement
def parse_setheader(s):
"""
Returns a (pattern, header, value) tuple.
The general form for a replacement hook is as follows:
/patt/header/value
The first character specifies the separator. Example:
:~q:foo:bar
If only two clauses are specified, the pattern is set to match
universally (i.e. ".*"). Example:
/foo/bar/
Clauses are parsed from left to right. Extra separators are taken to be
part of the final clause. For instance, the value clause below is
"foo/bar/":
/one/two/foo/bar/
Checks that pattern and regex are both well-formed. Raises
ParseException on error.
"""
return _parse_hook(s)
def parse_server_spec(url):
normalized_url = re.sub("^https?2", "", url)
p = http.parse_url(normalized_url)
if not p or not p[1]:
raise ArgumentTypeError("Invalid server specification: %s" % url)
if url.lower().startswith("https2http"):
ssl = [True, False]
elif url.lower().startswith("http2https"):
ssl = [False, True]
elif url.lower().startswith("https"):
ssl = [True, True]
else:
ssl = [False, False]
return ssl + list(p[1:3])
def get_common_options(options):
stickycookie, stickyauth = None, None
if options.stickycookie_filt:
2011-03-12 02:14:25 +00:00
stickycookie = options.stickycookie_filt
if options.stickyauth_filt:
stickyauth = options.stickyauth_filt
2014-07-21 19:06:55 +00:00
stream_large_bodies = utils.parse_size(options.stream_large_bodies)
reps = []
for i in options.replace:
try:
p = parse_replace_hook(i)
except ParseException, e:
raise ArgumentTypeError(e.message)
reps.append(p)
for i in options.replace_file:
try:
patt, rex, path = parse_replace_hook(i)
except ParseException, e:
raise ArgumentTypeError(e.message)
try:
2013-06-15 22:23:44 +00:00
v = open(path, "rb").read()
except IOError, e:
2014-07-21 19:06:55 +00:00
raise ArgumentTypeError("Could not read replace file: %s" % path)
reps.append((patt, rex, v))
setheaders = []
for i in options.setheader:
try:
p = parse_setheader(i)
except ParseException, e:
raise ArgumentTypeError(e.message)
setheaders.append(p)
return dict(
2014-07-21 19:06:55 +00:00
app=options.app,
app_host=options.app_host,
app_port=options.app_port,
anticache=options.anticache,
anticomp=options.anticomp,
client_replay=options.client_replay,
kill=options.kill,
no_server=options.no_server,
refresh_server_playback=not options.norefresh,
rheaders=options.rheaders,
rfile=options.rfile,
replacements=reps,
setheaders=setheaders,
server_replay=options.server_replay,
scripts=options.scripts,
stickycookie=stickycookie,
stickyauth=stickyauth,
stream_large_bodies=stream_large_bodies,
showhost=options.showhost,
wfile=options.wfile,
verbosity=options.verbose,
nopop=options.nopop,
)
def common_options(parser):
2012-08-17 17:04:39 +00:00
parser.add_argument(
"--anticache",
action="store_true", dest="anticache", default=False,
help="Strip out request headers that might cause the server to return 304-not-modified."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"--confdir",
2014-07-21 19:06:55 +00:00
action="store", type=str, dest="confdir", default='~/.mitmproxy',
help="Configuration directory. (~/.mitmproxy)"
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"--host",
action="store_true", dest="showhost", default=False,
help="Use the Host header to construct URLs for display."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-q",
action="store_true", dest="quiet",
help="Quiet."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-r",
action="store", dest="rfile", default=None,
help="Read flows from file."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-s",
action="append", type=str, dest="scripts", default=[],
metavar='"script.py --bar"',
help="Run a script. Surround with quotes to pass script arguments. Can be passed multiple times."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-t",
action="store", dest="stickycookie_filt", default=None, metavar="FILTER",
help="Set sticky cookie filter. Matched against requests."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-u",
action="store", dest="stickyauth_filt", default=None, metavar="FILTER",
help="Set sticky auth filter. Matched against requests."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-v",
2014-03-13 22:25:13 +00:00
action="store_const", dest="verbose", default=1, const=2,
help="Increase event log verbosity."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-w",
action="store", dest="wfile", default=None,
help="Write flows to file."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-z",
action="store_true", dest="anticomp", default=False,
help="Try to convince servers to send us un-compressed data."
)
2012-08-17 17:04:39 +00:00
parser.add_argument(
"-Z",
action="store", dest="body_size_limit", default=None,
metavar="SIZE",
2014-07-21 19:06:55 +00:00
help="Byte size limit of HTTP request and response bodies." \
" Understands k/m/g suffixes, i.e. 3m for 3 megabytes."
)
2014-07-21 19:06:55 +00:00
parser.add_argument(
"--stream",
action="store", dest="stream_large_bodies", default=None,
metavar="SIZE",
help="Stream data to the client if response body exceeds the given threshold. "
"If streamed, the body will not be stored in any way. Understands k/m/g suffixes, i.e. 3m for 3 megabytes."
)
group = parser.add_argument_group("Proxy Options")
2014-07-06 00:15:54 +00:00
# We could make a mutually exclusive group out of -R, -U, -T, but we don't do that because
# - --upstream-server should be in that group as well, but it's already in a different group.
# - our own error messages are more helpful
group.add_argument(
"-b",
2014-07-21 19:06:55 +00:00
action="store", type=str, dest="addr", default='',
help="Address to bind proxy to (defaults to all interfaces)"
)
group.add_argument(
2014-08-09 01:03:21 +00:00
"-I", "--ignore",
action="append", type=str, dest="ignore", default=[],
metavar="HOST",
help="Ignore host and forward all traffic without processing it. "
"In transparent mode, it is recommended to use an IP address (range), not the hostname. "
"In regular mode, only SSL traffic is ignored and the hostname should be used. "
"The supplied value is interpreted as a regular expression and matched on the ip or the hostname. "
"Can be passed multiple times. "
)
group.add_argument(
"-n",
action="store_true", dest="no_server",
help="Don't start a proxy server."
)
group.add_argument(
"-p",
2014-07-21 19:06:55 +00:00
action="store", type=int, dest="port", default=8080,
help="Proxy service port."
)
group.add_argument(
"-R",
action="store", type=parse_server_spec, dest="reverse_proxy", default=None,
2014-03-13 23:02:00 +00:00
help="Forward all requests to upstream HTTP server: http[s][2http[s]]://host[:port]"
)
group.add_argument(
"-T",
action="store_true", dest="transparent_proxy", default=False,
help="Set transparent proxy mode."
)
2014-08-09 01:03:21 +00:00
group.add_argument(
"-U",
action="store", type=parse_server_spec, dest="upstream_proxy", default=None,
help="Forward all requests to upstream proxy server: http://host[:port]"
)
group = parser.add_argument_group(
"Advanced Proxy Options",
"""
The following options allow a custom adjustment of the proxy behavior.
2014-07-06 00:15:54 +00:00
Normally, you don't want to use these options directly and use the provided wrappers instead (-R, -U, -T).
""".strip()
)
group.add_argument(
"--http-form-in", dest="http_form_in", default=None,
action="store", choices=("relative", "absolute"),
help="Override the HTTP request form accepted by the proxy"
)
group.add_argument(
"--http-form-out", dest="http_form_out", default=None,
action="store", choices=("relative", "absolute"),
help="Override the HTTP request form sent upstream by the proxy"
)
2014-09-14 00:22:28 +00:00
group = parser.add_argument_group("Onboarding App")
2013-03-24 20:20:26 +00:00
group.add_argument(
"-a",
action="store_false", dest="app", default=True,
2014-09-14 00:22:28 +00:00
help="Disable the mitmproxy onboarding app."
2013-03-24 20:20:26 +00:00
)
group.add_argument(
"--app-host",
action="store", dest="app_host", default=APP_HOST, metavar="host",
2014-09-14 00:22:28 +00:00
help="Domain to serve the onboarding app from. For transparent mode, use an IP when\
2014-07-21 19:06:55 +00:00
a DNS entry for the app domain is not present. Default: %s" % APP_HOST
)
group.add_argument(
"--app-port",
action="store", dest="app_port", default=APP_PORT, type=int, metavar="80",
2014-09-14 00:22:28 +00:00
help="Port to serve the onboarding app from."
)
2013-03-24 20:20:26 +00:00
2012-08-17 17:04:39 +00:00
group = parser.add_argument_group("Client Replay")
group.add_argument(
"-c",
action="store", dest="client_replay", default=None, metavar="PATH",
help="Replay client requests from a saved file."
)
2012-08-17 17:04:39 +00:00
group = parser.add_argument_group("Server Replay")
group.add_argument(
"-S",
action="store", dest="server_replay", default=None, metavar="PATH",
help="Replay server responses from a saved file."
)
2012-08-17 17:04:39 +00:00
group.add_argument(
"-k",
action="store_true", dest="kill", default=False,
help="Kill extra requests during replay."
)
2012-08-17 17:04:39 +00:00
group.add_argument(
"--rheader",
2012-08-17 17:04:39 +00:00
action="append", dest="rheaders", type=str,
help="Request headers to be considered during replay. "
2014-07-21 19:06:55 +00:00
"Can be passed multiple times."
)
2012-08-17 17:04:39 +00:00
group.add_argument(
"--norefresh",
action="store_true", dest="norefresh", default=False,
2014-07-21 19:06:55 +00:00
help="Disable response refresh, "
"which updates times in cookies and headers for replayed responses."
)
2012-08-17 17:04:39 +00:00
group.add_argument(
"--no-pop",
action="store_true", dest="nopop", default=False,
help="Disable response pop from response flow. "
2014-07-21 19:06:55 +00:00
"This makes it possible to replay same response multiple times."
)
2012-08-17 17:04:39 +00:00
group = parser.add_argument_group(
"Replacements",
"""
Replacements are of the form "/pattern/regex/replacement", where
the separator can be any character. Please see the documentation
for more information.
""".strip()
)
2012-08-17 17:04:39 +00:00
group.add_argument(
"--replace",
2012-08-17 17:04:39 +00:00
action="append", type=str, dest="replace", default=[],
metavar="PATTERN",
help="Replacement pattern."
)
2012-08-17 17:04:39 +00:00
group.add_argument(
"--replace-from-file",
2012-08-17 17:04:39 +00:00
action="append", type=str, dest="replace_file", default=[],
metavar="PATH",
help="Replacement pattern, where the replacement clause is a path to a file."
)
group = parser.add_argument_group(
"Set Headers",
"""
Header specifications are of the form "/pattern/header/value",
where the separator can be any character. Please see the
documentation for more information.
""".strip()
)
2012-08-17 17:13:56 +00:00
group.add_argument(
"--setheader",
action="append", type=str, dest="setheader", default=[],
metavar="PATTERN",
help="Header set pattern."
)
group = parser.add_argument_group(
"Proxy Authentication",
"""
Specify which users are allowed to access the proxy and the method
2014-09-06 10:23:05 +00:00
used for authenticating them.
"""
)
user_specification_group = group.add_mutually_exclusive_group()
user_specification_group.add_argument(
"--nonanonymous",
action="store_true", dest="auth_nonanonymous",
help="Allow access to any user long as a credentials are specified."
)
user_specification_group.add_argument(
"--singleuser",
action="store", dest="auth_singleuser", type=str,
metavar="USER",
help="Allows access to a a single user, specified in the form username:password."
)
user_specification_group.add_argument(
"--htpasswd",
2014-08-17 21:06:25 +00:00
action="store", dest="auth_htpasswd", type=str,
metavar="PATH",
help="Allow access to users specified in an Apache htpasswd file."
)
2014-04-12 05:57:19 +00:00
config.ssl_option_group(parser)