We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Mitmproxy crashes, when trying to send cuts to the clipboard without copy/paste mechanism for system
##### Steps to reproduce the problem:

1. Run mitmproxy.
2. Press `Ctrl l`
3. Input `: cut.clip 1 2 `. Press `Enter`.

Traceback:
```
Traceback (most recent call last):
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py", line 216, in run
    self.loop.run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 278, in run
    self._run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 376, in _run
    self.event_loop.run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 682, in run
    self._loop()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 719, in _loop
    self._watch_files[fd]()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 393, in <lambda>
    event_loop, callback, self.get_available_raw_input())
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 493, in parse_input
    callback(processed, processed_codes)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 403, in _update
    self.process_input(keys)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 309, in keypress
    k = super().keypress(size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1116, in keypress
    return self.footer.keypress((maxcol,),key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/statusbar.py", line 149, in keypress
    return self.ab.keypress(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/statusbar.py", line 104, in keypress
    self.prompt_execute(self._w.get_edit_text())
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/statusbar.py", line 124, in prompt_execute
    msg = p(txt)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/commandexecutor.py", line 17, in __call__
    ret = self.master.commands.call(cmd)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 221, in call
    return self.call_args(parts[0], parts[1:])
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 212, in call_args
    return self.commands[path].call(args)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 101, in call
    ret = self.func(*pargs)
  File "/usr/lib/python3.5/contextlib.py", line 77, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/master.py", line 68, in handlecontext
    yield
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 101, in call
    ret = self.func(*pargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 251, in wrapper
    return function(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/addons/cut.py", line 139, in clip
    pyperclip.copy(fp.getvalue())
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/pyperclip/__init__.py", line 574, in lazy_load_stub_copy
    return copy(text)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/pyperclip/__init__.py", line 284, in __call__
    raise PyperclipException(EXCEPT_MSG)
pyperclip.PyperclipException: 
    Pyperclip could not find a copy/paste mechanism for your system.
    For more information, please visit https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error 
```

##### Any other comments? What have you tried so far?

As traceback says, the issue is relevant for Linux without installed copy/paste mechanism and can be fixed using this https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error.
I think we must just handle it properly.


##### System information

Mitmproxy: 3.0.0.dev64 (commit 6dd336f) 
Python:    3.5.2
OpenSSL:   OpenSSL 1.1.0g  2 Nov 2017
Platform:  Linux-4.4.0-112-generic-x86_64-with-Ubuntu-16.04-xenial


--- 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/export.py`
Content:
```
1 import typing
2 
3 from mitmproxy import ctx
4 from mitmproxy import command
5 from mitmproxy import flow
6 from mitmproxy import exceptions
7 from mitmproxy.utils import strutils
8 from mitmproxy.net.http.http1 import assemble
9 import mitmproxy.types
10 
11 import pyperclip
12 
13 
14 def curl_command(f: flow.Flow) -> str:
15     if not hasattr(f, "request"):
16         raise exceptions.CommandError("Can't export flow with no request.")
17     data = "curl "
18     request = f.request.copy()  # type: ignore
19     request.decode(strict=False)
20     for k, v in request.headers.items(multi=True):
21         data += "-H '%s:%s' " % (k, v)
22     if request.method != "GET":
23         data += "-X %s " % request.method
24     data += "'%s'" % request.url
25     if request.content:
26         data += " --data-binary '%s'" % strutils.bytes_to_escaped_str(
27             request.content,
28             escape_single_quotes=True
29         )
30     return data
31 
32 
33 def raw(f: flow.Flow) -> bytes:
34     if not hasattr(f, "request"):
35         raise exceptions.CommandError("Can't export flow with no request.")
36     return assemble.assemble_request(f.request)  # type: ignore
37 
38 
39 formats = dict(
40     curl = curl_command,
41     raw = raw,
42 )
43 
44 
45 class Export():
46     @command.command("export.formats")
47     def formats(self) -> typing.Sequence[str]:
48         """
49             Return a list of the supported export formats.
50         """
51         return list(sorted(formats.keys()))
52 
53     @command.command("export.file")
54     def file(self, fmt: str, f: flow.Flow, path: mitmproxy.types.Path) -> None:
55         """
56             Export a flow to path.
57         """
58         if fmt not in formats:
59             raise exceptions.CommandError("No such export format: %s" % fmt)
60         func = formats[fmt]  # type: typing.Any
61         v = func(f)
62         try:
63             with open(path, "wb") as fp:
64                 if isinstance(v, bytes):
65                     fp.write(v)
66                 else:
67                     fp.write(v.encode("utf-8"))
68         except IOError as e:
69             ctx.log.error(str(e))
70 
71     @command.command("export.clip")
72     def clip(self, fmt: str, f: flow.Flow) -> None:
73         """
74             Export a flow to the system clipboard.
75         """
76         if fmt not in formats:
77             raise exceptions.CommandError("No such export format: %s" % fmt)
78         func = formats[fmt]  # type: typing.Any
79         v = strutils.always_str(func(f))
80         pyperclip.copy(v)
81
```

Path: `mitmproxy/addons/cut.py`
Content:
```
1 import io
2 import csv
3 import typing
4 from mitmproxy import command
5 from mitmproxy import exceptions
6 from mitmproxy import flow
7 from mitmproxy import ctx
8 from mitmproxy import certs
9 from mitmproxy.utils import strutils
10 import mitmproxy.types
11 
12 import pyperclip
13 
14 
15 def headername(spec: str):
16     if not (spec.startswith("header[") and spec.endswith("]")):
17         raise exceptions.CommandError("Invalid header spec: %s" % spec)
18     return spec[len("header["):-1].strip()
19 
20 
21 def is_addr(v):
22     return isinstance(v, tuple) and len(v) > 1
23 
24 
25 def extract(cut: str, f: flow.Flow) -> typing.Union[str, bytes]:
26     path = cut.split(".")
27     current = f  # type: typing.Any
28     for i, spec in enumerate(path):
29         if spec.startswith("_"):
30             raise exceptions.CommandError("Can't access internal attribute %s" % spec)
31 
32         part = getattr(current, spec, None)
33         if i == len(path) - 1:
34             if spec == "port" and is_addr(current):
35                 return str(current[1])
36             if spec == "host" and is_addr(current):
37                 return str(current[0])
38             elif spec.startswith("header["):
39                 if not current:
40                     return ""
41                 return current.headers.get(headername(spec), "")
42             elif isinstance(part, bytes):
43                 return part
44             elif isinstance(part, bool):
45                 return "true" if part else "false"
46             elif isinstance(part, certs.Cert):
47                 return part.to_pem().decode("ascii")
48         current = part
49     return str(current or "")
50 
51 
52 class Cut:
53     @command.command("cut")
54     def cut(
55         self,
56         flows: typing.Sequence[flow.Flow],
57         cuts: mitmproxy.types.CutSpec,
58     ) -> mitmproxy.types.Data:
59         """
60             Cut data from a set of flows. Cut specifications are attribute paths
61             from the base of the flow object, with a few conveniences - "port"
62             and "host" retrieve parts of an address tuple, ".header[key]"
63             retrieves a header value. Return values converted to strings or
64             bytes: SSL certicates are converted to PEM format, bools are "true"
65             or "false", "bytes" are preserved, and all other values are
66             converted to strings.
67         """
68         ret = []  # type:typing.List[typing.List[typing.Union[str, bytes]]]
69         for f in flows:
70             ret.append([extract(c, f) for c in cuts])
71         return ret  # type: ignore
72 
73     @command.command("cut.save")
74     def save(
75         self,
76         flows: typing.Sequence[flow.Flow],
77         cuts: mitmproxy.types.CutSpec,
78         path: mitmproxy.types.Path
79     ) -> None:
80         """
81             Save cuts to file. If there are multiple flows or cuts, the format
82             is UTF-8 encoded CSV. If there is exactly one row and one column,
83             the data is written to file as-is, with raw bytes preserved. If the
84             path is prefixed with a "+", values are appended if there is an
85             existing file.
86         """
87         append = False
88         if path.startswith("+"):
89             append = True
90             path = mitmproxy.types.Path(path[1:])
91         try:
92             if len(cuts) == 1 and len(flows) == 1:
93                 with open(path, "ab" if append else "wb") as fp:
94                     if fp.tell() > 0:
95                         # We're appending to a file that already exists and has content
96                         fp.write(b"\n")
97                     v = extract(cuts[0], flows[0])
98                     if isinstance(v, bytes):
99                         fp.write(v)
100                     else:
101                         fp.write(v.encode("utf8"))
102                 ctx.log.alert("Saved single cut.")
103             else:
104                 with open(path, "a" if append else "w", newline='', encoding="utf8") as fp:
105                     writer = csv.writer(fp)
106                     for f in flows:
107                         vals = [extract(c, f) for c in cuts]
108                         writer.writerow(
109                             [strutils.always_str(x) or "" for x in vals]  # type: ignore
110                         )
111                 ctx.log.alert("Saved %s cuts over %d flows as CSV." % (len(cuts), len(flows)))
112         except IOError as e:
113             ctx.log.error(str(e))
114 
115     @command.command("cut.clip")
116     def clip(
117         self,
118         flows: typing.Sequence[flow.Flow],
119         cuts: mitmproxy.types.CutSpec,
120     ) -> None:
121         """
122             Send cuts to the clipboard. If there are multiple flows or cuts, the
123             format is UTF-8 encoded CSV. If there is exactly one row and one
124             column, the data is written to file as-is, with raw bytes preserved.
125         """
126         fp = io.StringIO(newline="")
127         if len(cuts) == 1 and len(flows) == 1:
128             v = extract(cuts[0], flows[0])
129             if isinstance(v, bytes):
130                 fp.write(strutils.always_str(v))
131             else:
132                 fp.write("utf8")
133             ctx.log.alert("Clipped single cut.")
134         else:
135             writer = csv.writer(fp)
136             for f in flows:
137                 vals = [extract(c, f) for c in cuts]
138                 writer.writerow(
139                     [strutils.always_str(v) or "" for v in vals]  # type: ignore
140                 )
141             ctx.log.alert("Clipped %s cuts as CSV." % len(cuts))
142         pyperclip.copy(fp.getvalue())
143
```
--- 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()

```

diff --git a/mitmproxy/addons/cut.py b/mitmproxy/addons/cut.py
--- a/mitmproxy/addons/cut.py
+++ b/mitmproxy/addons/cut.py
@@ -139,4 +139,7 @@
                     [strutils.always_str(v) or "" for v in vals]  # type: ignore
                 )
             ctx.log.alert("Clipped %s cuts as CSV." % len(cuts))
-        pyperclip.copy(fp.getvalue())
+        try:
+            pyperclip.copy(fp.getvalue())
+        except pyperclip.PyperclipException as e:
+            ctx.log.error(str(e))
diff --git a/mitmproxy/addons/export.py b/mitmproxy/addons/export.py
--- a/mitmproxy/addons/export.py
+++ b/mitmproxy/addons/export.py
@@ -77,4 +77,7 @@
             raise exceptions.CommandError("No such export format: %s" % fmt)
         func = formats[fmt]  # type: typing.Any
         v = strutils.always_str(func(f))
-        pyperclip.copy(v)
+        try:
+            pyperclip.copy(v)
+        except pyperclip.PyperclipException as e:
+            ctx.log.error(str(e))

{"golden_diff": "diff --git a/mitmproxy/addons/cut.py b/mitmproxy/addons/cut.py\n--- a/mitmproxy/addons/cut.py\n+++ b/mitmproxy/addons/cut.py\n@@ -139,4 +139,7 @@\n                     [strutils.always_str(v) or \"\" for v in vals]  # type: ignore\n                 )\n             ctx.log.alert(\"Clipped %s cuts as CSV.\" % len(cuts))\n-        pyperclip.copy(fp.getvalue())\n+        try:\n+            pyperclip.copy(fp.getvalue())\n+        except pyperclip.PyperclipException as e:\n+            ctx.log.error(str(e))\ndiff --git a/mitmproxy/addons/export.py b/mitmproxy/addons/export.py\n--- a/mitmproxy/addons/export.py\n+++ b/mitmproxy/addons/export.py\n@@ -77,4 +77,7 @@\n             raise exceptions.CommandError(\"No such export format: %s\" % fmt)\n         func = formats[fmt]  # type: typing.Any\n         v = strutils.always_str(func(f))\n-        pyperclip.copy(v)\n+        try:\n+            pyperclip.copy(v)\n+        except pyperclip.PyperclipException as e:\n+            ctx.log.error(str(e))\n", "issue": "Mitmproxy crashes, when trying to send cuts to the clipboard without copy/paste mechanism for system\n##### Steps to reproduce the problem:\r\n\r\n1. Run mitmproxy.\r\n2. Press `Ctrl l`\r\n3. Input `: cut.clip 1 2 `. Press `Enter`.\r\n\r\nTraceback:\r\n```\r\nTraceback (most recent call last):\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py\", line 216, in run\r\n    self.loop.run()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 278, in run\r\n    self._run()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 376, in _run\r\n    self.event_loop.run()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 682, in run\r\n    self._loop()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 719, in _loop\r\n    self._watch_files[fd]()\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py\", line 393, in <lambda>\r\n    event_loop, callback, self.get_available_raw_input())\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py\", line 493, in parse_input\r\n    callback(processed, processed_codes)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 403, in _update\r\n    self.process_input(keys)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py\", line 503, in process_input\r\n    k = self._topmost_widget.keypress(self.screen_size, k)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py\", line 309, in keypress\r\n    k = super().keypress(size, k)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py\", line 1116, in keypress\r\n    return self.footer.keypress((maxcol,),key)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/statusbar.py\", line 149, in keypress\r\n    return self.ab.keypress(*args, **kwargs)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/statusbar.py\", line 104, in keypress\r\n    self.prompt_execute(self._w.get_edit_text())\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/statusbar.py\", line 124, in prompt_execute\r\n    msg = p(txt)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/commandexecutor.py\", line 17, in __call__\r\n    ret = self.master.commands.call(cmd)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 221, in call\r\n    return self.call_args(parts[0], parts[1:])\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 212, in call_args\r\n    return self.commands[path].call(args)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 101, in call\r\n    ret = self.func(*pargs)\r\n  File \"/usr/lib/python3.5/contextlib.py\", line 77, in __exit__\r\n    self.gen.throw(type, value, traceback)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/master.py\", line 68, in handlecontext\r\n    yield\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 101, in call\r\n    ret = self.func(*pargs)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py\", line 251, in wrapper\r\n    return function(*args, **kwargs)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/addons/cut.py\", line 139, in clip\r\n    pyperclip.copy(fp.getvalue())\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/pyperclip/__init__.py\", line 574, in lazy_load_stub_copy\r\n    return copy(text)\r\n  File \"/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/pyperclip/__init__.py\", line 284, in __call__\r\n    raise PyperclipException(EXCEPT_MSG)\r\npyperclip.PyperclipException: \r\n    Pyperclip could not find a copy/paste mechanism for your system.\r\n    For more information, please visit https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error \r\n```\r\n\r\n##### Any other comments? What have you tried so far?\r\n\r\nAs traceback says, the issue is relevant for Linux without installed copy/paste mechanism and can be fixed using this https://pyperclip.readthedocs.io/en/latest/introduction.html#not-implemented-error.\r\nI think we must just handle it properly.\r\n\r\n\r\n##### System information\r\n\r\nMitmproxy: 3.0.0.dev64 (commit 6dd336f) \r\nPython:    3.5.2\r\nOpenSSL:   OpenSSL 1.1.0g  2 Nov 2017\r\nPlatform:  Linux-4.4.0-112-generic-x86_64-with-Ubuntu-16.04-xenial\r\n\n", "before_files": [{"content": "import typing\n\nfrom mitmproxy import ctx\nfrom mitmproxy import command\nfrom mitmproxy import flow\nfrom mitmproxy import exceptions\nfrom mitmproxy.utils import strutils\nfrom mitmproxy.net.http.http1 import assemble\nimport mitmproxy.types\n\nimport pyperclip\n\n\ndef curl_command(f: flow.Flow) -> str:\n    if not hasattr(f, \"request\"):\n        raise exceptions.CommandError(\"Can't export flow with no request.\")\n    data = \"curl \"\n    request = f.request.copy()  # type: ignore\n    request.decode(strict=False)\n    for k, v in request.headers.items(multi=True):\n        data += \"-H '%s:%s' \" % (k, v)\n    if request.method != \"GET\":\n        data += \"-X %s \" % request.method\n    data += \"'%s'\" % request.url\n    if request.content:\n        data += \" --data-binary '%s'\" % strutils.bytes_to_escaped_str(\n            request.content,\n            escape_single_quotes=True\n        )\n    return data\n\n\ndef raw(f: flow.Flow) -> bytes:\n    if not hasattr(f, \"request\"):\n        raise exceptions.CommandError(\"Can't export flow with no request.\")\n    return assemble.assemble_request(f.request)  # type: ignore\n\n\nformats = dict(\n    curl = curl_command,\n    raw = raw,\n)\n\n\nclass Export():\n    @command.command(\"export.formats\")\n    def formats(self) -> typing.Sequence[str]:\n        \"\"\"\n            Return a list of the supported export formats.\n        \"\"\"\n        return list(sorted(formats.keys()))\n\n    @command.command(\"export.file\")\n    def file(self, fmt: str, f: flow.Flow, path: mitmproxy.types.Path) -> None:\n        \"\"\"\n            Export a flow to path.\n        \"\"\"\n        if fmt not in formats:\n            raise exceptions.CommandError(\"No such export format: %s\" % fmt)\n        func = formats[fmt]  # type: typing.Any\n        v = func(f)\n        try:\n            with open(path, \"wb\") as fp:\n                if isinstance(v, bytes):\n                    fp.write(v)\n                else:\n                    fp.write(v.encode(\"utf-8\"))\n        except IOError as e:\n            ctx.log.error(str(e))\n\n    @command.command(\"export.clip\")\n    def clip(self, fmt: str, f: flow.Flow) -> None:\n        \"\"\"\n            Export a flow to the system clipboard.\n        \"\"\"\n        if fmt not in formats:\n            raise exceptions.CommandError(\"No such export format: %s\" % fmt)\n        func = formats[fmt]  # type: typing.Any\n        v = strutils.always_str(func(f))\n        pyperclip.copy(v)\n", "path": "mitmproxy/addons/export.py"}, {"content": "import io\nimport csv\nimport typing\nfrom mitmproxy import command\nfrom mitmproxy import exceptions\nfrom mitmproxy import flow\nfrom mitmproxy import ctx\nfrom mitmproxy import certs\nfrom mitmproxy.utils import strutils\nimport mitmproxy.types\n\nimport pyperclip\n\n\ndef headername(spec: str):\n    if not (spec.startswith(\"header[\") and spec.endswith(\"]\")):\n        raise exceptions.CommandError(\"Invalid header spec: %s\" % spec)\n    return spec[len(\"header[\"):-1].strip()\n\n\ndef is_addr(v):\n    return isinstance(v, tuple) and len(v) > 1\n\n\ndef extract(cut: str, f: flow.Flow) -> typing.Union[str, bytes]:\n    path = cut.split(\".\")\n    current = f  # type: typing.Any\n    for i, spec in enumerate(path):\n        if spec.startswith(\"_\"):\n            raise exceptions.CommandError(\"Can't access internal attribute %s\" % spec)\n\n        part = getattr(current, spec, None)\n        if i == len(path) - 1:\n            if spec == \"port\" and is_addr(current):\n                return str(current[1])\n            if spec == \"host\" and is_addr(current):\n                return str(current[0])\n            elif spec.startswith(\"header[\"):\n                if not current:\n                    return \"\"\n                return current.headers.get(headername(spec), \"\")\n            elif isinstance(part, bytes):\n                return part\n            elif isinstance(part, bool):\n                return \"true\" if part else \"false\"\n            elif isinstance(part, certs.Cert):\n                return part.to_pem().decode(\"ascii\")\n        current = part\n    return str(current or \"\")\n\n\nclass Cut:\n    @command.command(\"cut\")\n    def cut(\n        self,\n        flows: typing.Sequence[flow.Flow],\n        cuts: mitmproxy.types.CutSpec,\n    ) -> mitmproxy.types.Data:\n        \"\"\"\n            Cut data from a set of flows. Cut specifications are attribute paths\n            from the base of the flow object, with a few conveniences - \"port\"\n            and \"host\" retrieve parts of an address tuple, \".header[key]\"\n            retrieves a header value. Return values converted to strings or\n            bytes: SSL certicates are converted to PEM format, bools are \"true\"\n            or \"false\", \"bytes\" are preserved, and all other values are\n            converted to strings.\n        \"\"\"\n        ret = []  # type:typing.List[typing.List[typing.Union[str, bytes]]]\n        for f in flows:\n            ret.append([extract(c, f) for c in cuts])\n        return ret  # type: ignore\n\n    @command.command(\"cut.save\")\n    def save(\n        self,\n        flows: typing.Sequence[flow.Flow],\n        cuts: mitmproxy.types.CutSpec,\n        path: mitmproxy.types.Path\n    ) -> None:\n        \"\"\"\n            Save cuts to file. If there are multiple flows or cuts, the format\n            is UTF-8 encoded CSV. If there is exactly one row and one column,\n            the data is written to file as-is, with raw bytes preserved. If the\n            path is prefixed with a \"+\", values are appended if there is an\n            existing file.\n        \"\"\"\n        append = False\n        if path.startswith(\"+\"):\n            append = True\n            path = mitmproxy.types.Path(path[1:])\n        try:\n            if len(cuts) == 1 and len(flows) == 1:\n                with open(path, \"ab\" if append else \"wb\") as fp:\n                    if fp.tell() > 0:\n                        # We're appending to a file that already exists and has content\n                        fp.write(b\"\\n\")\n                    v = extract(cuts[0], flows[0])\n                    if isinstance(v, bytes):\n                        fp.write(v)\n                    else:\n                        fp.write(v.encode(\"utf8\"))\n                ctx.log.alert(\"Saved single cut.\")\n            else:\n                with open(path, \"a\" if append else \"w\", newline='', encoding=\"utf8\") as fp:\n                    writer = csv.writer(fp)\n                    for f in flows:\n                        vals = [extract(c, f) for c in cuts]\n                        writer.writerow(\n                            [strutils.always_str(x) or \"\" for x in vals]  # type: ignore\n                        )\n                ctx.log.alert(\"Saved %s cuts over %d flows as CSV.\" % (len(cuts), len(flows)))\n        except IOError as e:\n            ctx.log.error(str(e))\n\n    @command.command(\"cut.clip\")\n    def clip(\n        self,\n        flows: typing.Sequence[flow.Flow],\n        cuts: mitmproxy.types.CutSpec,\n    ) -> None:\n        \"\"\"\n            Send cuts to the clipboard. If there are multiple flows or cuts, the\n            format is UTF-8 encoded CSV. If there is exactly one row and one\n            column, the data is written to file as-is, with raw bytes preserved.\n        \"\"\"\n        fp = io.StringIO(newline=\"\")\n        if len(cuts) == 1 and len(flows) == 1:\n            v = extract(cuts[0], flows[0])\n            if isinstance(v, bytes):\n                fp.write(strutils.always_str(v))\n            else:\n                fp.write(\"utf8\")\n            ctx.log.alert(\"Clipped single cut.\")\n        else:\n            writer = csv.writer(fp)\n            for f in flows:\n                vals = [extract(c, f) for c in cuts]\n                writer.writerow(\n                    [strutils.always_str(v) or \"\" for v in vals]  # type: ignore\n                )\n            ctx.log.alert(\"Clipped %s cuts as CSV.\" % len(cuts))\n        pyperclip.copy(fp.getvalue())\n", "path": "mitmproxy/addons/cut.py"}], "after_files": [{"content": "import typing\n\nfrom mitmproxy import ctx\nfrom mitmproxy import command\nfrom mitmproxy import flow\nfrom mitmproxy import exceptions\nfrom mitmproxy.utils import strutils\nfrom mitmproxy.net.http.http1 import assemble\nimport mitmproxy.types\n\nimport pyperclip\n\n\ndef curl_command(f: flow.Flow) -> str:\n    if not hasattr(f, \"request\"):\n        raise exceptions.CommandError(\"Can't export flow with no request.\")\n    data = \"curl \"\n    request = f.request.copy()  # type: ignore\n    request.decode(strict=False)\n    for k, v in request.headers.items(multi=True):\n        data += \"-H '%s:%s' \" % (k, v)\n    if request.method != \"GET\":\n        data += \"-X %s \" % request.method\n    data += \"'%s'\" % request.url\n    if request.content:\n        data += \" --data-binary '%s'\" % strutils.bytes_to_escaped_str(\n            request.content,\n            escape_single_quotes=True\n        )\n    return data\n\n\ndef raw(f: flow.Flow) -> bytes:\n    if not hasattr(f, \"request\"):\n        raise exceptions.CommandError(\"Can't export flow with no request.\")\n    return assemble.assemble_request(f.request)  # type: ignore\n\n\nformats = dict(\n    curl = curl_command,\n    raw = raw,\n)\n\n\nclass Export():\n    @command.command(\"export.formats\")\n    def formats(self) -> typing.Sequence[str]:\n        \"\"\"\n            Return a list of the supported export formats.\n        \"\"\"\n        return list(sorted(formats.keys()))\n\n    @command.command(\"export.file\")\n    def file(self, fmt: str, f: flow.Flow, path: mitmproxy.types.Path) -> None:\n        \"\"\"\n            Export a flow to path.\n        \"\"\"\n        if fmt not in formats:\n            raise exceptions.CommandError(\"No such export format: %s\" % fmt)\n        func = formats[fmt]  # type: typing.Any\n        v = func(f)\n        try:\n            with open(path, \"wb\") as fp:\n                if isinstance(v, bytes):\n                    fp.write(v)\n                else:\n                    fp.write(v.encode(\"utf-8\"))\n        except IOError as e:\n            ctx.log.error(str(e))\n\n    @command.command(\"export.clip\")\n    def clip(self, fmt: str, f: flow.Flow) -> None:\n        \"\"\"\n            Export a flow to the system clipboard.\n        \"\"\"\n        if fmt not in formats:\n            raise exceptions.CommandError(\"No such export format: %s\" % fmt)\n        func = formats[fmt]  # type: typing.Any\n        v = strutils.always_str(func(f))\n        try:\n            pyperclip.copy(v)\n        except pyperclip.PyperclipException as e:\n            ctx.log.error(str(e))\n", "path": "mitmproxy/addons/export.py"}, {"content": "import io\nimport csv\nimport typing\nfrom mitmproxy import command\nfrom mitmproxy import exceptions\nfrom mitmproxy import flow\nfrom mitmproxy import ctx\nfrom mitmproxy import certs\nfrom mitmproxy.utils import strutils\nimport mitmproxy.types\n\nimport pyperclip\n\n\ndef headername(spec: str):\n    if not (spec.startswith(\"header[\") and spec.endswith(\"]\")):\n        raise exceptions.CommandError(\"Invalid header spec: %s\" % spec)\n    return spec[len(\"header[\"):-1].strip()\n\n\ndef is_addr(v):\n    return isinstance(v, tuple) and len(v) > 1\n\n\ndef extract(cut: str, f: flow.Flow) -> typing.Union[str, bytes]:\n    path = cut.split(\".\")\n    current = f  # type: typing.Any\n    for i, spec in enumerate(path):\n        if spec.startswith(\"_\"):\n            raise exceptions.CommandError(\"Can't access internal attribute %s\" % spec)\n\n        part = getattr(current, spec, None)\n        if i == len(path) - 1:\n            if spec == \"port\" and is_addr(current):\n                return str(current[1])\n            if spec == \"host\" and is_addr(current):\n                return str(current[0])\n            elif spec.startswith(\"header[\"):\n                if not current:\n                    return \"\"\n                return current.headers.get(headername(spec), \"\")\n            elif isinstance(part, bytes):\n                return part\n            elif isinstance(part, bool):\n                return \"true\" if part else \"false\"\n            elif isinstance(part, certs.Cert):\n                return part.to_pem().decode(\"ascii\")\n        current = part\n    return str(current or \"\")\n\n\nclass Cut:\n    @command.command(\"cut\")\n    def cut(\n        self,\n        flows: typing.Sequence[flow.Flow],\n        cuts: mitmproxy.types.CutSpec,\n    ) -> mitmproxy.types.Data:\n        \"\"\"\n            Cut data from a set of flows. Cut specifications are attribute paths\n            from the base of the flow object, with a few conveniences - \"port\"\n            and \"host\" retrieve parts of an address tuple, \".header[key]\"\n            retrieves a header value. Return values converted to strings or\n            bytes: SSL certicates are converted to PEM format, bools are \"true\"\n            or \"false\", \"bytes\" are preserved, and all other values are\n            converted to strings.\n        \"\"\"\n        ret = []  # type:typing.List[typing.List[typing.Union[str, bytes]]]\n        for f in flows:\n            ret.append([extract(c, f) for c in cuts])\n        return ret  # type: ignore\n\n    @command.command(\"cut.save\")\n    def save(\n        self,\n        flows: typing.Sequence[flow.Flow],\n        cuts: mitmproxy.types.CutSpec,\n        path: mitmproxy.types.Path\n    ) -> None:\n        \"\"\"\n            Save cuts to file. If there are multiple flows or cuts, the format\n            is UTF-8 encoded CSV. If there is exactly one row and one column,\n            the data is written to file as-is, with raw bytes preserved. If the\n            path is prefixed with a \"+\", values are appended if there is an\n            existing file.\n        \"\"\"\n        append = False\n        if path.startswith(\"+\"):\n            append = True\n            path = mitmproxy.types.Path(path[1:])\n        try:\n            if len(cuts) == 1 and len(flows) == 1:\n                with open(path, \"ab\" if append else \"wb\") as fp:\n                    if fp.tell() > 0:\n                        # We're appending to a file that already exists and has content\n                        fp.write(b\"\\n\")\n                    v = extract(cuts[0], flows[0])\n                    if isinstance(v, bytes):\n                        fp.write(v)\n                    else:\n                        fp.write(v.encode(\"utf8\"))\n                ctx.log.alert(\"Saved single cut.\")\n            else:\n                with open(path, \"a\" if append else \"w\", newline='', encoding=\"utf8\") as fp:\n                    writer = csv.writer(fp)\n                    for f in flows:\n                        vals = [extract(c, f) for c in cuts]\n                        writer.writerow(\n                            [strutils.always_str(x) or \"\" for x in vals]  # type: ignore\n                        )\n                ctx.log.alert(\"Saved %s cuts over %d flows as CSV.\" % (len(cuts), len(flows)))\n        except IOError as e:\n            ctx.log.error(str(e))\n\n    @command.command(\"cut.clip\")\n    def clip(\n        self,\n        flows: typing.Sequence[flow.Flow],\n        cuts: mitmproxy.types.CutSpec,\n    ) -> None:\n        \"\"\"\n            Send cuts to the clipboard. If there are multiple flows or cuts, the\n            format is UTF-8 encoded CSV. If there is exactly one row and one\n            column, the data is written to file as-is, with raw bytes preserved.\n        \"\"\"\n        fp = io.StringIO(newline=\"\")\n        if len(cuts) == 1 and len(flows) == 1:\n            v = extract(cuts[0], flows[0])\n            if isinstance(v, bytes):\n                fp.write(strutils.always_str(v))\n            else:\n                fp.write(\"utf8\")\n            ctx.log.alert(\"Clipped single cut.\")\n        else:\n            writer = csv.writer(fp)\n            for f in flows:\n                vals = [extract(c, f) for c in cuts]\n                writer.writerow(\n                    [strutils.always_str(v) or \"\" for v in vals]  # type: ignore\n                )\n            ctx.log.alert(\"Clipped %s cuts as CSV.\" % len(cuts))\n        try:\n            pyperclip.copy(fp.getvalue())\n        except pyperclip.PyperclipException as e:\n            ctx.log.error(str(e))\n", "path": "mitmproxy/addons/cut.py"}]}