Command-line options for header setting.

This commit is contained in:
Aldo Cortesi 2012-08-19 00:14:16 +12:00
parent 3e96015e61
commit 1b7990897e
5 changed files with 102 additions and 29 deletions

View File

@ -17,9 +17,28 @@ import proxy
import re, filt import re, filt
class ParseReplaceException(Exception): pass class ParseException(Exception): pass
class OptionException(Exception): pass class OptionException(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:
raise ParseException("Malformed hook specifier - too few clauses: %s"%s)
if not a:
raise ParseException("Empty clause: %s"%str(patt))
if not filt.parse(patt):
raise ParseException("Malformed filter pattern: %s"%patt)
return patt, a, b
def parse_replace_hook(s): def parse_replace_hook(s):
""" """
@ -45,32 +64,45 @@ def parse_replace_hook(s):
/one/two/foo/bar/ /one/two/foo/bar/
Checks that pattern and regex are both well-formed. Raises Checks that pattern and regex are both well-formed. Raises
ParseReplaceException on error. ParseException on error.
""" """
sep, rem = s[0], s[1:] patt, regex, replacement = _parse_hook(s)
parts = rem.split(sep, 2)
if len(parts) == 2:
patt = ".*"
regex, replacement = parts
elif len(parts) == 3:
patt, regex, replacement = parts
else:
raise ParseReplaceException("Malformed replacement specifier - too few clauses: %s"%s)
if not regex:
raise ParseReplaceException("Empty replacement regex: %s"%str(patt))
try: try:
re.compile(regex) re.compile(regex)
except re.error, e: except re.error, e:
raise ParseReplaceException("Malformed replacement regex: %s"%str(e.message)) raise ParseException("Malformed replacement regex: %s"%str(e.message))
if not filt.parse(patt):
raise ParseReplaceException("Malformed replacement filter pattern: %s"%patt)
return patt, regex, replacement 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 get_common_options(options): def get_common_options(options):
stickycookie, stickyauth = None, None stickycookie, stickyauth = None, None
if options.stickycookie_filt: if options.stickycookie_filt:
@ -83,13 +115,13 @@ def get_common_options(options):
for i in options.replace: for i in options.replace:
try: try:
p = parse_replace_hook(i) p = parse_replace_hook(i)
except ParseReplaceException, e: except ParseException, e:
raise OptionException(e.message) raise OptionException(e.message)
reps.append(p) reps.append(p)
for i in options.replace_file: for i in options.replace_file:
try: try:
patt, rex, path = parse_replace_hook(i) patt, rex, path = parse_replace_hook(i)
except ParseReplaceException, e: except ParseException, e:
raise OptionException(e.message) raise OptionException(e.message)
try: try:
v = open(path, "r").read() v = open(path, "r").read()
@ -97,6 +129,15 @@ def get_common_options(options):
raise OptionException("Could not read replace file: %s"%path) raise OptionException("Could not read replace file: %s"%path)
reps.append((patt, rex, v)) reps.append((patt, rex, v))
setheaders = []
for i in options.setheader:
try:
p = parse_setheader(i)
except ParseException, e:
raise OptionException(e.message)
setheaders.append(p)
return dict( return dict(
anticache = options.anticache, anticache = options.anticache,
anticomp = options.anticomp, anticomp = options.anticomp,
@ -108,6 +149,7 @@ def get_common_options(options):
rheaders = options.rheaders, rheaders = options.rheaders,
rfile = options.rfile, rfile = options.rfile,
replacements = reps, replacements = reps,
setheaders = setheaders,
server_replay = options.server_replay, server_replay = options.server_replay,
script = options.script, script = options.script,
stickycookie = stickycookie, stickycookie = stickycookie,
@ -211,6 +253,7 @@ def common_options(parser):
action="store", dest="cert_wait_time", default=0, action="store", dest="cert_wait_time", default=0,
help="Wait for specified number of seconds after a new cert is generated. This can smooth over small discrepancies between the client and server times." help="Wait for specified number of seconds after a new cert is generated. This can smooth over small discrepancies between the client and server times."
) )
parser.add_argument( parser.add_argument(
"--no-upstream-cert", default=False, "--no-upstream-cert", default=False,
action="store_true", dest="no_upstream_cert", action="store_true", dest="no_upstream_cert",
@ -271,14 +314,24 @@ def common_options(parser):
group.add_argument( group.add_argument(
"--replace-from-file", "--replace-from-file",
action="append", type=str, dest="replace_file", default=[], action="append", type=str, dest="replace_file", default=[],
metavar="PATTERN", metavar="PATH",
help="Replacement pattern, where the replacement clause is a path to a file." 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()
)
group.add_argument( group.add_argument(
"--dummy-certs", action="store", "--setheader",
type = str, dest = "certdir", default=None, action="append", type=str, dest="setheader", default=[],
help = "Generated dummy certs directory." metavar="PATTERN",
help="Header set pattern."
) )
proxy.certificate_option_group(parser) proxy.certificate_option_group(parser)

View File

@ -339,6 +339,7 @@ class Options(object):
"script", "script",
"replacements", "replacements",
"rheaders", "rheaders",
"setheaders",
"server_replay", "server_replay",
"stickycookie", "stickycookie",
"stickyauth", "stickyauth",
@ -369,6 +370,9 @@ class ConsoleMaster(flow.FlowMaster):
for i in options.replacements: for i in options.replacements:
self.replacehooks.add(*i) self.replacehooks.add(*i)
for i in options.setheaders:
self.setheaders.add(*i)
self.flow_list_walker = None self.flow_list_walker = None
self.set_palette(options.palette) self.set_palette(options.palette)

View File

@ -33,6 +33,7 @@ class Options(object):
"replacements", "replacements",
"rfile", "rfile",
"rheaders", "rheaders",
"setheaders",
"server_replay", "server_replay",
"script", "script",
"stickycookie", "stickycookie",
@ -99,6 +100,10 @@ class DumpMaster(flow.FlowMaster):
for i in options.replacements: for i in options.replacements:
self.replacehooks.add(*i) self.replacehooks.add(*i)
if options.setheaders:
for i in options.setheaders:
self.setheaders.add(*i)
if options.server_replay: if options.server_replay:
self.start_server_playback( self.start_server_playback(
self._readflow(options.server_replay), self._readflow(options.server_replay),

View File

@ -475,6 +475,11 @@ def certificate_option_group(parser):
type = str, dest = "clientcerts", default=None, type = str, dest = "clientcerts", default=None,
help = "Client certificate directory." help = "Client certificate directory."
) )
group.add_argument(
"--dummy-certs", action="store",
type = str, dest = "certdir", default=None,
help = "Generated dummy certs directory."
)
TRANSPARENT_SSL_PORTS = [443, 8443] TRANSPARENT_SSL_PORTS = [443, 8443]

View File

@ -14,7 +14,7 @@ def test_parse_replace_hook():
assert x == (".*", "bar", "voing") assert x == (".*", "bar", "voing")
tutils.raises( tutils.raises(
cmdline.ParseReplaceException, cmdline.ParseException,
cmdline.parse_replace_hook, cmdline.parse_replace_hook,
"/foo" "/foo"
) )
@ -29,11 +29,17 @@ def test_parse_replace_hook():
"/~/foo/rep" "/~/foo/rep"
) )
tutils.raises( tutils.raises(
"empty replacement regex", "empty clause",
cmdline.parse_replace_hook, cmdline.parse_replace_hook,
"//" "//"
) )
def test_parse_setheaders():
x = cmdline.parse_replace_hook("/foo/bar/voing")
assert x == ("foo", "bar", "voing")
def test_common(): def test_common():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
cmdline.common_options(parser) cmdline.common_options(parser)
@ -53,7 +59,7 @@ def test_common():
opts.replace = ["//"] opts.replace = ["//"]
tutils.raises( tutils.raises(
"empty replacement regex", "empty clause",
cmdline.get_common_options, cmdline.get_common_options,
opts opts
) )