diff --git a/mitmproxy/console/flowview.py b/mitmproxy/console/flowview.py index b761a9244..b2ebe49eb 100644 --- a/mitmproxy/console/flowview.py +++ b/mitmproxy/console/flowview.py @@ -364,12 +364,11 @@ class FlowView(tabs.Tabs): self.edit_form(conn) def set_cookies(self, lst, conn): - od = odict.ODict(lst) - conn.set_cookies(od) + conn.cookies = odict.ODict(lst) signals.flow_change.send(self, flow = self.flow) def set_setcookies(self, data, conn): - conn.set_cookies(data) + conn.cookies = data signals.flow_change.send(self, flow = self.flow) def edit(self, part): @@ -389,7 +388,7 @@ class FlowView(tabs.Tabs): self.master.view_grideditor( grideditor.CookieEditor( self.master, - message.get_cookies().lst, + message.cookies.lst, self.set_cookies, message ) @@ -398,7 +397,7 @@ class FlowView(tabs.Tabs): self.master.view_grideditor( grideditor.SetCookieEditor( self.master, - message.get_cookies(), + message.cookies, self.set_setcookies, message ) diff --git a/mitmproxy/flow.py b/mitmproxy/flow.py index c63c1efa6..7fd97af30 100644 --- a/mitmproxy/flow.py +++ b/mitmproxy/flow.py @@ -13,9 +13,9 @@ from six.moves import http_cookies, http_cookiejar, urllib import os import re -from netlib import wsgi +from netlib import wsgi, odict from netlib.exceptions import HttpException -from netlib.http import Headers, http1 +from netlib.http import Headers, http1, cookies from . import controller, tnetstring, filt, script, version, flow_format_compat from .onboarding import app from .proxy.config import HostMatcher @@ -313,15 +313,17 @@ class StickyCookieState: self.jar = defaultdict(dict) self.flt = flt - def ckey(self, m, f): + def ckey(self, attrs, f): """ Returns a (domain, port, path) tuple. """ - return ( - m["domain"] or f.request.host, - f.request.port, - m["path"] or "/" - ) + domain = f.request.host + path = "/" + if attrs["domain"]: + domain = attrs["domain"][-1] + if attrs["path"]: + path = attrs["path"][-1] + return (domain, f.request.port, path) def domain_match(self, a, b): if http_cookiejar.domain_match(a, b): @@ -334,11 +336,12 @@ class StickyCookieState: for i in f.response.headers.get_all("set-cookie"): # FIXME: We now know that Cookie.py screws up some cookies with # valid RFC 822/1123 datetime specifications for expiry. Sigh. - c = http_cookies.SimpleCookie(str(i)) - for m in c.values(): - k = self.ckey(m, f) - if self.domain_match(f.request.host, k[0]): - self.jar[k][m.key] = m + name, value, attrs = cookies.parse_set_cookie_header(str(i)) + a = self.ckey(attrs, f) + if self.domain_match(f.request.host, a[0]): + b = attrs.lst + b.insert(0, [name, value]) + self.jar[a][name] = odict.ODictCaseless(b) def handle_request(self, f): l = [] @@ -350,7 +353,8 @@ class StickyCookieState: f.request.path.startswith(i[2]) ] if all(match): - l.extend([m.output(header="").strip() for m in self.jar[i].values()]) + c = self.jar[i] + l.extend([cookies.format_cookie_header(c[name]) for name in c.keys()]) if l: f.request.stickycookie = True f.request.headers["cookie"] = "; ".join(l) diff --git a/netlib/http/cookies.py b/netlib/http/cookies.py index caa84ff7d..4451f1daf 100644 --- a/netlib/http/cookies.py +++ b/netlib/http/cookies.py @@ -1,5 +1,6 @@ from six.moves import http_cookies as Cookie import re +import string from email.utils import parsedate_tz, formatdate, mktime_tz from .. import odict @@ -27,7 +28,6 @@ variants. Serialization follows RFC6265. # TODO: Disallow LHS-only Cookie values - def _read_until(s, start, term): """ Read until one of the characters in term is reached. @@ -203,25 +203,26 @@ def refresh_set_cookie_header(c, delta): Returns: A refreshed Set-Cookie string """ - try: - c = Cookie.SimpleCookie(str(c)) - except Cookie.CookieError: + + name, value, attrs = parse_set_cookie_header(c) + if not name or not value: raise ValueError("Invalid Cookie") - for i in c.values(): - if "expires" in i: - d = parsedate_tz(i["expires"]) - if d: - d = mktime_tz(d) + delta - i["expires"] = formatdate(d) - else: - # This can happen when the expires tag is invalid. - # reddit.com sends a an expires tag like this: "Thu, 31 Dec - # 2037 23:59:59 GMT", which is valid RFC 1123, but not - # strictly correct according to the cookie spec. Browsers - # appear to parse this tolerantly - maybe we should too. - # For now, we just ignore this. - del i["expires"] - ret = c.output(header="").strip() + + if "expires" in attrs: + e = parsedate_tz(attrs["expires"][-1]) + if e: + f = mktime_tz(e) + delta + attrs["expires"] = [formatdate(f)] + else: + # This can happen when the expires tag is invalid. + # reddit.com sends a an expires tag like this: "Thu, 31 Dec + # 2037 23:59:59 GMT", which is valid RFC 1123, but not + # strictly correct according to the cookie spec. Browsers + # appear to parse this tolerantly - maybe we should too. + # For now, we just ignore this. + del attrs["expires"] + + ret = format_set_cookie_header(name, value, attrs) if not ret: raise ValueError("Invalid Cookie") return ret diff --git a/netlib/http/request.py b/netlib/http/request.py index 67aa17cee..a42150ff9 100644 --- a/netlib/http/request.py +++ b/netlib/http/request.py @@ -343,14 +343,6 @@ class Request(Message): # Legacy - def get_cookies(self): # pragma: no cover - warnings.warn(".get_cookies is deprecated, use .cookies instead.", DeprecationWarning) - return self.cookies - - def set_cookies(self, odict): # pragma: no cover - warnings.warn(".set_cookies is deprecated, use .cookies instead.", DeprecationWarning) - self.cookies = odict - def get_query(self): # pragma: no cover warnings.warn(".get_query is deprecated, use .query instead.", DeprecationWarning) return self.query or ODict([]) diff --git a/netlib/http/response.py b/netlib/http/response.py index efd7f60aa..2f06149e3 100644 --- a/netlib/http/response.py +++ b/netlib/http/response.py @@ -127,13 +127,3 @@ class Response(Message): c.append(refreshed) if c: self.headers.set_all("set-cookie", c) - - # Legacy - - def get_cookies(self): # pragma: no cover - warnings.warn(".get_cookies is deprecated, use .cookies instead.", DeprecationWarning) - return self.cookies - - def set_cookies(self, odict): # pragma: no cover - warnings.warn(".set_cookies is deprecated, use .cookies instead.", DeprecationWarning) - self.cookies = odict diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index 84a25689c..b9c6a2f64 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -76,6 +76,21 @@ class TestStickyCookieState: googlekey = s.jar.keys()[0] assert len(s.jar[googlekey].keys()) == 2 + # Test setting of weird cookie keys + s = flow.StickyCookieState(filt.parse(".*")) + f = tutils.tflow(req=netlib.tutils.treq(host="www.google.com", port=80), resp=True) + cs = [ + "foo/bar=hello", + "foo:bar=world", + "foo@bar=fizz", + "foo,bar=buzz", + ] + for c in cs: + f.response.headers["Set-Cookie"] = c + s.handle_response(f) + googlekey = s.jar.keys()[0] + assert len(s.jar[googlekey].keys()) == len(cs) + # Test overwriting of a cookie value c1 = "somecookie=helloworld; Path=/" c2 = "somecookie=newvalue; Path=/" @@ -84,7 +99,7 @@ class TestStickyCookieState: s.handle_response(f) googlekey = s.jar.keys()[0] assert len(s.jar[googlekey].keys()) == 1 - assert s.jar[googlekey]["somecookie"].value == "newvalue" + assert s.jar[googlekey]["somecookie"].items()[0][1] == "newvalue" def test_handle_request(self): s, f = self._response("SSID=mooo", "www.google.com") diff --git a/test/netlib/http/test_cookies.py b/test/netlib/http/test_cookies.py index 3b520a445..da28850f2 100644 --- a/test/netlib/http/test_cookies.py +++ b/test/netlib/http/test_cookies.py @@ -228,7 +228,16 @@ def test_refresh_cookie(): c = "MOO=BAR; Expires=Tue, 08-Mar-2011 00:20:38 GMT; Path=foo.com; Secure" assert "00:21:38" in cookies.refresh_set_cookie_header(c, 60) + c = "foo,bar" + with raises(ValueError): + cookies.refresh_set_cookie_header(c, 60) + # https://github.com/mitmproxy/mitmproxy/issues/773 c = ">=A" - with raises(ValueError): - cookies.refresh_set_cookie_header(c, 60) \ No newline at end of file + assert cookies.refresh_set_cookie_header(c, 60) + + # https://github.com/mitmproxy/mitmproxy/issues/1118 + c = "foo:bar=bla" + assert cookies.refresh_set_cookie_header(c, 0) + c = "foo/bar=bla" + assert cookies.refresh_set_cookie_header(c, 0) diff --git a/test/netlib/http/test_request.py b/test/netlib/http/test_request.py index d57dca13e..7ed6bd0f0 100644 --- a/test/netlib/http/test_request.py +++ b/test/netlib/http/test_request.py @@ -172,7 +172,7 @@ class TestRequestUtils(object): def test_get_cookies_none(self): request = treq() request.headers = Headers() - assert len(request.cookies) == 0 + assert not request.cookies def test_get_cookies_single(self): request = treq() diff --git a/test/netlib/http/test_response.py b/test/netlib/http/test_response.py index a0c44d904..5440176c3 100644 --- a/test/netlib/http/test_response.py +++ b/test/netlib/http/test_response.py @@ -98,7 +98,7 @@ class TestResponseUtils(object): resp = tresp() v = resp.cookies v.add("foo", ["bar", ODictCaseless()]) - resp.set_cookies(v) + resp.cookies = v v = resp.cookies assert len(v) == 1