prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Sticky cookies are improperly formatted.
  ##### Steps to reproduce the problem:
  
  1. Go to http://www.html-kit.com/tools/cookietester/
  2. Click 'Set Test Cookie'
  3. Observe that one cookie is sent to the server.
  4. Remove the cookie.
  5. launch mitmproxy with `mitmproxy -t html-kit\.com` and tell your browser to use it as a proxy
  6. Reload the page.
  7. Click 'Set Test Cookie'
  8. Observe that two 'cookies' are sent to the server.
  
  ##### Any other comments? What have you tried so far?
  There appears to be a comma in the output of mitmproxy, even though it is surrounded by quotes. It's possible, then that this is a parsing fail on the tool's end caused by a difference in what's sent back for the format of the date. Still, should it really be changing that?
  
  ##### System information
  Arch Linux, freshly updated.
  
  Mitmproxy version: 2.0.1 (release version) 
  Python version: 3.6.0
  Platform: Linux-4.10.6-1-ARCH-x86_64-with-glibc2.3.4
  SSL version: OpenSSL 1.0.2k  26 Jan 2017
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `mitmproxy/addons/stickycookie.py`
  Content:
  ```
  1 import collections
  2 from http import cookiejar
  3 
  4 from mitmproxy.net.http import cookies
  5 
  6 from mitmproxy import exceptions
  7 from mitmproxy import flowfilter
  8 from mitmproxy import ctx
  9 
  10 
  11 def ckey(attrs, f):
  12     """
  13         Returns a (domain, port, path) tuple.
  14     """
  15     domain = f.request.host
  16     path = "/"
  17     if "domain" in attrs:
  18         domain = attrs["domain"]
  19     if "path" in attrs:
  20         path = attrs["path"]
  21     return (domain, f.request.port, path)
  22 
  23 
  24 def domain_match(a, b):
  25     if cookiejar.domain_match(a, b):
  26         return True
  27     elif cookiejar.domain_match(a, b.strip(".")):
  28         return True
  29     return False
  30 
  31 
  32 class StickyCookie:
  33     def __init__(self):
  34         self.jar = collections.defaultdict(dict)
  35         self.flt = None
  36 
  37     def configure(self, updated):
  38         if "stickycookie" in updated:
  39             if ctx.options.stickycookie:
  40                 flt = flowfilter.parse(ctx.options.stickycookie)
  41                 if not flt:
  42                     raise exceptions.OptionsError(
  43                         "stickycookie: invalid filter expression: %s" % ctx.options.stickycookie
  44                     )
  45                 self.flt = flt
  46             else:
  47                 self.flt = None
  48 
  49     def response(self, flow):
  50         if self.flt:
  51             for name, (value, attrs) in flow.response.cookies.items(multi=True):
  52                 # FIXME: We now know that Cookie.py screws up some cookies with
  53                 # valid RFC 822/1123 datetime specifications for expiry. Sigh.
  54                 dom_port_path = ckey(attrs, flow)
  55 
  56                 if domain_match(flow.request.host, dom_port_path[0]):
  57                     if cookies.is_expired(attrs):
  58                         # Remove the cookie from jar
  59                         self.jar[dom_port_path].pop(name, None)
  60 
  61                         # If all cookies of a dom_port_path have been removed
  62                         # then remove it from the jar itself
  63                         if not self.jar[dom_port_path]:
  64                             self.jar.pop(dom_port_path, None)
  65                     else:
  66                         b = attrs.copy()
  67                         b.insert(0, name, value)
  68                         self.jar[dom_port_path][name] = b
  69 
  70     def request(self, flow):
  71         if self.flt:
  72             l = []
  73             if flowfilter.match(self.flt, flow):
  74                 for domain, port, path in self.jar.keys():
  75                     match = [
  76                         domain_match(flow.request.host, domain),
  77                         flow.request.port == port,
  78                         flow.request.path.startswith(path)
  79                     ]
  80                     if all(match):
  81                         c = self.jar[(domain, port, path)]
  82                         l.extend([cookies.format_cookie_header(c[name].items(multi=True)) for name in c.keys()])
  83             if l:
  84                 # FIXME: we need to formalise this...
  85                 flow.request.stickycookie = True
  86                 flow.request.headers["cookie"] = "; ".join(l)
  87
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/mitmproxy/addons/stickycookie.py b/mitmproxy/addons/stickycookie.py
  --- a/mitmproxy/addons/stickycookie.py
  +++ b/mitmproxy/addons/stickycookie.py
  @@ -1,14 +1,14 @@
   import collections
   from http import cookiejar
  +from typing import List, Tuple, Dict, Optional  # noqa
   
  +from mitmproxy import http, flowfilter, ctx, exceptions
   from mitmproxy.net.http import cookies
   
  -from mitmproxy import exceptions
  -from mitmproxy import flowfilter
  -from mitmproxy import ctx
  +TOrigin = Tuple[str, int, str]
   
   
  -def ckey(attrs, f):
  +def ckey(attrs: Dict[str, str], f: http.HTTPFlow) -> TOrigin:
       """
           Returns a (domain, port, path) tuple.
       """
  @@ -21,18 +21,18 @@
       return (domain, f.request.port, path)
   
   
  -def domain_match(a, b):
  -    if cookiejar.domain_match(a, b):
  +def domain_match(a: str, b: str) -> bool:
  +    if cookiejar.domain_match(a, b):  # type: ignore
           return True
  -    elif cookiejar.domain_match(a, b.strip(".")):
  +    elif cookiejar.domain_match(a, b.strip(".")):  # type: ignore
           return True
       return False
   
   
   class StickyCookie:
       def __init__(self):
  -        self.jar = collections.defaultdict(dict)
  -        self.flt = None
  +        self.jar = collections.defaultdict(dict)  # type: Dict[TOrigin, Dict[str, str]]
  +        self.flt = None  # type: Optional[flowfilter.TFilter]
   
       def configure(self, updated):
           if "stickycookie" in updated:
  @@ -46,7 +46,7 @@
               else:
                   self.flt = None
   
  -    def response(self, flow):
  +    def response(self, flow: http.HTTPFlow):
           if self.flt:
               for name, (value, attrs) in flow.response.cookies.items(multi=True):
                   # FIXME: We now know that Cookie.py screws up some cookies with
  @@ -63,24 +63,21 @@
                           if not self.jar[dom_port_path]:
                               self.jar.pop(dom_port_path, None)
                       else:
  -                        b = attrs.copy()
  -                        b.insert(0, name, value)
  -                        self.jar[dom_port_path][name] = b
  +                        self.jar[dom_port_path][name] = value
   
  -    def request(self, flow):
  +    def request(self, flow: http.HTTPFlow):
           if self.flt:
  -            l = []
  +            cookie_list = []  # type: List[Tuple[str,str]]
               if flowfilter.match(self.flt, flow):
  -                for domain, port, path in self.jar.keys():
  +                for (domain, port, path), c in self.jar.items():
                       match = [
                           domain_match(flow.request.host, domain),
                           flow.request.port == port,
                           flow.request.path.startswith(path)
                       ]
                       if all(match):
  -                        c = self.jar[(domain, port, path)]
  -                        l.extend([cookies.format_cookie_header(c[name].items(multi=True)) for name in c.keys()])
  -            if l:
  +                        cookie_list.extend(c.items())
  +            if cookie_list:
                   # FIXME: we need to formalise this...
  -                flow.request.stickycookie = True
  -                flow.request.headers["cookie"] = "; ".join(l)
  +                flow.metadata["stickycookie"] = True
  +                flow.request.headers["cookie"] = cookies.format_cookie_header(cookie_list)
  
verification_info: {"golden_diff": "diff --git a/mitmproxy/addons/stickycookie.py b/mitmproxy/addons/stickycookie.py\n--- a/mitmproxy/addons/stickycookie.py\n+++ b/mitmproxy/addons/stickycookie.py\n@@ -1,14 +1,14 @@\n import collections\n from http import cookiejar\n+from typing import List, Tuple, Dict, Optional  # noqa\n \n+from mitmproxy import http, flowfilter, ctx, exceptions\n from mitmproxy.net.http import cookies\n \n-from mitmproxy import exceptions\n-from mitmproxy import flowfilter\n-from mitmproxy import ctx\n+TOrigin = Tuple[str, int, str]\n \n \n-def ckey(attrs, f):\n+def ckey(attrs: Dict[str, str], f: http.HTTPFlow) -> TOrigin:\n     \"\"\"\n         Returns a (domain, port, path) tuple.\n     \"\"\"\n@@ -21,18 +21,18 @@\n     return (domain, f.request.port, path)\n \n \n-def domain_match(a, b):\n-    if cookiejar.domain_match(a, b):\n+def domain_match(a: str, b: str) -> bool:\n+    if cookiejar.domain_match(a, b):  # type: ignore\n         return True\n-    elif cookiejar.domain_match(a, b.strip(\".\")):\n+    elif cookiejar.domain_match(a, b.strip(\".\")):  # type: ignore\n         return True\n     return False\n \n \n class StickyCookie:\n     def __init__(self):\n-        self.jar = collections.defaultdict(dict)\n-        self.flt = None\n+        self.jar = collections.defaultdict(dict)  # type: Dict[TOrigin, Dict[str, str]]\n+        self.flt = None  # type: Optional[flowfilter.TFilter]\n \n     def configure(self, updated):\n         if \"stickycookie\" in updated:\n@@ -46,7 +46,7 @@\n             else:\n                 self.flt = None\n \n-    def response(self, flow):\n+    def response(self, flow: http.HTTPFlow):\n         if self.flt:\n             for name, (value, attrs) in flow.response.cookies.items(multi=True):\n                 # FIXME: We now know that Cookie.py screws up some cookies with\n@@ -63,24 +63,21 @@\n                         if not self.jar[dom_port_path]:\n                             self.jar.pop(dom_port_path, None)\n                     else:\n-                        b = attrs.copy()\n-                        b.insert(0, name, value)\n-                        self.jar[dom_port_path][name] = b\n+                        self.jar[dom_port_path][name] = value\n \n-    def request(self, flow):\n+    def request(self, flow: http.HTTPFlow):\n         if self.flt:\n-            l = []\n+            cookie_list = []  # type: List[Tuple[str,str]]\n             if flowfilter.match(self.flt, flow):\n-                for domain, port, path in self.jar.keys():\n+                for (domain, port, path), c in self.jar.items():\n                     match = [\n                         domain_match(flow.request.host, domain),\n                         flow.request.port == port,\n                         flow.request.path.startswith(path)\n                     ]\n                     if all(match):\n-                        c = self.jar[(domain, port, path)]\n-                        l.extend([cookies.format_cookie_header(c[name].items(multi=True)) for name in c.keys()])\n-            if l:\n+                        cookie_list.extend(c.items())\n+            if cookie_list:\n                 # FIXME: we need to formalise this...\n-                flow.request.stickycookie = True\n-                flow.request.headers[\"cookie\"] = \"; \".join(l)\n+                flow.metadata[\"stickycookie\"] = True\n+                flow.request.headers[\"cookie\"] = cookies.format_cookie_header(cookie_list)\n", "issue": "Sticky cookies are improperly formatted.\n##### Steps to reproduce the problem:\r\n\r\n1. Go to http://www.html-kit.com/tools/cookietester/\r\n2. Click 'Set Test Cookie'\r\n3. Observe that one cookie is sent to the server.\r\n4. Remove the cookie.\r\n5. launch mitmproxy with `mitmproxy -t html-kit\\.com` and tell your browser to use it as a proxy\r\n6. Reload the page.\r\n7. Click 'Set Test Cookie'\r\n8. Observe that two 'cookies' are sent to the server.\r\n\r\n##### Any other comments? What have you tried so far?\r\nThere appears to be a comma in the output of mitmproxy, even though it is surrounded by quotes. It's possible, then that this is a parsing fail on the tool's end caused by a difference in what's sent back for the format of the date. Still, should it really be changing that?\r\n\r\n##### System information\r\nArch Linux, freshly updated.\r\n\r\nMitmproxy version: 2.0.1 (release version) \r\nPython version: 3.6.0\r\nPlatform: Linux-4.10.6-1-ARCH-x86_64-with-glibc2.3.4\r\nSSL version: OpenSSL 1.0.2k  26 Jan 2017\r\n\n", "before_files": [{"content": "import collections\nfrom http import cookiejar\n\nfrom mitmproxy.net.http import cookies\n\nfrom mitmproxy import exceptions\nfrom mitmproxy import flowfilter\nfrom mitmproxy import ctx\n\n\ndef ckey(attrs, f):\n    \"\"\"\n        Returns a (domain, port, path) tuple.\n    \"\"\"\n    domain = f.request.host\n    path = \"/\"\n    if \"domain\" in attrs:\n        domain = attrs[\"domain\"]\n    if \"path\" in attrs:\n        path = attrs[\"path\"]\n    return (domain, f.request.port, path)\n\n\ndef domain_match(a, b):\n    if cookiejar.domain_match(a, b):\n        return True\n    elif cookiejar.domain_match(a, b.strip(\".\")):\n        return True\n    return False\n\n\nclass StickyCookie:\n    def __init__(self):\n        self.jar = collections.defaultdict(dict)\n        self.flt = None\n\n    def configure(self, updated):\n        if \"stickycookie\" in updated:\n            if ctx.options.stickycookie:\n                flt = flowfilter.parse(ctx.options.stickycookie)\n                if not flt:\n                    raise exceptions.OptionsError(\n                        \"stickycookie: invalid filter expression: %s\" % ctx.options.stickycookie\n                    )\n                self.flt = flt\n            else:\n                self.flt = None\n\n    def response(self, flow):\n        if self.flt:\n            for name, (value, attrs) in flow.response.cookies.items(multi=True):\n                # FIXME: We now know that Cookie.py screws up some cookies with\n                # valid RFC 822/1123 datetime specifications for expiry. Sigh.\n                dom_port_path = ckey(attrs, flow)\n\n                if domain_match(flow.request.host, dom_port_path[0]):\n                    if cookies.is_expired(attrs):\n                        # Remove the cookie from jar\n                        self.jar[dom_port_path].pop(name, None)\n\n                        # If all cookies of a dom_port_path have been removed\n                        # then remove it from the jar itself\n                        if not self.jar[dom_port_path]:\n                            self.jar.pop(dom_port_path, None)\n                    else:\n                        b = attrs.copy()\n                        b.insert(0, name, value)\n                        self.jar[dom_port_path][name] = b\n\n    def request(self, flow):\n        if self.flt:\n            l = []\n            if flowfilter.match(self.flt, flow):\n                for domain, port, path in self.jar.keys():\n                    match = [\n                        domain_match(flow.request.host, domain),\n                        flow.request.port == port,\n                        flow.request.path.startswith(path)\n                    ]\n                    if all(match):\n                        c = self.jar[(domain, port, path)]\n                        l.extend([cookies.format_cookie_header(c[name].items(multi=True)) for name in c.keys()])\n            if l:\n                # FIXME: we need to formalise this...\n                flow.request.stickycookie = True\n                flow.request.headers[\"cookie\"] = \"; \".join(l)\n", "path": "mitmproxy/addons/stickycookie.py"}], "after_files": [{"content": "import collections\nfrom http import cookiejar\nfrom typing import List, Tuple, Dict, Optional  # noqa\n\nfrom mitmproxy import http, flowfilter, ctx, exceptions\nfrom mitmproxy.net.http import cookies\n\nTOrigin = Tuple[str, int, str]\n\n\ndef ckey(attrs: Dict[str, str], f: http.HTTPFlow) -> TOrigin:\n    \"\"\"\n        Returns a (domain, port, path) tuple.\n    \"\"\"\n    domain = f.request.host\n    path = \"/\"\n    if \"domain\" in attrs:\n        domain = attrs[\"domain\"]\n    if \"path\" in attrs:\n        path = attrs[\"path\"]\n    return (domain, f.request.port, path)\n\n\ndef domain_match(a: str, b: str) -> bool:\n    if cookiejar.domain_match(a, b):  # type: ignore\n        return True\n    elif cookiejar.domain_match(a, b.strip(\".\")):  # type: ignore\n        return True\n    return False\n\n\nclass StickyCookie:\n    def __init__(self):\n        self.jar = collections.defaultdict(dict)  # type: Dict[TOrigin, Dict[str, str]]\n        self.flt = None  # type: Optional[flowfilter.TFilter]\n\n    def configure(self, updated):\n        if \"stickycookie\" in updated:\n            if ctx.options.stickycookie:\n                flt = flowfilter.parse(ctx.options.stickycookie)\n                if not flt:\n                    raise exceptions.OptionsError(\n                        \"stickycookie: invalid filter expression: %s\" % ctx.options.stickycookie\n                    )\n                self.flt = flt\n            else:\n                self.flt = None\n\n    def response(self, flow: http.HTTPFlow):\n        if self.flt:\n            for name, (value, attrs) in flow.response.cookies.items(multi=True):\n                # FIXME: We now know that Cookie.py screws up some cookies with\n                # valid RFC 822/1123 datetime specifications for expiry. Sigh.\n                dom_port_path = ckey(attrs, flow)\n\n                if domain_match(flow.request.host, dom_port_path[0]):\n                    if cookies.is_expired(attrs):\n                        # Remove the cookie from jar\n                        self.jar[dom_port_path].pop(name, None)\n\n                        # If all cookies of a dom_port_path have been removed\n                        # then remove it from the jar itself\n                        if not self.jar[dom_port_path]:\n                            self.jar.pop(dom_port_path, None)\n                    else:\n                        self.jar[dom_port_path][name] = value\n\n    def request(self, flow: http.HTTPFlow):\n        if self.flt:\n            cookie_list = []  # type: List[Tuple[str,str]]\n            if flowfilter.match(self.flt, flow):\n                for (domain, port, path), c in self.jar.items():\n                    match = [\n                        domain_match(flow.request.host, domain),\n                        flow.request.port == port,\n                        flow.request.path.startswith(path)\n                    ]\n                    if all(match):\n                        cookie_list.extend(c.items())\n            if cookie_list:\n                # FIXME: we need to formalise this...\n                flow.metadata[\"stickycookie\"] = True\n                flow.request.headers[\"cookie\"] = cookies.format_cookie_header(cookie_list)\n", "path": "mitmproxy/addons/stickycookie.py"}]}