mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-27 02:24:18 +00:00
Merge pull request #2707 from cortesi/addtypes
commands: refactor types
This commit is contained in:
commit
6ef6286d8e
@ -3,6 +3,7 @@ from mitmproxy import ctx
|
|||||||
from mitmproxy import io
|
from mitmproxy import io
|
||||||
from mitmproxy import flow
|
from mitmproxy import flow
|
||||||
from mitmproxy import command
|
from mitmproxy import command
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ class ClientPlayback:
|
|||||||
ctx.master.addons.trigger("update", [])
|
ctx.master.addons.trigger("update", [])
|
||||||
|
|
||||||
@command.command("replay.client.file")
|
@command.command("replay.client.file")
|
||||||
def load_file(self, path: command.Path) -> None:
|
def load_file(self, path: mitmproxy.types.Path) -> None:
|
||||||
try:
|
try:
|
||||||
flows = io.read_flows_from_paths([path])
|
flows = io.read_flows_from_paths([path])
|
||||||
except exceptions.FlowReadException as e:
|
except exceptions.FlowReadException as e:
|
||||||
|
@ -6,6 +6,7 @@ from mitmproxy import command
|
|||||||
from mitmproxy import flow
|
from mitmproxy import flow
|
||||||
from mitmproxy import optmanager
|
from mitmproxy import optmanager
|
||||||
from mitmproxy.net.http import status_codes
|
from mitmproxy.net.http import status_codes
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
|
|
||||||
class Core:
|
class Core:
|
||||||
@ -96,7 +97,7 @@ class Core:
|
|||||||
]
|
]
|
||||||
|
|
||||||
@command.command("flow.set")
|
@command.command("flow.set")
|
||||||
@command.argument("spec", type=command.Choice("flow.set.options"))
|
@command.argument("spec", type=mitmproxy.types.Choice("flow.set.options"))
|
||||||
def flow_set(
|
def flow_set(
|
||||||
self,
|
self,
|
||||||
flows: typing.Sequence[flow.Flow],
|
flows: typing.Sequence[flow.Flow],
|
||||||
@ -187,7 +188,7 @@ class Core:
|
|||||||
ctx.log.alert("Toggled encoding on %s flows." % len(updated))
|
ctx.log.alert("Toggled encoding on %s flows." % len(updated))
|
||||||
|
|
||||||
@command.command("flow.encode")
|
@command.command("flow.encode")
|
||||||
@command.argument("enc", type=command.Choice("flow.encode.options"))
|
@command.argument("enc", type=mitmproxy.types.Choice("flow.encode.options"))
|
||||||
def encode(
|
def encode(
|
||||||
self,
|
self,
|
||||||
flows: typing.Sequence[flow.Flow],
|
flows: typing.Sequence[flow.Flow],
|
||||||
@ -216,7 +217,7 @@ class Core:
|
|||||||
return ["gzip", "deflate", "br"]
|
return ["gzip", "deflate", "br"]
|
||||||
|
|
||||||
@command.command("options.load")
|
@command.command("options.load")
|
||||||
def options_load(self, path: command.Path) -> None:
|
def options_load(self, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Load options from a file.
|
Load options from a file.
|
||||||
"""
|
"""
|
||||||
@ -228,7 +229,7 @@ class Core:
|
|||||||
) from e
|
) from e
|
||||||
|
|
||||||
@command.command("options.save")
|
@command.command("options.save")
|
||||||
def options_save(self, path: command.Path) -> None:
|
def options_save(self, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Save options to a file.
|
Save options to a file.
|
||||||
"""
|
"""
|
||||||
|
@ -7,6 +7,7 @@ from mitmproxy import flow
|
|||||||
from mitmproxy import ctx
|
from mitmproxy import ctx
|
||||||
from mitmproxy import certs
|
from mitmproxy import certs
|
||||||
from mitmproxy.utils import strutils
|
from mitmproxy.utils import strutils
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
import pyperclip
|
import pyperclip
|
||||||
|
|
||||||
@ -51,8 +52,8 @@ class Cut:
|
|||||||
def cut(
|
def cut(
|
||||||
self,
|
self,
|
||||||
flows: typing.Sequence[flow.Flow],
|
flows: typing.Sequence[flow.Flow],
|
||||||
cuts: typing.Sequence[command.Cut]
|
cuts: mitmproxy.types.CutSpec,
|
||||||
) -> command.Cuts:
|
) -> mitmproxy.types.Data:
|
||||||
"""
|
"""
|
||||||
Cut data from a set of flows. Cut specifications are attribute paths
|
Cut data from a set of flows. Cut specifications are attribute paths
|
||||||
from the base of the flow object, with a few conveniences - "port"
|
from the base of the flow object, with a few conveniences - "port"
|
||||||
@ -62,17 +63,17 @@ class Cut:
|
|||||||
or "false", "bytes" are preserved, and all other values are
|
or "false", "bytes" are preserved, and all other values are
|
||||||
converted to strings.
|
converted to strings.
|
||||||
"""
|
"""
|
||||||
ret = []
|
ret = [] # type:typing.List[typing.List[typing.Union[str, bytes]]]
|
||||||
for f in flows:
|
for f in flows:
|
||||||
ret.append([extract(c, f) for c in cuts])
|
ret.append([extract(c, f) for c in cuts])
|
||||||
return ret
|
return ret # type: ignore
|
||||||
|
|
||||||
@command.command("cut.save")
|
@command.command("cut.save")
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
flows: typing.Sequence[flow.Flow],
|
flows: typing.Sequence[flow.Flow],
|
||||||
cuts: typing.Sequence[command.Cut],
|
cuts: mitmproxy.types.CutSpec,
|
||||||
path: command.Path
|
path: mitmproxy.types.Path
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Save cuts to file. If there are multiple flows or cuts, the format
|
Save cuts to file. If there are multiple flows or cuts, the format
|
||||||
@ -84,7 +85,7 @@ class Cut:
|
|||||||
append = False
|
append = False
|
||||||
if path.startswith("+"):
|
if path.startswith("+"):
|
||||||
append = True
|
append = True
|
||||||
path = command.Path(path[1:])
|
path = mitmproxy.types.Path(path[1:])
|
||||||
if len(cuts) == 1 and len(flows) == 1:
|
if len(cuts) == 1 and len(flows) == 1:
|
||||||
with open(path, "ab" if append else "wb") as fp:
|
with open(path, "ab" if append else "wb") as fp:
|
||||||
if fp.tell() > 0:
|
if fp.tell() > 0:
|
||||||
@ -110,7 +111,7 @@ class Cut:
|
|||||||
def clip(
|
def clip(
|
||||||
self,
|
self,
|
||||||
flows: typing.Sequence[flow.Flow],
|
flows: typing.Sequence[flow.Flow],
|
||||||
cuts: typing.Sequence[command.Cut],
|
cuts: mitmproxy.types.CutSpec,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Send cuts to the clipboard. If there are multiple flows or cuts, the
|
Send cuts to the clipboard. If there are multiple flows or cuts, the
|
||||||
|
@ -5,6 +5,7 @@ from mitmproxy import flow
|
|||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy.utils import strutils
|
from mitmproxy.utils import strutils
|
||||||
from mitmproxy.net.http.http1 import assemble
|
from mitmproxy.net.http.http1 import assemble
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
import pyperclip
|
import pyperclip
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ class Export():
|
|||||||
return list(sorted(formats.keys()))
|
return list(sorted(formats.keys()))
|
||||||
|
|
||||||
@command.command("export.file")
|
@command.command("export.file")
|
||||||
def file(self, fmt: str, f: flow.Flow, path: command.Path) -> None:
|
def file(self, fmt: str, f: flow.Flow, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Export a flow to path.
|
Export a flow to path.
|
||||||
"""
|
"""
|
||||||
|
@ -7,6 +7,7 @@ from mitmproxy import flowfilter
|
|||||||
from mitmproxy import io
|
from mitmproxy import io
|
||||||
from mitmproxy import ctx
|
from mitmproxy import ctx
|
||||||
from mitmproxy import flow
|
from mitmproxy import flow
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
|
|
||||||
class Save:
|
class Save:
|
||||||
@ -50,7 +51,7 @@ class Save:
|
|||||||
self.start_stream_to_path(ctx.options.save_stream_file, self.filt)
|
self.start_stream_to_path(ctx.options.save_stream_file, self.filt)
|
||||||
|
|
||||||
@command.command("save.file")
|
@command.command("save.file")
|
||||||
def save(self, flows: typing.Sequence[flow.Flow], path: command.Path) -> None:
|
def save(self, flows: typing.Sequence[flow.Flow], path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Save flows to a file. If the path starts with a +, flows are
|
Save flows to a file. If the path starts with a +, flows are
|
||||||
appended to the file, otherwise it is over-written.
|
appended to the file, otherwise it is over-written.
|
||||||
|
@ -9,6 +9,7 @@ from mitmproxy import flow
|
|||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy import io
|
from mitmproxy import io
|
||||||
from mitmproxy import command
|
from mitmproxy import command
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
|
|
||||||
class ServerPlayback:
|
class ServerPlayback:
|
||||||
@ -31,7 +32,7 @@ class ServerPlayback:
|
|||||||
ctx.master.addons.trigger("update", [])
|
ctx.master.addons.trigger("update", [])
|
||||||
|
|
||||||
@command.command("replay.server.file")
|
@command.command("replay.server.file")
|
||||||
def load_file(self, path: command.Path) -> None:
|
def load_file(self, path: mitmproxy.types.Path) -> None:
|
||||||
try:
|
try:
|
||||||
flows = io.read_flows_from_paths([path])
|
flows = io.read_flows_from_paths([path])
|
||||||
except exceptions.FlowReadException as e:
|
except exceptions.FlowReadException as e:
|
||||||
|
@ -351,7 +351,7 @@ class View(collections.Sequence):
|
|||||||
ctx.master.addons.trigger("update", updated)
|
ctx.master.addons.trigger("update", updated)
|
||||||
|
|
||||||
@command.command("view.load")
|
@command.command("view.load")
|
||||||
def load_file(self, path: command.Path) -> None:
|
def load_file(self, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Load flows into the view, without processing them with addons.
|
Load flows into the view, without processing them with addons.
|
||||||
"""
|
"""
|
||||||
|
@ -12,7 +12,7 @@ import sys
|
|||||||
|
|
||||||
from mitmproxy.utils import typecheck
|
from mitmproxy.utils import typecheck
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy import flow
|
import mitmproxy.types
|
||||||
|
|
||||||
|
|
||||||
def lexer(s):
|
def lexer(s):
|
||||||
@ -24,113 +24,14 @@ def lexer(s):
|
|||||||
return lex
|
return lex
|
||||||
|
|
||||||
|
|
||||||
# This is an awkward location for these values, but it's better than having
|
|
||||||
# the console core import and depend on an addon. FIXME: Add a way for
|
|
||||||
# addons to add custom types and manage their completion and validation.
|
|
||||||
valid_flow_prefixes = [
|
|
||||||
"@all",
|
|
||||||
"@focus",
|
|
||||||
"@shown",
|
|
||||||
"@hidden",
|
|
||||||
"@marked",
|
|
||||||
"@unmarked",
|
|
||||||
"~q",
|
|
||||||
"~s",
|
|
||||||
"~a",
|
|
||||||
"~hq",
|
|
||||||
"~hs",
|
|
||||||
"~b",
|
|
||||||
"~bq",
|
|
||||||
"~bs",
|
|
||||||
"~t",
|
|
||||||
"~d",
|
|
||||||
"~m",
|
|
||||||
"~u",
|
|
||||||
"~c",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
Cuts = typing.Sequence[
|
|
||||||
typing.Sequence[typing.Union[str, bytes]]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Cut(str):
|
|
||||||
# This is an awkward location for these values, but it's better than having
|
|
||||||
# the console core import and depend on an addon. FIXME: Add a way for
|
|
||||||
# addons to add custom types and manage their completion and validation.
|
|
||||||
valid_prefixes = [
|
|
||||||
"request.method",
|
|
||||||
"request.scheme",
|
|
||||||
"request.host",
|
|
||||||
"request.http_version",
|
|
||||||
"request.port",
|
|
||||||
"request.path",
|
|
||||||
"request.url",
|
|
||||||
"request.text",
|
|
||||||
"request.content",
|
|
||||||
"request.raw_content",
|
|
||||||
"request.timestamp_start",
|
|
||||||
"request.timestamp_end",
|
|
||||||
"request.header[",
|
|
||||||
|
|
||||||
"response.status_code",
|
|
||||||
"response.reason",
|
|
||||||
"response.text",
|
|
||||||
"response.content",
|
|
||||||
"response.timestamp_start",
|
|
||||||
"response.timestamp_end",
|
|
||||||
"response.raw_content",
|
|
||||||
"response.header[",
|
|
||||||
|
|
||||||
"client_conn.address.port",
|
|
||||||
"client_conn.address.host",
|
|
||||||
"client_conn.tls_version",
|
|
||||||
"client_conn.sni",
|
|
||||||
"client_conn.ssl_established",
|
|
||||||
|
|
||||||
"server_conn.address.port",
|
|
||||||
"server_conn.address.host",
|
|
||||||
"server_conn.ip_address.host",
|
|
||||||
"server_conn.tls_version",
|
|
||||||
"server_conn.sni",
|
|
||||||
"server_conn.ssl_established",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Path(str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Cmd(str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Arg(str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def typename(t: type) -> str:
|
def typename(t: type) -> str:
|
||||||
"""
|
"""
|
||||||
Translates a type to an explanatory string. If ret is True, we're
|
Translates a type to an explanatory string.
|
||||||
looking at a return type, else we're looking at a parameter type.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(t, Choice):
|
to = mitmproxy.types.CommandTypes.get(t, None)
|
||||||
return "choice"
|
if not to:
|
||||||
elif t == typing.Sequence[flow.Flow]:
|
|
||||||
return "[flow]"
|
|
||||||
elif t == typing.Sequence[str]:
|
|
||||||
return "[str]"
|
|
||||||
elif t == typing.Sequence[Cut]:
|
|
||||||
return "[cut]"
|
|
||||||
elif t == Cuts:
|
|
||||||
return "[cuts]"
|
|
||||||
elif t == flow.Flow:
|
|
||||||
return "flow"
|
|
||||||
elif issubclass(t, (str, int, bool)):
|
|
||||||
return t.__name__.lower()
|
|
||||||
else: # pragma: no cover
|
|
||||||
raise NotImplementedError(t)
|
raise NotImplementedError(t)
|
||||||
|
return to.display
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
@ -168,7 +69,7 @@ class Command:
|
|||||||
ret = " -> " + ret
|
ret = " -> " + ret
|
||||||
return "%s %s%s" % (self.path, params, ret)
|
return "%s %s%s" % (self.path, params, ret)
|
||||||
|
|
||||||
def call(self, args: typing.Sequence[str]):
|
def call(self, args: typing.Sequence[str]) -> typing.Any:
|
||||||
"""
|
"""
|
||||||
Call the command with a list of arguments. At this point, all
|
Call the command with a list of arguments. At this point, all
|
||||||
arguments are strings.
|
arguments are strings.
|
||||||
@ -255,13 +156,13 @@ class CommandManager:
|
|||||||
typ = None # type: typing.Type
|
typ = None # type: typing.Type
|
||||||
for i in range(len(parts)):
|
for i in range(len(parts)):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
typ = Cmd
|
typ = mitmproxy.types.Cmd
|
||||||
if parts[i] in self.commands:
|
if parts[i] in self.commands:
|
||||||
params.extend(self.commands[parts[i]].paramtypes)
|
params.extend(self.commands[parts[i]].paramtypes)
|
||||||
elif params:
|
elif params:
|
||||||
typ = params.pop(0)
|
typ = params.pop(0)
|
||||||
# FIXME: Do we need to check that Arg is positional?
|
# FIXME: Do we need to check that Arg is positional?
|
||||||
if typ == Cmd and params and params[0] == Arg:
|
if typ == mitmproxy.types.Cmd and params and params[0] == mitmproxy.types.Arg:
|
||||||
if parts[i] in self.commands:
|
if parts[i] in self.commands:
|
||||||
params[:] = self.commands[parts[i]].paramtypes
|
params[:] = self.commands[parts[i]].paramtypes
|
||||||
else:
|
else:
|
||||||
@ -269,7 +170,7 @@ class CommandManager:
|
|||||||
parse.append(ParseResult(value=parts[i], type=typ))
|
parse.append(ParseResult(value=parts[i], type=typ))
|
||||||
return parse
|
return parse
|
||||||
|
|
||||||
def call_args(self, path, args):
|
def call_args(self, path: str, args: typing.Sequence[str]) -> typing.Any:
|
||||||
"""
|
"""
|
||||||
Call a command using a list of string arguments. May raise CommandError.
|
Call a command using a list of string arguments. May raise CommandError.
|
||||||
"""
|
"""
|
||||||
@ -300,45 +201,13 @@ def parsearg(manager: CommandManager, spec: str, argtype: type) -> typing.Any:
|
|||||||
"""
|
"""
|
||||||
Convert a string to a argument to the appropriate type.
|
Convert a string to a argument to the appropriate type.
|
||||||
"""
|
"""
|
||||||
if isinstance(argtype, Choice):
|
t = mitmproxy.types.CommandTypes.get(argtype, None)
|
||||||
cmd = argtype.options_command
|
if not t:
|
||||||
opts = manager.call(cmd)
|
|
||||||
if spec not in opts:
|
|
||||||
raise exceptions.CommandError(
|
|
||||||
"Invalid choice: see %s for options" % cmd
|
|
||||||
)
|
|
||||||
return spec
|
|
||||||
elif issubclass(argtype, str):
|
|
||||||
return spec
|
|
||||||
elif argtype == bool:
|
|
||||||
if spec == "true":
|
|
||||||
return True
|
|
||||||
elif spec == "false":
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
raise exceptions.CommandError(
|
|
||||||
"Booleans are 'true' or 'false', got %s" % spec
|
|
||||||
)
|
|
||||||
elif issubclass(argtype, int):
|
|
||||||
try:
|
|
||||||
return int(spec)
|
|
||||||
except ValueError as e:
|
|
||||||
raise exceptions.CommandError("Expected an integer, got %s." % spec)
|
|
||||||
elif argtype == typing.Sequence[flow.Flow]:
|
|
||||||
return manager.call_args("view.resolve", [spec])
|
|
||||||
elif argtype == Cuts:
|
|
||||||
return manager.call_args("cut", [spec])
|
|
||||||
elif argtype == flow.Flow:
|
|
||||||
flows = manager.call_args("view.resolve", [spec])
|
|
||||||
if len(flows) != 1:
|
|
||||||
raise exceptions.CommandError(
|
|
||||||
"Command requires one flow, specification matched %s." % len(flows)
|
|
||||||
)
|
|
||||||
return flows[0]
|
|
||||||
elif argtype in (typing.Sequence[str], typing.Sequence[Cut]):
|
|
||||||
return [i.strip() for i in spec.split(",")]
|
|
||||||
else:
|
|
||||||
raise exceptions.CommandError("Unsupported argument type: %s" % argtype)
|
raise exceptions.CommandError("Unsupported argument type: %s" % argtype)
|
||||||
|
try:
|
||||||
|
return t.parse(manager, argtype, spec) # type: ignore
|
||||||
|
except exceptions.TypeError as e:
|
||||||
|
raise exceptions.CommandError from e
|
||||||
|
|
||||||
|
|
||||||
def verify_arg_signature(f: typing.Callable, args: list, kwargs: dict) -> None:
|
def verify_arg_signature(f: typing.Callable, args: list, kwargs: dict) -> None:
|
||||||
@ -360,20 +229,10 @@ def command(path):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class Choice:
|
|
||||||
def __init__(self, options_command):
|
|
||||||
self.options_command = options_command
|
|
||||||
|
|
||||||
def __instancecheck__(self, instance):
|
|
||||||
# return false here so that arguments are piped through parsearg,
|
|
||||||
# which does extended validation.
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def argument(name, type):
|
def argument(name, type):
|
||||||
"""
|
"""
|
||||||
Set the type of a command argument at runtime.
|
Set the type of a command argument at runtime. This is useful for more
|
||||||
This is useful for more specific types such as command.Choice, which we cannot annotate
|
specific types such as mitmproxy.types.Choice, which we cannot annotate
|
||||||
directly as mypy does not like that.
|
directly as mypy does not like that.
|
||||||
"""
|
"""
|
||||||
def decorator(f: types.FunctionType) -> types.FunctionType:
|
def decorator(f: types.FunctionType) -> types.FunctionType:
|
||||||
|
@ -112,6 +112,10 @@ class AddonHalt(MitmproxyException):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TypeError(MitmproxyException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Net-layer exceptions
|
Net-layer exceptions
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import abc
|
import abc
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
@ -9,6 +7,7 @@ from urwid.text_layout import calc_coords
|
|||||||
import mitmproxy.flow
|
import mitmproxy.flow
|
||||||
import mitmproxy.master
|
import mitmproxy.master
|
||||||
import mitmproxy.command
|
import mitmproxy.command
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
|
|
||||||
class Completer: # pragma: no cover
|
class Completer: # pragma: no cover
|
||||||
@ -39,30 +38,6 @@ class ListCompleter(Completer):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
# Generates the completion options for a specific starting input
|
|
||||||
def pathOptions(start: str) -> typing.Sequence[str]:
|
|
||||||
if not start:
|
|
||||||
start = "./"
|
|
||||||
path = os.path.expanduser(start)
|
|
||||||
ret = []
|
|
||||||
if os.path.isdir(path):
|
|
||||||
files = glob.glob(os.path.join(path, "*"))
|
|
||||||
prefix = start
|
|
||||||
else:
|
|
||||||
files = glob.glob(path + "*")
|
|
||||||
prefix = os.path.dirname(start)
|
|
||||||
prefix = prefix or "./"
|
|
||||||
for f in files:
|
|
||||||
display = os.path.join(prefix, os.path.normpath(os.path.basename(f)))
|
|
||||||
if os.path.isdir(f):
|
|
||||||
display += "/"
|
|
||||||
ret.append(display)
|
|
||||||
if not ret:
|
|
||||||
ret = [start]
|
|
||||||
ret.sort()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
CompletionState = typing.NamedTuple(
|
CompletionState = typing.NamedTuple(
|
||||||
"CompletionState",
|
"CompletionState",
|
||||||
[
|
[
|
||||||
@ -106,48 +81,12 @@ class CommandBuffer():
|
|||||||
if not self.completion:
|
if not self.completion:
|
||||||
parts = self.master.commands.parse_partial(self.buf[:self.cursor])
|
parts = self.master.commands.parse_partial(self.buf[:self.cursor])
|
||||||
last = parts[-1]
|
last = parts[-1]
|
||||||
if last.type == mitmproxy.command.Cmd:
|
ct = mitmproxy.types.CommandTypes.get(last.type, None)
|
||||||
|
if ct:
|
||||||
self.completion = CompletionState(
|
self.completion = CompletionState(
|
||||||
completer = ListCompleter(
|
completer = ListCompleter(
|
||||||
parts[-1].value,
|
parts[-1].value,
|
||||||
self.master.commands.commands.keys(),
|
ct.completion(self.master.commands, last.type, parts[-1].value)
|
||||||
),
|
|
||||||
parse = parts,
|
|
||||||
)
|
|
||||||
if last.type == typing.Sequence[mitmproxy.command.Cut]:
|
|
||||||
spec = parts[-1].value.split(",")
|
|
||||||
opts = []
|
|
||||||
for pref in mitmproxy.command.Cut.valid_prefixes:
|
|
||||||
spec[-1] = pref
|
|
||||||
opts.append(",".join(spec))
|
|
||||||
self.completion = CompletionState(
|
|
||||||
completer = ListCompleter(
|
|
||||||
parts[-1].value,
|
|
||||||
opts,
|
|
||||||
),
|
|
||||||
parse = parts,
|
|
||||||
)
|
|
||||||
elif isinstance(last.type, mitmproxy.command.Choice):
|
|
||||||
self.completion = CompletionState(
|
|
||||||
completer = ListCompleter(
|
|
||||||
parts[-1].value,
|
|
||||||
self.master.commands.call(last.type.options_command),
|
|
||||||
),
|
|
||||||
parse = parts,
|
|
||||||
)
|
|
||||||
elif last.type == mitmproxy.command.Path:
|
|
||||||
self.completion = CompletionState(
|
|
||||||
completer = ListCompleter(
|
|
||||||
"",
|
|
||||||
pathOptions(parts[1].value)
|
|
||||||
),
|
|
||||||
parse = parts,
|
|
||||||
)
|
|
||||||
elif last.type in (typing.Sequence[mitmproxy.flow.Flow], mitmproxy.flow.Flow):
|
|
||||||
self.completion = CompletionState(
|
|
||||||
completer = ListCompleter(
|
|
||||||
"",
|
|
||||||
mitmproxy.command.valid_flow_prefixes,
|
|
||||||
),
|
),
|
||||||
parse = parts,
|
parse = parts,
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,8 @@ from mitmproxy import exceptions
|
|||||||
from mitmproxy import flow
|
from mitmproxy import flow
|
||||||
from mitmproxy import contentviews
|
from mitmproxy import contentviews
|
||||||
from mitmproxy.utils import strutils
|
from mitmproxy.utils import strutils
|
||||||
|
import mitmproxy.types
|
||||||
|
|
||||||
|
|
||||||
from mitmproxy.tools.console import overlay
|
from mitmproxy.tools.console import overlay
|
||||||
from mitmproxy.tools.console import signals
|
from mitmproxy.tools.console import signals
|
||||||
@ -218,8 +220,8 @@ class ConsoleAddon:
|
|||||||
self,
|
self,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
choices: typing.Sequence[str],
|
choices: typing.Sequence[str],
|
||||||
cmd: command.Cmd,
|
cmd: mitmproxy.types.Cmd,
|
||||||
*args: command.Arg
|
*args: mitmproxy.types.Arg
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Prompt the user to choose from a specified list of strings, then
|
Prompt the user to choose from a specified list of strings, then
|
||||||
@ -241,7 +243,7 @@ class ConsoleAddon:
|
|||||||
|
|
||||||
@command.command("console.choose.cmd")
|
@command.command("console.choose.cmd")
|
||||||
def console_choose_cmd(
|
def console_choose_cmd(
|
||||||
self, prompt: str, choicecmd: command.Cmd, *cmd: command.Arg
|
self, prompt: str, choicecmd: mitmproxy.types.Cmd, *cmd: mitmproxy.types.Arg
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Prompt the user to choose from a list of strings returned by a
|
Prompt the user to choose from a list of strings returned by a
|
||||||
@ -352,7 +354,7 @@ class ConsoleAddon:
|
|||||||
]
|
]
|
||||||
|
|
||||||
@command.command("console.edit.focus")
|
@command.command("console.edit.focus")
|
||||||
@command.argument("part", type=command.Choice("console.edit.focus.options"))
|
@command.argument("part", type=mitmproxy.types.Choice("console.edit.focus.options"))
|
||||||
def edit_focus(self, part: str) -> None:
|
def edit_focus(self, part: str) -> None:
|
||||||
"""
|
"""
|
||||||
Edit a component of the currently focused flow.
|
Edit a component of the currently focused flow.
|
||||||
@ -404,14 +406,14 @@ class ConsoleAddon:
|
|||||||
self._grideditor().cmd_delete()
|
self._grideditor().cmd_delete()
|
||||||
|
|
||||||
@command.command("console.grideditor.load")
|
@command.command("console.grideditor.load")
|
||||||
def grideditor_load(self, path: command.Path) -> None:
|
def grideditor_load(self, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Read a file into the currrent cell.
|
Read a file into the currrent cell.
|
||||||
"""
|
"""
|
||||||
self._grideditor().cmd_read_file(path)
|
self._grideditor().cmd_read_file(path)
|
||||||
|
|
||||||
@command.command("console.grideditor.load_escaped")
|
@command.command("console.grideditor.load_escaped")
|
||||||
def grideditor_load_escaped(self, path: command.Path) -> None:
|
def grideditor_load_escaped(self, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Read a file containing a Python-style escaped string into the
|
Read a file containing a Python-style escaped string into the
|
||||||
currrent cell.
|
currrent cell.
|
||||||
@ -419,7 +421,7 @@ class ConsoleAddon:
|
|||||||
self._grideditor().cmd_read_file_escaped(path)
|
self._grideditor().cmd_read_file_escaped(path)
|
||||||
|
|
||||||
@command.command("console.grideditor.save")
|
@command.command("console.grideditor.save")
|
||||||
def grideditor_save(self, path: command.Path) -> None:
|
def grideditor_save(self, path: mitmproxy.types.Path) -> None:
|
||||||
"""
|
"""
|
||||||
Save data to file as a CSV.
|
Save data to file as a CSV.
|
||||||
"""
|
"""
|
||||||
@ -440,7 +442,7 @@ class ConsoleAddon:
|
|||||||
self._grideditor().cmd_spawn_editor()
|
self._grideditor().cmd_spawn_editor()
|
||||||
|
|
||||||
@command.command("console.flowview.mode.set")
|
@command.command("console.flowview.mode.set")
|
||||||
@command.argument("mode", type=command.Choice("console.flowview.mode.options"))
|
@command.argument("mode", type=mitmproxy.types.Choice("console.flowview.mode.options"))
|
||||||
def flowview_mode_set(self, mode: str) -> None:
|
def flowview_mode_set(self, mode: str) -> None:
|
||||||
"""
|
"""
|
||||||
Set the display mode for the current flow view.
|
Set the display mode for the current flow view.
|
||||||
@ -498,8 +500,8 @@ class ConsoleAddon:
|
|||||||
self,
|
self,
|
||||||
contexts: typing.Sequence[str],
|
contexts: typing.Sequence[str],
|
||||||
key: str,
|
key: str,
|
||||||
cmd: command.Cmd,
|
cmd: mitmproxy.types.Cmd,
|
||||||
*args: command.Arg
|
*args: mitmproxy.types.Arg
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Bind a shortcut key.
|
Bind a shortcut key.
|
||||||
|
330
mitmproxy/types.py
Normal file
330
mitmproxy/types.py
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
import os
|
||||||
|
import glob
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from mitmproxy import exceptions
|
||||||
|
from mitmproxy import flow
|
||||||
|
|
||||||
|
|
||||||
|
class Path(str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Cmd(str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Arg(str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CutSpec(typing.Sequence[str]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Data(typing.Sequence[typing.Sequence[typing.Union[str, bytes]]]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Choice:
|
||||||
|
def __init__(self, options_command):
|
||||||
|
self.options_command = options_command
|
||||||
|
|
||||||
|
def __instancecheck__(self, instance): # pragma: no cover
|
||||||
|
# return false here so that arguments are piped through parsearg,
|
||||||
|
# which does extended validation.
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# One of the many charming things about mypy is that introducing type
|
||||||
|
# annotations can cause circular dependencies where there were none before.
|
||||||
|
# Rather than putting types and the CommandManger in the same file, we introduce
|
||||||
|
# a stub type with the signature we use.
|
||||||
|
class _CommandStub:
|
||||||
|
commands = {} # type: typing.Mapping[str, typing.Any]
|
||||||
|
|
||||||
|
def call_args(self, path: str, args: typing.Sequence[str]) -> typing.Any: # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def call(self, args: typing.Sequence[str]) -> typing.Any: # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseType:
|
||||||
|
typ = object # type: typing.Type
|
||||||
|
display = "" # type: str
|
||||||
|
|
||||||
|
def completion(
|
||||||
|
self, manager: _CommandStub, t: type, s: str
|
||||||
|
) -> typing.Sequence[str]: # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse(
|
||||||
|
self, manager: _CommandStub, t: type, s: str
|
||||||
|
) -> typing.Any: # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Bool(BaseType):
|
||||||
|
typ = bool
|
||||||
|
display = "bool"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return ["false", "true"]
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> bool:
|
||||||
|
if s == "true":
|
||||||
|
return True
|
||||||
|
elif s == "false":
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise exceptions.TypeError(
|
||||||
|
"Booleans are 'true' or 'false', got %s" % s
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Str(BaseType):
|
||||||
|
typ = str
|
||||||
|
display = "str"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> str:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class Int(BaseType):
|
||||||
|
typ = int
|
||||||
|
display = "int"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> int:
|
||||||
|
try:
|
||||||
|
return int(s)
|
||||||
|
except ValueError as e:
|
||||||
|
raise exceptions.TypeError from e
|
||||||
|
|
||||||
|
|
||||||
|
class PathType(BaseType):
|
||||||
|
typ = Path
|
||||||
|
display = "path"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, start: str) -> typing.Sequence[str]:
|
||||||
|
if not start:
|
||||||
|
start = "./"
|
||||||
|
path = os.path.expanduser(start)
|
||||||
|
ret = []
|
||||||
|
if os.path.isdir(path):
|
||||||
|
files = glob.glob(os.path.join(path, "*"))
|
||||||
|
prefix = start
|
||||||
|
else:
|
||||||
|
files = glob.glob(path + "*")
|
||||||
|
prefix = os.path.dirname(start)
|
||||||
|
prefix = prefix or "./"
|
||||||
|
for f in files:
|
||||||
|
display = os.path.join(prefix, os.path.normpath(os.path.basename(f)))
|
||||||
|
if os.path.isdir(f):
|
||||||
|
display += "/"
|
||||||
|
ret.append(display)
|
||||||
|
if not ret:
|
||||||
|
ret = [start]
|
||||||
|
ret.sort()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> str:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class CmdType(BaseType):
|
||||||
|
typ = Cmd
|
||||||
|
display = "cmd"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return list(manager.commands.keys())
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> str:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class ArgType(BaseType):
|
||||||
|
typ = Arg
|
||||||
|
display = "arg"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> str:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class StrSeq(BaseType):
|
||||||
|
typ = typing.Sequence[str]
|
||||||
|
display = "[str]"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return [x.strip() for x in s.split(",")]
|
||||||
|
|
||||||
|
|
||||||
|
class CutSpecType(BaseType):
|
||||||
|
typ = CutSpec
|
||||||
|
display = "[cut]"
|
||||||
|
valid_prefixes = [
|
||||||
|
"request.method",
|
||||||
|
"request.scheme",
|
||||||
|
"request.host",
|
||||||
|
"request.http_version",
|
||||||
|
"request.port",
|
||||||
|
"request.path",
|
||||||
|
"request.url",
|
||||||
|
"request.text",
|
||||||
|
"request.content",
|
||||||
|
"request.raw_content",
|
||||||
|
"request.timestamp_start",
|
||||||
|
"request.timestamp_end",
|
||||||
|
"request.header[",
|
||||||
|
|
||||||
|
"response.status_code",
|
||||||
|
"response.reason",
|
||||||
|
"response.text",
|
||||||
|
"response.content",
|
||||||
|
"response.timestamp_start",
|
||||||
|
"response.timestamp_end",
|
||||||
|
"response.raw_content",
|
||||||
|
"response.header[",
|
||||||
|
|
||||||
|
"client_conn.address.port",
|
||||||
|
"client_conn.address.host",
|
||||||
|
"client_conn.tls_version",
|
||||||
|
"client_conn.sni",
|
||||||
|
"client_conn.ssl_established",
|
||||||
|
|
||||||
|
"server_conn.address.port",
|
||||||
|
"server_conn.address.host",
|
||||||
|
"server_conn.ip_address.host",
|
||||||
|
"server_conn.tls_version",
|
||||||
|
"server_conn.sni",
|
||||||
|
"server_conn.ssl_established",
|
||||||
|
]
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
spec = s.split(",")
|
||||||
|
opts = []
|
||||||
|
for pref in self.valid_prefixes:
|
||||||
|
spec[-1] = pref
|
||||||
|
opts.append(",".join(spec))
|
||||||
|
return opts
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> CutSpec:
|
||||||
|
parts = s.split(",") # type: typing.Any
|
||||||
|
return parts
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFlowType(BaseType):
|
||||||
|
valid_prefixes = [
|
||||||
|
"@all",
|
||||||
|
"@focus",
|
||||||
|
"@shown",
|
||||||
|
"@hidden",
|
||||||
|
"@marked",
|
||||||
|
"@unmarked",
|
||||||
|
"~q",
|
||||||
|
"~s",
|
||||||
|
"~a",
|
||||||
|
"~hq",
|
||||||
|
"~hs",
|
||||||
|
"~b",
|
||||||
|
"~bq",
|
||||||
|
"~bs",
|
||||||
|
"~t",
|
||||||
|
"~d",
|
||||||
|
"~m",
|
||||||
|
"~u",
|
||||||
|
"~c",
|
||||||
|
]
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[str]:
|
||||||
|
return self.valid_prefixes
|
||||||
|
|
||||||
|
|
||||||
|
class FlowType(BaseFlowType):
|
||||||
|
typ = flow.Flow
|
||||||
|
display = "flow"
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> flow.Flow:
|
||||||
|
flows = manager.call_args("view.resolve", [s])
|
||||||
|
if len(flows) != 1:
|
||||||
|
raise exceptions.TypeError(
|
||||||
|
"Command requires one flow, specification matched %s." % len(flows)
|
||||||
|
)
|
||||||
|
return flows[0]
|
||||||
|
|
||||||
|
|
||||||
|
class FlowsType(BaseFlowType):
|
||||||
|
typ = typing.Sequence[flow.Flow]
|
||||||
|
display = "[flow]"
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: type, s: str) -> typing.Sequence[flow.Flow]:
|
||||||
|
return manager.call_args("view.resolve", [s])
|
||||||
|
|
||||||
|
|
||||||
|
class DataType:
|
||||||
|
typ = Data
|
||||||
|
display = "[data]"
|
||||||
|
|
||||||
|
def completion(
|
||||||
|
self, manager: _CommandStub, t: type, s: str
|
||||||
|
) -> typing.Sequence[str]: # pragma: no cover
|
||||||
|
raise exceptions.TypeError("data cannot be passed as argument")
|
||||||
|
|
||||||
|
def parse(
|
||||||
|
self, manager: _CommandStub, t: type, s: str
|
||||||
|
) -> typing.Any: # pragma: no cover
|
||||||
|
raise exceptions.TypeError("data cannot be passed as argument")
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceType:
|
||||||
|
typ = Choice
|
||||||
|
display = "choice"
|
||||||
|
|
||||||
|
def completion(self, manager: _CommandStub, t: Choice, s: str) -> typing.Sequence[str]:
|
||||||
|
return manager.call(t.options_command)
|
||||||
|
|
||||||
|
def parse(self, manager: _CommandStub, t: Choice, s: str) -> str:
|
||||||
|
opts = manager.call(t.options_command)
|
||||||
|
if s not in opts:
|
||||||
|
raise exceptions.TypeError("Invalid choice.")
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class TypeManager:
|
||||||
|
def __init__(self, *types):
|
||||||
|
self.typemap = {}
|
||||||
|
for t in types:
|
||||||
|
self.typemap[t.typ] = t()
|
||||||
|
|
||||||
|
def get(self, t: type, default=None) -> BaseType:
|
||||||
|
if type(t) in self.typemap:
|
||||||
|
return self.typemap[type(t)]
|
||||||
|
return self.typemap.get(t, default)
|
||||||
|
|
||||||
|
|
||||||
|
CommandTypes = TypeManager(
|
||||||
|
ArgType,
|
||||||
|
Bool,
|
||||||
|
ChoiceType,
|
||||||
|
CmdType,
|
||||||
|
CutSpecType,
|
||||||
|
DataType,
|
||||||
|
FlowType,
|
||||||
|
FlowsType,
|
||||||
|
Int,
|
||||||
|
PathType,
|
||||||
|
Str,
|
||||||
|
StrSeq,
|
||||||
|
)
|
@ -4,6 +4,7 @@ from mitmproxy import flow
|
|||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy.test import tflow
|
from mitmproxy.test import tflow
|
||||||
from mitmproxy.test import taddons
|
from mitmproxy.test import taddons
|
||||||
|
import mitmproxy.types
|
||||||
import io
|
import io
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ class TAddon:
|
|||||||
return foo
|
return foo
|
||||||
|
|
||||||
@command.command("subcommand")
|
@command.command("subcommand")
|
||||||
def subcommand(self, cmd: command.Cmd, *args: command.Arg) -> str:
|
def subcommand(self, cmd: mitmproxy.types.Cmd, *args: mitmproxy.types.Arg) -> str:
|
||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
@command.command("empty")
|
@command.command("empty")
|
||||||
@ -39,12 +40,12 @@ class TAddon:
|
|||||||
def choices(self) -> typing.Sequence[str]:
|
def choices(self) -> typing.Sequence[str]:
|
||||||
return ["one", "two", "three"]
|
return ["one", "two", "three"]
|
||||||
|
|
||||||
@command.argument("arg", type=command.Choice("choices"))
|
@command.argument("arg", type=mitmproxy.types.Choice("choices"))
|
||||||
def choose(self, arg: str) -> typing.Sequence[str]:
|
def choose(self, arg: str) -> typing.Sequence[str]:
|
||||||
return ["one", "two", "three"]
|
return ["one", "two", "three"]
|
||||||
|
|
||||||
@command.command("path")
|
@command.command("path")
|
||||||
def path(self, arg: command.Path) -> None:
|
def path(self, arg: mitmproxy.types.Path) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -79,45 +80,45 @@ class TestCommand:
|
|||||||
[
|
[
|
||||||
"foo bar",
|
"foo bar",
|
||||||
[
|
[
|
||||||
command.ParseResult(value = "foo", type = command.Cmd),
|
command.ParseResult(value = "foo", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "bar", type = str)
|
command.ParseResult(value = "bar", type = str)
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"foo 'bar",
|
"foo 'bar",
|
||||||
[
|
[
|
||||||
command.ParseResult(value = "foo", type = command.Cmd),
|
command.ParseResult(value = "foo", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "'bar", type = str)
|
command.ParseResult(value = "'bar", type = str)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
["a", [command.ParseResult(value = "a", type = command.Cmd)]],
|
["a", [command.ParseResult(value = "a", type = mitmproxy.types.Cmd)]],
|
||||||
["", [command.ParseResult(value = "", type = command.Cmd)]],
|
["", [command.ParseResult(value = "", type = mitmproxy.types.Cmd)]],
|
||||||
[
|
[
|
||||||
"cmd3 1",
|
"cmd3 1",
|
||||||
[
|
[
|
||||||
command.ParseResult(value = "cmd3", type = command.Cmd),
|
command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "1", type = int),
|
command.ParseResult(value = "1", type = int),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"cmd3 ",
|
"cmd3 ",
|
||||||
[
|
[
|
||||||
command.ParseResult(value = "cmd3", type = command.Cmd),
|
command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "", type = int),
|
command.ParseResult(value = "", type = int),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"subcommand ",
|
"subcommand ",
|
||||||
[
|
[
|
||||||
command.ParseResult(value = "subcommand", type = command.Cmd),
|
command.ParseResult(value = "subcommand", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "", type = command.Cmd),
|
command.ParseResult(value = "", type = mitmproxy.types.Cmd),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"subcommand cmd3 ",
|
"subcommand cmd3 ",
|
||||||
[
|
[
|
||||||
command.ParseResult(value = "subcommand", type = command.Cmd),
|
command.ParseResult(value = "subcommand", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "cmd3", type = command.Cmd),
|
command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd),
|
||||||
command.ParseResult(value = "", type = int),
|
command.ParseResult(value = "", type = int),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
@ -154,15 +155,15 @@ def test_typename():
|
|||||||
assert command.typename(str) == "str"
|
assert command.typename(str) == "str"
|
||||||
assert command.typename(typing.Sequence[flow.Flow]) == "[flow]"
|
assert command.typename(typing.Sequence[flow.Flow]) == "[flow]"
|
||||||
|
|
||||||
assert command.typename(command.Cuts) == "[cuts]"
|
assert command.typename(mitmproxy.types.Data) == "[data]"
|
||||||
assert command.typename(typing.Sequence[command.Cut]) == "[cut]"
|
assert command.typename(mitmproxy.types.CutSpec) == "[cut]"
|
||||||
|
|
||||||
assert command.typename(flow.Flow) == "flow"
|
assert command.typename(flow.Flow) == "flow"
|
||||||
assert command.typename(typing.Sequence[str]) == "[str]"
|
assert command.typename(typing.Sequence[str]) == "[str]"
|
||||||
|
|
||||||
assert command.typename(command.Choice("foo")) == "choice"
|
assert command.typename(mitmproxy.types.Choice("foo")) == "choice"
|
||||||
assert command.typename(command.Path) == "path"
|
assert command.typename(mitmproxy.types.Path) == "path"
|
||||||
assert command.typename(command.Cmd) == "cmd"
|
assert command.typename(mitmproxy.types.Cmd) == "cmd"
|
||||||
|
|
||||||
|
|
||||||
class DummyConsole:
|
class DummyConsole:
|
||||||
@ -172,7 +173,7 @@ class DummyConsole:
|
|||||||
return [tflow.tflow(resp=True)] * n
|
return [tflow.tflow(resp=True)] * n
|
||||||
|
|
||||||
@command.command("cut")
|
@command.command("cut")
|
||||||
def cut(self, spec: str) -> command.Cuts:
|
def cut(self, spec: str) -> mitmproxy.types.Data:
|
||||||
return [["test"]]
|
return [["test"]]
|
||||||
|
|
||||||
|
|
||||||
@ -201,10 +202,6 @@ def test_parsearg():
|
|||||||
with pytest.raises(exceptions.CommandError):
|
with pytest.raises(exceptions.CommandError):
|
||||||
command.parsearg(tctx.master.commands, "foo", Exception)
|
command.parsearg(tctx.master.commands, "foo", Exception)
|
||||||
|
|
||||||
assert command.parsearg(
|
|
||||||
tctx.master.commands, "foo", command.Cuts
|
|
||||||
) == [["test"]]
|
|
||||||
|
|
||||||
assert command.parsearg(
|
assert command.parsearg(
|
||||||
tctx.master.commands, "foo", typing.Sequence[str]
|
tctx.master.commands, "foo", typing.Sequence[str]
|
||||||
) == ["foo"]
|
) == ["foo"]
|
||||||
@ -215,18 +212,18 @@ def test_parsearg():
|
|||||||
a = TAddon()
|
a = TAddon()
|
||||||
tctx.master.commands.add("choices", a.choices)
|
tctx.master.commands.add("choices", a.choices)
|
||||||
assert command.parsearg(
|
assert command.parsearg(
|
||||||
tctx.master.commands, "one", command.Choice("choices"),
|
tctx.master.commands, "one", mitmproxy.types.Choice("choices"),
|
||||||
) == "one"
|
) == "one"
|
||||||
with pytest.raises(exceptions.CommandError):
|
with pytest.raises(exceptions.CommandError):
|
||||||
assert command.parsearg(
|
assert command.parsearg(
|
||||||
tctx.master.commands, "invalid", command.Choice("choices"),
|
tctx.master.commands, "invalid", mitmproxy.types.Choice("choices"),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert command.parsearg(
|
assert command.parsearg(
|
||||||
tctx.master.commands, "foo", command.Path
|
tctx.master.commands, "foo", mitmproxy.types.Path
|
||||||
) == "foo"
|
) == "foo"
|
||||||
assert command.parsearg(
|
assert command.parsearg(
|
||||||
tctx.master.commands, "foo", command.Cmd
|
tctx.master.commands, "foo", mitmproxy.types.Cmd
|
||||||
) == "foo"
|
) == "foo"
|
||||||
|
|
||||||
|
|
||||||
@ -272,5 +269,5 @@ def test_choice():
|
|||||||
basic typechecking for choices should fail as we cannot verify if strings are a valid choice
|
basic typechecking for choices should fail as we cannot verify if strings are a valid choice
|
||||||
at this point.
|
at this point.
|
||||||
"""
|
"""
|
||||||
c = command.Choice("foo")
|
c = mitmproxy.types.Choice("foo")
|
||||||
assert not typecheck.check_command_type("foo", c)
|
assert not typecheck.check_command_type("foo", c)
|
||||||
|
0
test/mitmproxy/test_typemanager.py
Normal file
0
test/mitmproxy/test_typemanager.py
Normal file
175
test/mitmproxy/test_types.py
Normal file
175
test/mitmproxy/test_types.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import typing
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
from mitmproxy.test import tutils
|
||||||
|
import mitmproxy.exceptions
|
||||||
|
import mitmproxy.types
|
||||||
|
from mitmproxy.test import taddons
|
||||||
|
from mitmproxy.test import tflow
|
||||||
|
from mitmproxy import command
|
||||||
|
from mitmproxy import flow
|
||||||
|
|
||||||
|
from . import test_command
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def chdir(path: str):
|
||||||
|
old_dir = os.getcwd()
|
||||||
|
os.chdir(path)
|
||||||
|
yield
|
||||||
|
os.chdir(old_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bool():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.Bool()
|
||||||
|
assert b.completion(tctx.master.commands, bool, "b") == ["false", "true"]
|
||||||
|
assert b.parse(tctx.master.commands, bool, "true") is True
|
||||||
|
assert b.parse(tctx.master.commands, bool, "false") is False
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
b.parse(tctx.master.commands, bool, "foo")
|
||||||
|
|
||||||
|
|
||||||
|
def test_str():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.Str()
|
||||||
|
assert b.completion(tctx.master.commands, str, "") == []
|
||||||
|
assert b.parse(tctx.master.commands, str, "foo") == "foo"
|
||||||
|
|
||||||
|
|
||||||
|
def test_int():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.Int()
|
||||||
|
assert b.completion(tctx.master.commands, int, "b") == []
|
||||||
|
assert b.parse(tctx.master.commands, int, "1") == 1
|
||||||
|
assert b.parse(tctx.master.commands, int, "999") == 999
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
b.parse(tctx.master.commands, int, "foo")
|
||||||
|
|
||||||
|
|
||||||
|
def test_path():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.PathType()
|
||||||
|
assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/foo") == "/foo"
|
||||||
|
assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/bar") == "/bar"
|
||||||
|
|
||||||
|
def normPathOpts(prefix, match):
|
||||||
|
ret = []
|
||||||
|
for s in b.completion(tctx.master.commands, mitmproxy.types.Path, match):
|
||||||
|
s = s[len(prefix):]
|
||||||
|
s = s.replace(os.sep, "/")
|
||||||
|
ret.append(s)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
cd = os.path.normpath(tutils.test_data.path("mitmproxy/completion"))
|
||||||
|
assert normPathOpts(cd, cd) == ['/aaa', '/aab', '/aac', '/bbb/']
|
||||||
|
assert normPathOpts(cd, os.path.join(cd, "a")) == ['/aaa', '/aab', '/aac']
|
||||||
|
with chdir(cd):
|
||||||
|
assert normPathOpts("", "./") == ['./aaa', './aab', './aac', './bbb/']
|
||||||
|
assert normPathOpts("", "") == ['./aaa', './aab', './aac', './bbb/']
|
||||||
|
assert b.completion(
|
||||||
|
tctx.master.commands, mitmproxy.types.Path, "nonexistent"
|
||||||
|
) == ["nonexistent"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cmd():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
tctx.master.addons.add(test_command.TAddon())
|
||||||
|
b = mitmproxy.types.CmdType()
|
||||||
|
assert b.parse(tctx.master.commands, mitmproxy.types.Cmd, "foo") == "foo"
|
||||||
|
assert len(
|
||||||
|
b.completion(tctx.master.commands, mitmproxy.types.Cmd, "")
|
||||||
|
) == len(tctx.master.commands.commands.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def test_cutspec():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.CutSpecType()
|
||||||
|
b.parse(tctx.master.commands, mitmproxy.types.CutSpec, "foo,bar") == ["foo", "bar"]
|
||||||
|
assert b.completion(
|
||||||
|
tctx.master.commands, mitmproxy.types.CutSpec, "request.p"
|
||||||
|
) == b.valid_prefixes
|
||||||
|
ret = b.completion(tctx.master.commands, mitmproxy.types.CutSpec, "request.port,f")
|
||||||
|
assert ret[0].startswith("request.port,")
|
||||||
|
assert len(ret) == len(b.valid_prefixes)
|
||||||
|
|
||||||
|
|
||||||
|
def test_arg():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.ArgType()
|
||||||
|
assert b.completion(tctx.master.commands, mitmproxy.types.Arg, "") == []
|
||||||
|
assert b.parse(tctx.master.commands, mitmproxy.types.Arg, "foo") == "foo"
|
||||||
|
|
||||||
|
|
||||||
|
def test_strseq():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.StrSeq()
|
||||||
|
assert b.completion(tctx.master.commands, typing.Sequence[str], "") == []
|
||||||
|
assert b.parse(tctx.master.commands, typing.Sequence[str], "foo") == ["foo"]
|
||||||
|
assert b.parse(tctx.master.commands, typing.Sequence[str], "foo,bar") == ["foo", "bar"]
|
||||||
|
|
||||||
|
|
||||||
|
class DummyConsole:
|
||||||
|
@command.command("view.resolve")
|
||||||
|
def resolve(self, spec: str) -> typing.Sequence[flow.Flow]:
|
||||||
|
n = int(spec)
|
||||||
|
return [tflow.tflow(resp=True)] * n
|
||||||
|
|
||||||
|
@command.command("cut")
|
||||||
|
def cut(self, spec: str) -> mitmproxy.types.Data:
|
||||||
|
return [["test"]]
|
||||||
|
|
||||||
|
@command.command("options")
|
||||||
|
def options(self) -> typing.Sequence[str]:
|
||||||
|
return ["one", "two", "three"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_flow():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
tctx.master.addons.add(DummyConsole())
|
||||||
|
b = mitmproxy.types.FlowType()
|
||||||
|
assert len(b.completion(tctx.master.commands, flow.Flow, "")) == len(b.valid_prefixes)
|
||||||
|
assert b.parse(tctx.master.commands, flow.Flow, "1")
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
assert b.parse(tctx.master.commands, flow.Flow, "0")
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
assert b.parse(tctx.master.commands, flow.Flow, "2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_flows():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
tctx.master.addons.add(DummyConsole())
|
||||||
|
b = mitmproxy.types.FlowsType()
|
||||||
|
assert len(
|
||||||
|
b.completion(tctx.master.commands, typing.Sequence[flow.Flow], "")
|
||||||
|
) == len(b.valid_prefixes)
|
||||||
|
assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "0")) == 0
|
||||||
|
assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "1")) == 1
|
||||||
|
assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "2")) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_data():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
b = mitmproxy.types.DataType()
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
|
||||||
|
|
||||||
|
|
||||||
|
def test_choice():
|
||||||
|
with taddons.context() as tctx:
|
||||||
|
tctx.master.addons.add(DummyConsole())
|
||||||
|
b = mitmproxy.types.ChoiceType()
|
||||||
|
comp = b.completion(tctx.master.commands, mitmproxy.types.Choice("options"), "")
|
||||||
|
assert comp == ["one", "two", "three"]
|
||||||
|
assert b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "one") == "one"
|
||||||
|
with pytest.raises(mitmproxy.exceptions.TypeError):
|
||||||
|
b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "invalid")
|
||||||
|
|
||||||
|
|
||||||
|
def test_typemanager():
|
||||||
|
assert mitmproxy.types.CommandTypes.get(bool, None)
|
||||||
|
assert mitmproxy.types.CommandTypes.get(mitmproxy.types.Choice("choide"), None)
|
@ -1,36 +1,6 @@
|
|||||||
import os
|
|
||||||
import contextlib
|
|
||||||
|
|
||||||
from mitmproxy.tools.console.commander import commander
|
from mitmproxy.tools.console.commander import commander
|
||||||
from mitmproxy.test import taddons
|
from mitmproxy.test import taddons
|
||||||
from mitmproxy.test import tutils
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def chdir(path: str):
|
|
||||||
old_dir = os.getcwd()
|
|
||||||
os.chdir(path)
|
|
||||||
yield
|
|
||||||
os.chdir(old_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def normPathOpts(prefix, match):
|
|
||||||
ret = []
|
|
||||||
for s in commander.pathOptions(match):
|
|
||||||
s = s[len(prefix):]
|
|
||||||
s = s.replace(os.sep, "/")
|
|
||||||
ret.append(s)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def test_pathOptions():
|
|
||||||
cd = os.path.normpath(tutils.test_data.path("mitmproxy/completion"))
|
|
||||||
assert normPathOpts(cd, cd) == ['/aaa', '/aab', '/aac', '/bbb/']
|
|
||||||
assert normPathOpts(cd, os.path.join(cd, "a")) == ['/aaa', '/aab', '/aac']
|
|
||||||
with chdir(cd):
|
|
||||||
assert normPathOpts("", "./") == ['./aaa', './aab', './aac', './bbb/']
|
|
||||||
assert normPathOpts("", "") == ['./aaa', './aab', './aac', './bbb/']
|
|
||||||
assert commander.pathOptions("nonexistent") == ["nonexistent"]
|
|
||||||
|
|
||||||
|
|
||||||
class TestListCompleter:
|
class TestListCompleter:
|
||||||
|
@ -4,7 +4,6 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mitmproxy.utils import typecheck
|
from mitmproxy.utils import typecheck
|
||||||
from mitmproxy import command
|
|
||||||
|
|
||||||
|
|
||||||
class TBase:
|
class TBase:
|
||||||
@ -95,9 +94,6 @@ def test_check_command_type():
|
|||||||
assert(typecheck.check_command_type(None, None))
|
assert(typecheck.check_command_type(None, None))
|
||||||
assert(not typecheck.check_command_type(["foo"], typing.Sequence[int]))
|
assert(not typecheck.check_command_type(["foo"], typing.Sequence[int]))
|
||||||
assert(not typecheck.check_command_type("foo", typing.Sequence[int]))
|
assert(not typecheck.check_command_type("foo", typing.Sequence[int]))
|
||||||
assert(typecheck.check_command_type([["foo", b"bar"]], command.Cuts))
|
|
||||||
assert(not typecheck.check_command_type(["foo", b"bar"], command.Cuts))
|
|
||||||
assert(not typecheck.check_command_type([["foo", 22]], command.Cuts))
|
|
||||||
|
|
||||||
# Python 3.5 only defines __parameters__
|
# Python 3.5 only defines __parameters__
|
||||||
m = mock.Mock()
|
m = mock.Mock()
|
||||||
|
Loading…
Reference in New Issue
Block a user