Remove odict
- Adds default implementations for _kconv and _reduce_values to MultiDict. Without these, operations fail in really, really non-obvious ways. - Replace the remaining few instances of ODict Fixes #1159
This commit is contained in:
parent
90cb84b536
commit
c421c41307
|
@ -18,22 +18,22 @@ __prompt__, and __content\_types__ and a function named __\_\_call\_\___.
|
|||
Adding a new content viewer to parse a data type is as simple as writing a new
|
||||
View class. Your new content viewer View class should have the same properties
|
||||
as the other View classes: __name__, __prompt__, and __content\_types__ and a
|
||||
__\_\_call\_\___ function to parse the content of the request/response.
|
||||
__\_\_call\_\___ function to parse the content of the request/response.
|
||||
|
||||
* The __name__ property should be a string describing the contents and new content viewer;
|
||||
* The __name__ property should be a string describing the contents and new content viewer;
|
||||
* The __prompt__ property should be a two item tuple:
|
||||
|
||||
- __1__: A string that will be used to display the new content viewer's type; and
|
||||
- __2__: A one character string that will be the hotkey used to select the new content viewer from the Flow View screen;
|
||||
- __2__: A one character string that will be the hotkey used to select the new content viewer from the Flow View screen;
|
||||
|
||||
* The __content\_types__ property should be a list of strings of HTTP Content\-Types that the new content viewer can parse.
|
||||
* The __content\_types__ property should be a list of strings of HTTP Content\-Types that the new content viewer can parse.
|
||||
* Note that mitmproxy will use the content\_types to try and heuristically show a friendly view of content and that you can override the built-in views by populating content\_types with values for content\_types that are already parsed -- e.g. "image/png".
|
||||
|
||||
After defining the __name__, __prompt__, and __content\_types__ properties of
|
||||
the class, you should write the __\_\_call\_\___ function, which will parse the
|
||||
request/response data and provide a friendly view of the data. The
|
||||
__\_\_call\_\___ function should take the following arguments: __self__,
|
||||
__hdrs__, __content__, __limit__; __hdrs__ is a ODictCaseless object containing
|
||||
__hdrs__, __content__, __limit__; __hdrs__ is a MultiDict object containing
|
||||
the headers of the request/response; __content__ is the content of the
|
||||
request/response, and __limit__ is an integer representing the amount of data
|
||||
to display in the view window.
|
||||
|
@ -46,7 +46,7 @@ Alternatively, you can display content as a series of key-value pairs; to do
|
|||
so, prepare a list of lists, where each list item is a two item list -- a key
|
||||
that describes the data, and then the data itself; after preparing the list of
|
||||
lists, use the __common.format\_keyvals__ function on it to prepare it as text
|
||||
for display.
|
||||
for display.
|
||||
|
||||
If the new content viewer fails or throws an exception, mitmproxy will default
|
||||
to a __raw__ view.
|
||||
|
|
|
@ -33,7 +33,7 @@ from mitmproxy.contrib import jsbeautifier
|
|||
from mitmproxy.contrib.wbxml import ASCommandResponse
|
||||
from netlib import encoding
|
||||
from netlib import http
|
||||
from netlib import odict
|
||||
from netlib import multidict
|
||||
from netlib.http import url
|
||||
from netlib import strutils
|
||||
|
||||
|
@ -277,7 +277,7 @@ class ViewURLEncoded(View):
|
|||
|
||||
def __call__(self, data, **metadata):
|
||||
d = url.decode(data)
|
||||
return "URLEncoded form", format_dict(odict.ODict(d))
|
||||
return "URLEncoded form", format_dict(multidict.MultiDict(d))
|
||||
|
||||
|
||||
class ViewMultipart(View):
|
||||
|
@ -288,7 +288,7 @@ class ViewMultipart(View):
|
|||
@staticmethod
|
||||
def _format(v):
|
||||
yield [("highlight", "Form data:\n")]
|
||||
for message in format_dict(odict.ODict(v)):
|
||||
for message in format_dict(multidict.MultiDict(v)):
|
||||
yield message
|
||||
|
||||
def __call__(self, data, **metadata):
|
||||
|
@ -437,7 +437,7 @@ class ViewImage(View):
|
|||
parts.append(
|
||||
(str(tag), str(ex[i]))
|
||||
)
|
||||
fmt = format_dict(odict.ODict(parts))
|
||||
fmt = format_dict(multidict.MultiDict(parts))
|
||||
return "%s image" % img.format, fmt
|
||||
|
||||
|
||||
|
|
|
@ -73,10 +73,11 @@ class Response(message.Message):
|
|||
def cookies(self):
|
||||
# type: () -> multidict.MultiDictView
|
||||
"""
|
||||
The response cookies. A possibly empty :py:class:`~netlib.multidict.MultiDictView`, where the keys are
|
||||
cookie name strings, and values are (value, attr) tuples. Value is a string, and attr is
|
||||
an ODictCaseless containing cookie attributes. Within attrs, unary attributes (e.g. HTTPOnly)
|
||||
are indicated by a Null value.
|
||||
The response cookies. A possibly empty
|
||||
:py:class:`~netlib.multidict.MultiDictView`, where the keys are cookie
|
||||
name strings, and values are (value, attr) tuples. Value is a string,
|
||||
and attr is an MultiDictView containing cookie attributes. Within
|
||||
attrs, unary attributes (e.g. HTTPOnly) are indicated by a Null value.
|
||||
|
||||
Caveats:
|
||||
Updating the attr
|
||||
|
|
|
@ -235,6 +235,14 @@ class MultiDict(_MultiDict):
|
|||
tuple(i) for i in fields
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _reduce_values(values):
|
||||
return values[0]
|
||||
|
||||
@staticmethod
|
||||
def _kconv(key):
|
||||
return key
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class ImmutableMultiDict(MultiDict):
|
||||
|
|
170
netlib/odict.py
170
netlib/odict.py
|
@ -1,170 +0,0 @@
|
|||
from __future__ import (absolute_import, print_function, division)
|
||||
import copy
|
||||
|
||||
import six
|
||||
|
||||
from netlib import basetypes, strutils
|
||||
|
||||
|
||||
class ODict(basetypes.Serializable):
|
||||
|
||||
"""
|
||||
A dictionary-like object for managing ordered (key, value) data. Think
|
||||
about it as a convenient interface to a list of (key, value) tuples.
|
||||
"""
|
||||
|
||||
def __init__(self, lst=None):
|
||||
self.lst = lst or []
|
||||
|
||||
def _kconv(self, s):
|
||||
return s
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.lst == other.lst
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __iter__(self):
|
||||
return self.lst.__iter__()
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
Returns a list of values matching key.
|
||||
"""
|
||||
|
||||
key = self._kconv(key)
|
||||
return [
|
||||
v
|
||||
for k, v in self.lst
|
||||
if self._kconv(k) == key
|
||||
]
|
||||
|
||||
def keys(self):
|
||||
return list(
|
||||
set(
|
||||
self._kconv(k) for k, _ in self.lst
|
||||
)
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Total number of (key, value) pairs.
|
||||
"""
|
||||
return len(self.lst)
|
||||
|
||||
def __setitem__(self, k, valuelist):
|
||||
"""
|
||||
Sets the values for key k. If there are existing values for this
|
||||
key, they are cleared.
|
||||
"""
|
||||
if isinstance(valuelist, six.text_type) or isinstance(valuelist, six.binary_type):
|
||||
raise ValueError(
|
||||
"Expected list of values instead of string. "
|
||||
"Example: odict[b'Host'] = [b'www.example.com']"
|
||||
)
|
||||
kc = self._kconv(k)
|
||||
new = []
|
||||
for i in self.lst:
|
||||
if self._kconv(i[0]) == kc:
|
||||
if valuelist:
|
||||
new.append([k, valuelist.pop(0)])
|
||||
else:
|
||||
new.append(i)
|
||||
while valuelist:
|
||||
new.append([k, valuelist.pop(0)])
|
||||
self.lst = new
|
||||
|
||||
def __delitem__(self, k):
|
||||
"""
|
||||
Delete all items matching k.
|
||||
"""
|
||||
k = self._kconv(k)
|
||||
self.lst = [
|
||||
i
|
||||
for i in self.lst
|
||||
if self._kconv(i[0]) != k
|
||||
]
|
||||
|
||||
def __contains__(self, key):
|
||||
key = self._kconv(key)
|
||||
return any(
|
||||
self._kconv(k) == key
|
||||
for k, _ in self.lst
|
||||
)
|
||||
|
||||
def add(self, key, value, prepend=False):
|
||||
if prepend:
|
||||
self.lst.insert(0, [key, value])
|
||||
else:
|
||||
self.lst.append([key, value])
|
||||
|
||||
def get(self, k, d=None):
|
||||
if k in self:
|
||||
return self[k]
|
||||
else:
|
||||
return d
|
||||
|
||||
def get_first(self, k, d=None):
|
||||
if k in self:
|
||||
return self[k][0]
|
||||
else:
|
||||
return d
|
||||
|
||||
def items(self):
|
||||
return self.lst[:]
|
||||
|
||||
def copy(self):
|
||||
"""
|
||||
Returns a copy of this object.
|
||||
"""
|
||||
lst = copy.deepcopy(self.lst)
|
||||
return self.__class__(lst)
|
||||
|
||||
def extend(self, other):
|
||||
"""
|
||||
Add the contents of other, preserving any duplicates.
|
||||
"""
|
||||
self.lst.extend(other.lst)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.lst)
|
||||
|
||||
def replace(self, pattern, repl, *args, **kwargs):
|
||||
"""
|
||||
Replaces a regular expression pattern with repl in both keys and
|
||||
values.
|
||||
|
||||
Returns the number of replacements made.
|
||||
"""
|
||||
new, count = [], 0
|
||||
for k, v in self.lst:
|
||||
k, c = strutils.safe_subn(pattern, repl, k, *args, **kwargs)
|
||||
count += c
|
||||
v, c = strutils.safe_subn(pattern, repl, v, *args, **kwargs)
|
||||
count += c
|
||||
new.append([k, v])
|
||||
self.lst = new
|
||||
return count
|
||||
|
||||
# Implement Serializable
|
||||
def get_state(self):
|
||||
return [tuple(i) for i in self.lst]
|
||||
|
||||
def set_state(self, state):
|
||||
self.lst = [list(i) for i in state]
|
||||
|
||||
@classmethod
|
||||
def from_state(cls, state):
|
||||
return cls([list(i) for i in state])
|
||||
|
||||
|
||||
class ODictCaseless(ODict):
|
||||
|
||||
"""
|
||||
A variant of ODict with "caseless" keys. This version _preserves_ key
|
||||
case, but does not consider case when setting or getting items.
|
||||
"""
|
||||
|
||||
def _kconv(self, s):
|
||||
return s.lower()
|
|
@ -1,8 +1,8 @@
|
|||
from mitmproxy.exceptions import ContentViewException
|
||||
from netlib.http import Headers
|
||||
from netlib.odict import ODict
|
||||
from netlib import encoding
|
||||
from netlib.http import url
|
||||
from netlib import multidict
|
||||
|
||||
import mitmproxy.contentviews as cv
|
||||
from . import tutils
|
||||
|
@ -55,7 +55,7 @@ class TestContentView:
|
|||
f = v(
|
||||
"",
|
||||
headers=Headers(),
|
||||
query=ODict([("foo", "bar")]),
|
||||
query=multidict.MultiDict([("foo", "bar")]),
|
||||
)
|
||||
assert f[0] == "Query"
|
||||
|
||||
|
@ -175,7 +175,7 @@ Larry
|
|||
def test_view_query(self):
|
||||
d = ""
|
||||
v = cv.ViewQuery()
|
||||
f = v(d, query=ODict([("foo", "bar")]))
|
||||
f = v(d, query=multidict.MultiDict([("foo", "bar")]))
|
||||
assert f[0] == "Query"
|
||||
assert [x for x in f[1]] == [[("header", "foo: "), ("text", "bar")]]
|
||||
|
||||
|
|
|
@ -3,10 +3,6 @@ from netlib.multidict import MultiDict, ImmutableMultiDict, MultiDictView
|
|||
|
||||
|
||||
class _TMulti(object):
|
||||
@staticmethod
|
||||
def _reduce_values(values):
|
||||
return values[0]
|
||||
|
||||
@staticmethod
|
||||
def _kconv(key):
|
||||
return key.lower()
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
from netlib import odict, tutils
|
||||
|
||||
|
||||
class TestODict(object):
|
||||
|
||||
def test_repr(self):
|
||||
h = odict.ODict()
|
||||
h["one"] = ["two"]
|
||||
assert repr(h)
|
||||
|
||||
def test_str_err(self):
|
||||
h = odict.ODict()
|
||||
with tutils.raises(ValueError):
|
||||
h["key"] = u"foo"
|
||||
with tutils.raises(ValueError):
|
||||
h["key"] = b"foo"
|
||||
|
||||
def test_getset_state(self):
|
||||
od = odict.ODict()
|
||||
od.add("foo", 1)
|
||||
od.add("foo", 2)
|
||||
od.add("bar", 3)
|
||||
state = od.get_state()
|
||||
nd = odict.ODict.from_state(state)
|
||||
assert nd == od
|
||||
b = odict.ODict()
|
||||
b.set_state(state)
|
||||
assert b == od
|
||||
|
||||
def test_iter(self):
|
||||
od = odict.ODict()
|
||||
assert not [i for i in od]
|
||||
od.add("foo", 1)
|
||||
assert [i for i in od]
|
||||
|
||||
def test_keys(self):
|
||||
od = odict.ODict()
|
||||
assert not od.keys()
|
||||
od.add("foo", 1)
|
||||
assert od.keys() == ["foo"]
|
||||
od.add("foo", 2)
|
||||
assert od.keys() == ["foo"]
|
||||
od.add("bar", 2)
|
||||
assert len(od.keys()) == 2
|
||||
|
||||
def test_copy(self):
|
||||
od = odict.ODict()
|
||||
od.add("foo", 1)
|
||||
od.add("foo", 2)
|
||||
od.add("bar", 3)
|
||||
assert od == od.copy()
|
||||
assert not od != od.copy()
|
||||
|
||||
def test_del(self):
|
||||
od = odict.ODict()
|
||||
od.add("foo", 1)
|
||||
od.add("Foo", 2)
|
||||
od.add("bar", 3)
|
||||
del od["foo"]
|
||||
assert len(od.lst) == 2
|
||||
|
||||
def test_replace(self):
|
||||
od = odict.ODict()
|
||||
od.add("one", "two")
|
||||
od.add("two", "one")
|
||||
assert od.replace("one", "vun") == 2
|
||||
assert od.lst == [
|
||||
["vun", "two"],
|
||||
["two", "vun"],
|
||||
]
|
||||
|
||||
def test_get(self):
|
||||
od = odict.ODict()
|
||||
od.add("one", "two")
|
||||
assert od.get("one") == ["two"]
|
||||
assert od.get("two") is None
|
||||
|
||||
def test_get_first(self):
|
||||
od = odict.ODict()
|
||||
od.add("one", "two")
|
||||
od.add("one", "three")
|
||||
assert od.get_first("one") == "two"
|
||||
assert od.get_first("two") is None
|
||||
|
||||
def test_extend(self):
|
||||
a = odict.ODict([["a", "b"], ["c", "d"]])
|
||||
b = odict.ODict([["a", "b"], ["e", "f"]])
|
||||
a.extend(b)
|
||||
assert len(a) == 4
|
||||
assert a["a"] == ["b", "b"]
|
||||
|
||||
|
||||
class TestODictCaseless(object):
|
||||
|
||||
def test_override(self):
|
||||
o = odict.ODictCaseless()
|
||||
o.add('T', 'application/x-www-form-urlencoded; charset=UTF-8')
|
||||
o["T"] = ["foo"]
|
||||
assert o["T"] == ["foo"]
|
||||
|
||||
def test_case_preservation(self):
|
||||
od = odict.ODictCaseless()
|
||||
od["Foo"] = ["1"]
|
||||
assert "foo" in od
|
||||
assert od.items()[0][0] == "Foo"
|
||||
assert od.get("foo") == ["1"]
|
||||
assert od.get("foo", [""]) == ["1"]
|
||||
assert od.get("Foo", [""]) == ["1"]
|
||||
assert od.get("xx", "yy") == "yy"
|
||||
|
||||
def test_del(self):
|
||||
od = odict.ODictCaseless()
|
||||
od.add("foo", 1)
|
||||
od.add("Foo", 2)
|
||||
od.add("bar", 3)
|
||||
del od["foo"]
|
||||
assert len(od) == 1
|
||||
|
||||
def test_keys(self):
|
||||
od = odict.ODictCaseless()
|
||||
assert not od.keys()
|
||||
od.add("foo", 1)
|
||||
assert od.keys() == ["foo"]
|
||||
od.add("Foo", 2)
|
||||
assert od.keys() == ["foo"]
|
||||
od.add("bar", 2)
|
||||
assert len(od.keys()) == 2
|
||||
|
||||
def test_add_order(self):
|
||||
od = odict.ODict(
|
||||
[
|
||||
["one", "uno"],
|
||||
["two", "due"],
|
||||
["three", "tre"],
|
||||
]
|
||||
)
|
||||
od["two"] = ["foo", "bar"]
|
||||
assert od.lst == [
|
||||
["one", "uno"],
|
||||
["two", "foo"],
|
||||
["three", "tre"],
|
||||
["two", "bar"],
|
||||
]
|
Loading…
Reference in New Issue