diff options
Diffstat (limited to 'mitmproxy')
-rw-r--r-- | mitmproxy/contentviews.py | 2 | ||||
-rw-r--r-- | mitmproxy/master.py | 2 | ||||
-rw-r--r-- | mitmproxy/proxy/protocol/http2.py | 2 | ||||
-rw-r--r-- | mitmproxy/proxy/protocol/http_replay.py | 2 | ||||
-rw-r--r-- | mitmproxy/script/concurrent.py | 2 | ||||
-rw-r--r-- | mitmproxy/stateobject.py | 4 | ||||
-rw-r--r-- | mitmproxy/tcp.py | 4 | ||||
-rw-r--r-- | mitmproxy/types/__init__.py | 0 | ||||
-rw-r--r-- | mitmproxy/types/basethread.py | 14 | ||||
-rw-r--r-- | mitmproxy/types/multidict.py | 298 | ||||
-rw-r--r-- | mitmproxy/types/serializable.py | 32 | ||||
-rw-r--r-- | mitmproxy/utils/__init__.py | 0 |
12 files changed, 353 insertions, 9 deletions
diff --git a/mitmproxy/contentviews.py b/mitmproxy/contentviews.py index 07bf09f5..a171f36b 100644 --- a/mitmproxy/contentviews.py +++ b/mitmproxy/contentviews.py @@ -34,7 +34,7 @@ from PIL import Image from mitmproxy import exceptions from mitmproxy.contrib.wbxml import ASCommandResponse from netlib import http -from netlib import multidict +from mitmproxy.types import multidict from mitmproxy.utils import strutils from netlib.http import url diff --git a/mitmproxy/master.py b/mitmproxy/master.py index 1fc00112..2e57e57d 100644 --- a/mitmproxy/master.py +++ b/mitmproxy/master.py @@ -14,7 +14,7 @@ from mitmproxy import http from mitmproxy import log from mitmproxy import io from mitmproxy.proxy.protocol import http_replay -from netlib import basethread +from mitmproxy.types import basethread import netlib.http from . import ctx as mitmproxy_ctx diff --git a/mitmproxy/proxy/protocol/http2.py b/mitmproxy/proxy/protocol/http2.py index cbd8b34c..93ac51bc 100644 --- a/mitmproxy/proxy/protocol/http2.py +++ b/mitmproxy/proxy/protocol/http2.py @@ -15,7 +15,7 @@ from mitmproxy.proxy.protocol import base from mitmproxy.proxy.protocol import http as httpbase import netlib.http from netlib import tcp -from netlib import basethread +from mitmproxy.types import basethread from netlib.http import http2 diff --git a/mitmproxy/proxy/protocol/http_replay.py b/mitmproxy/proxy/protocol/http_replay.py index bf0697be..eef5a109 100644 --- a/mitmproxy/proxy/protocol/http_replay.py +++ b/mitmproxy/proxy/protocol/http_replay.py @@ -8,7 +8,7 @@ from mitmproxy import http from mitmproxy import flow from mitmproxy import connections from netlib.http import http1 -from netlib import basethread +from mitmproxy.types import basethread # TODO: Doesn't really belong into mitmproxy.proxy.protocol... diff --git a/mitmproxy/script/concurrent.py b/mitmproxy/script/concurrent.py index dc72e5b7..2fd7ad8d 100644 --- a/mitmproxy/script/concurrent.py +++ b/mitmproxy/script/concurrent.py @@ -4,7 +4,7 @@ offload computations from mitmproxy's main master thread. """ from mitmproxy import events -from netlib import basethread +from mitmproxy.types import basethread class ScriptThread(basethread.BaseThread): diff --git a/mitmproxy/stateobject.py b/mitmproxy/stateobject.py index f4415ecf..1ab744a5 100644 --- a/mitmproxy/stateobject.py +++ b/mitmproxy/stateobject.py @@ -1,7 +1,7 @@ from typing import Any from typing import List -import netlib.basetypes +from mitmproxy.types import serializable def _is_list(cls): @@ -10,7 +10,7 @@ def _is_list(cls): return issubclass(cls, List) or is_list_bugfix -class StateObject(netlib.basetypes.Serializable): +class StateObject(serializable.Serializable): """ An object with serializable state. diff --git a/mitmproxy/tcp.py b/mitmproxy/tcp.py index af54c9d4..d73be98d 100644 --- a/mitmproxy/tcp.py +++ b/mitmproxy/tcp.py @@ -2,11 +2,11 @@ import time from typing import List -import netlib.basetypes from mitmproxy import flow +from mitmproxy.types import serializable -class TCPMessage(netlib.basetypes.Serializable): +class TCPMessage(serializable.Serializable): def __init__(self, from_client, content, timestamp=None): self.content = content diff --git a/mitmproxy/types/__init__.py b/mitmproxy/types/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mitmproxy/types/__init__.py diff --git a/mitmproxy/types/basethread.py b/mitmproxy/types/basethread.py new file mode 100644 index 00000000..a3c81d19 --- /dev/null +++ b/mitmproxy/types/basethread.py @@ -0,0 +1,14 @@ +import time +import threading + + +class BaseThread(threading.Thread): + def __init__(self, name, *args, **kwargs): + super().__init__(name=name, *args, **kwargs) + self._thread_started = time.time() + + def _threadinfo(self): + return "%s - age: %is" % ( + self.name, + int(time.time() - self._thread_started) + ) diff --git a/mitmproxy/types/multidict.py b/mitmproxy/types/multidict.py new file mode 100644 index 00000000..d351e48b --- /dev/null +++ b/mitmproxy/types/multidict.py @@ -0,0 +1,298 @@ +from abc import ABCMeta, abstractmethod + + +try: + from collections.abc import MutableMapping +except ImportError: # pragma: no cover + from collections import MutableMapping # Workaround for Python < 3.3 + +from mitmproxy.types import serializable + + +class _MultiDict(MutableMapping, serializable.Serializable, metaclass=ABCMeta): + def __repr__(self): + fields = ( + repr(field) + for field in self.fields + ) + return "{cls}[{fields}]".format( + cls=type(self).__name__, + fields=", ".join(fields) + ) + + @staticmethod + @abstractmethod + def _reduce_values(values): + """ + If a user accesses multidict["foo"], this method + reduces all values for "foo" to a single value that is returned. + For example, HTTP headers are folded, whereas we will just take + the first cookie we found with that name. + """ + + @staticmethod + @abstractmethod + def _kconv(key): + """ + This method converts a key to its canonical representation. + For example, HTTP headers are case-insensitive, so this method returns key.lower(). + """ + + def __getitem__(self, key): + values = self.get_all(key) + if not values: + raise KeyError(key) + return self._reduce_values(values) + + def __setitem__(self, key, value): + self.set_all(key, [value]) + + def __delitem__(self, key): + if key not in self: + raise KeyError(key) + key = self._kconv(key) + self.fields = tuple( + field for field in self.fields + if key != self._kconv(field[0]) + ) + + def __iter__(self): + seen = set() + for key, _ in self.fields: + key_kconv = self._kconv(key) + if key_kconv not in seen: + seen.add(key_kconv) + yield key + + def __len__(self): + return len(set(self._kconv(key) for key, _ in self.fields)) + + def __eq__(self, other): + if isinstance(other, MultiDict): + return self.fields == other.fields + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def get_all(self, key): + """ + Return the list of all values for a given key. + If that key is not in the MultiDict, the return value will be an empty list. + """ + key = self._kconv(key) + return [ + value + for k, value in self.fields + if self._kconv(k) == key + ] + + def set_all(self, key, values): + """ + Remove the old values for a key and add new ones. + """ + key_kconv = self._kconv(key) + + new_fields = [] + for field in self.fields: + if self._kconv(field[0]) == key_kconv: + if values: + new_fields.append( + (field[0], values.pop(0)) + ) + else: + new_fields.append(field) + while values: + new_fields.append( + (key, values.pop(0)) + ) + self.fields = tuple(new_fields) + + def add(self, key, value): + """ + Add an additional value for the given key at the bottom. + """ + self.insert(len(self.fields), key, value) + + def insert(self, index, key, value): + """ + Insert an additional value for the given key at the specified position. + """ + item = (key, value) + self.fields = self.fields[:index] + (item,) + self.fields[index:] + + def keys(self, multi=False): + """ + Get all keys. + + Args: + multi(bool): + If True, one key per value will be returned. + If False, duplicate keys will only be returned once. + """ + return ( + k + for k, _ in self.items(multi) + ) + + def values(self, multi=False): + """ + Get all values. + + Args: + multi(bool): + If True, all values will be returned. + If False, only the first value per key will be returned. + """ + return ( + v + for _, v in self.items(multi) + ) + + def items(self, multi=False): + """ + Get all (key, value) tuples. + + Args: + multi(bool): + If True, all (key, value) pairs will be returned + If False, only the first (key, value) pair per unique key will be returned. + """ + if multi: + return self.fields + else: + return super().items() + + def collect(self): + """ + Returns a list of (key, value) tuples, where values are either + singular if there is only one matching item for a key, or a list + if there are more than one. The order of the keys matches the order + in the underlying fields list. + """ + coll = [] + for key in self: + values = self.get_all(key) + if len(values) == 1: + coll.append([key, values[0]]) + else: + coll.append([key, values]) + return coll + + def to_dict(self): + """ + Get the MultiDict as a plain Python dict. + Keys with multiple values are returned as lists. + + Example: + + .. code-block:: python + + # Simple dict with duplicate values. + >>> d = MultiDict([("name", "value"), ("a", False), ("a", 42)]) + >>> d.to_dict() + { + "name": "value", + "a": [False, 42] + } + """ + return { + k: v for k, v in self.collect() + } + + def get_state(self): + return self.fields + + def set_state(self, state): + self.fields = tuple(tuple(x) for x in state) + + @classmethod + def from_state(cls, state): + return cls(state) + + +class MultiDict(_MultiDict): + def __init__(self, fields=()): + super().__init__() + self.fields = tuple( + tuple(i) for i in fields + ) + + @staticmethod + def _reduce_values(values): + return values[0] + + @staticmethod + def _kconv(key): + return key + + +class ImmutableMultiDict(MultiDict, metaclass=ABCMeta): + def _immutable(self, *_): + raise TypeError('{} objects are immutable'.format(self.__class__.__name__)) + + __delitem__ = set_all = insert = _immutable + + def __hash__(self): + return hash(self.fields) + + def with_delitem(self, key): + """ + Returns: + An updated ImmutableMultiDict. The original object will not be modified. + """ + ret = self.copy() + # FIXME: This is filthy... + super(ImmutableMultiDict, ret).__delitem__(key) + return ret + + def with_set_all(self, key, values): + """ + Returns: + An updated ImmutableMultiDict. The original object will not be modified. + """ + ret = self.copy() + # FIXME: This is filthy... + super(ImmutableMultiDict, ret).set_all(key, values) + return ret + + def with_insert(self, index, key, value): + """ + Returns: + An updated ImmutableMultiDict. The original object will not be modified. + """ + ret = self.copy() + # FIXME: This is filthy... + super(ImmutableMultiDict, ret).insert(index, key, value) + return ret + + +class MultiDictView(_MultiDict): + """ + The MultiDictView provides the MultiDict interface over calculated data. + The view itself contains no state - data is retrieved from the parent on + request, and stored back to the parent on change. + """ + def __init__(self, getter, setter): + self._getter = getter + self._setter = setter + super().__init__() + + @staticmethod + def _kconv(key): + # All request-attributes are case-sensitive. + return key + + @staticmethod + def _reduce_values(values): + # We just return the first element if + # multiple elements exist with the same key. + return values[0] + + @property + def fields(self): + return self._getter() + + @fields.setter + def fields(self, value): + self._setter(value) diff --git a/mitmproxy/types/serializable.py b/mitmproxy/types/serializable.py new file mode 100644 index 00000000..49892ffc --- /dev/null +++ b/mitmproxy/types/serializable.py @@ -0,0 +1,32 @@ +import abc + + +class Serializable(metaclass=abc.ABCMeta): + """ + Abstract Base Class that defines an API to save an object's state and restore it later on. + """ + + @classmethod + @abc.abstractmethod + def from_state(cls, state): + """ + Create a new object from the given state. + """ + raise NotImplementedError() + + @abc.abstractmethod + def get_state(self): + """ + Retrieve object state. + """ + raise NotImplementedError() + + @abc.abstractmethod + def set_state(self, state): + """ + Set object state to the given state. + """ + raise NotImplementedError() + + def copy(self): + return self.from_state(self.get_state()) diff --git a/mitmproxy/utils/__init__.py b/mitmproxy/utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mitmproxy/utils/__init__.py |