We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unpacking request body (gzip) doesn't automatically update the Content-Length header
##### Steps to reproduce the problem:

1. Send a request with the body packed as gzip.
2. Enter into the request in mitmproxy and notice Content-Length shows the packed length.
3. Unpack the body (the z key) - notice the Content-Length header doesn't change, although the unpacked content length must be known at this point. Replying the request fails in my case as the server complains about the stream having more data than expected (the un-gzipped data has more bytes than gzipped).

When the users goes into raw body edit mode ('e', than 'r') and just quits the editor, the Content-Length header is updated correctly.

##### System information

Mitmproxy version: 2.0.2 (release version)
Python version: 3.6.1
Platform: Darwin-14.5.0-x86_64-i386-64bit
SSL version: OpenSSL 1.1.0e  16 Feb 2017
Mac version: 10.10.5 ('', '', '') x86_64

The same behavior observed on an up-to-date Arch linux.

--- 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/net/http/message.py`
Content:
```
1 import re
2 from typing import Optional, Union  # noqa
3 
4 from mitmproxy.utils import strutils
5 from mitmproxy.net.http import encoding
6 from mitmproxy.types import serializable
7 from mitmproxy.net.http import headers
8 
9 
10 class MessageData(serializable.Serializable):
11     content = None  # type: bytes
12 
13     def __eq__(self, other):
14         if isinstance(other, MessageData):
15             return self.__dict__ == other.__dict__
16         return False
17 
18     def set_state(self, state):
19         for k, v in state.items():
20             if k == "headers":
21                 v = headers.Headers.from_state(v)
22             setattr(self, k, v)
23 
24     def get_state(self):
25         state = vars(self).copy()
26         state["headers"] = state["headers"].get_state()
27         return state
28 
29     @classmethod
30     def from_state(cls, state):
31         state["headers"] = headers.Headers.from_state(state["headers"])
32         return cls(**state)
33 
34 
35 class Message(serializable.Serializable):
36     data = None  # type: MessageData
37 
38     def __eq__(self, other):
39         if isinstance(other, Message):
40             return self.data == other.data
41         return False
42 
43     def get_state(self):
44         return self.data.get_state()
45 
46     def set_state(self, state):
47         self.data.set_state(state)
48 
49     @classmethod
50     def from_state(cls, state):
51         state["headers"] = headers.Headers.from_state(state["headers"])
52         return cls(**state)
53 
54     @property
55     def headers(self):
56         """
57         Message headers object
58 
59         Returns:
60             mitmproxy.net.http.Headers
61         """
62         return self.data.headers
63 
64     @headers.setter
65     def headers(self, h):
66         self.data.headers = h
67 
68     @property
69     def raw_content(self) -> bytes:
70         """
71         The raw (encoded) HTTP message body
72 
73         See also: :py:attr:`content`, :py:class:`text`
74         """
75         return self.data.content
76 
77     @raw_content.setter
78     def raw_content(self, content):
79         self.data.content = content
80 
81     def get_content(self, strict: bool=True) -> bytes:
82         """
83         The HTTP message body decoded with the content-encoding header (e.g. gzip)
84 
85         Raises:
86             ValueError, when the content-encoding is invalid and strict is True.
87 
88         See also: :py:class:`raw_content`, :py:attr:`text`
89         """
90         if self.raw_content is None:
91             return None
92         ce = self.headers.get("content-encoding")
93         if ce:
94             try:
95                 content = encoding.decode(self.raw_content, ce)
96                 # A client may illegally specify a byte -> str encoding here (e.g. utf8)
97                 if isinstance(content, str):
98                     raise ValueError("Invalid Content-Encoding: {}".format(ce))
99                 return content
100             except ValueError:
101                 if strict:
102                     raise
103                 return self.raw_content
104         else:
105             return self.raw_content
106 
107     def set_content(self, value):
108         if value is None:
109             self.raw_content = None
110             return
111         if not isinstance(value, bytes):
112             raise TypeError(
113                 "Message content must be bytes, not {}. "
114                 "Please use .text if you want to assign a str."
115                 .format(type(value).__name__)
116             )
117         ce = self.headers.get("content-encoding")
118         try:
119             self.raw_content = encoding.encode(value, ce or "identity")
120         except ValueError:
121             # So we have an invalid content-encoding?
122             # Let's remove it!
123             del self.headers["content-encoding"]
124             self.raw_content = value
125         self.headers["content-length"] = str(len(self.raw_content))
126 
127     content = property(get_content, set_content)
128 
129     @property
130     def http_version(self):
131         """
132         Version string, e.g. "HTTP/1.1"
133         """
134         return self.data.http_version.decode("utf-8", "surrogateescape")
135 
136     @http_version.setter
137     def http_version(self, http_version):
138         self.data.http_version = strutils.always_bytes(http_version, "utf-8", "surrogateescape")
139 
140     @property
141     def timestamp_start(self):
142         """
143         First byte timestamp
144         """
145         return self.data.timestamp_start
146 
147     @timestamp_start.setter
148     def timestamp_start(self, timestamp_start):
149         self.data.timestamp_start = timestamp_start
150 
151     @property
152     def timestamp_end(self):
153         """
154         Last byte timestamp
155         """
156         return self.data.timestamp_end
157 
158     @timestamp_end.setter
159     def timestamp_end(self, timestamp_end):
160         self.data.timestamp_end = timestamp_end
161 
162     def _get_content_type_charset(self) -> Optional[str]:
163         ct = headers.parse_content_type(self.headers.get("content-type", ""))
164         if ct:
165             return ct[2].get("charset")
166         return None
167 
168     def _guess_encoding(self) -> str:
169         enc = self._get_content_type_charset()
170         if enc:
171             return enc
172 
173         if "json" in self.headers.get("content-type", ""):
174             return "utf8"
175         else:
176             # We may also want to check for HTML meta tags here at some point.
177             # REGEX_ENCODING = re.compile(rb"""<meta[^>]+charset=['"]?([^'"]+)""")
178             return "latin-1"
179 
180     def get_text(self, strict: bool=True) -> Optional[str]:
181         """
182         The HTTP message body decoded with both content-encoding header (e.g. gzip)
183         and content-type header charset.
184 
185         Raises:
186             ValueError, when either content-encoding or charset is invalid and strict is True.
187 
188         See also: :py:attr:`content`, :py:class:`raw_content`
189         """
190         if self.raw_content is None:
191             return None
192         enc = self._guess_encoding()
193 
194         content = self.get_content(strict)
195         try:
196             return encoding.decode(content, enc)
197         except ValueError:
198             if strict:
199                 raise
200             return content.decode("utf8", "surrogateescape")
201 
202     def set_text(self, text):
203         if text is None:
204             self.content = None
205             return
206         enc = self._guess_encoding()
207 
208         try:
209             self.content = encoding.encode(text, enc)
210         except ValueError:
211             # Fall back to UTF-8 and update the content-type header.
212             ct = headers.parse_content_type(self.headers.get("content-type", "")) or ("text", "plain", {})
213             ct[2]["charset"] = "utf-8"
214             self.headers["content-type"] = headers.assemble_content_type(*ct)
215             enc = "utf8"
216             self.content = text.encode(enc, "surrogateescape")
217 
218     text = property(get_text, set_text)
219 
220     def decode(self, strict=True):
221         """
222         Decodes body based on the current Content-Encoding header, then
223         removes the header. If there is no Content-Encoding header, no
224         action is taken.
225 
226         Raises:
227             ValueError, when the content-encoding is invalid and strict is True.
228         """
229         self.raw_content = self.get_content(strict)
230         self.headers.pop("content-encoding", None)
231 
232     def encode(self, e):
233         """
234         Encodes body with the encoding e, where e is "gzip", "deflate", "identity", or "br".
235         Any existing content-encodings are overwritten,
236         the content is not decoded beforehand.
237 
238         Raises:
239             ValueError, when the specified content-encoding is invalid.
240         """
241         self.headers["content-encoding"] = e
242         self.content = self.raw_content
243         if "content-encoding" not in self.headers:
244             raise ValueError("Invalid content encoding {}".format(repr(e)))
245 
246     def replace(self, pattern, repl, flags=0, count=0):
247         """
248         Replaces a regular expression pattern with repl in both the headers
249         and the body of the message. Encoded body will be decoded
250         before replacement, and re-encoded afterwards.
251 
252         Returns:
253             The number of replacements made.
254         """
255         if isinstance(pattern, str):
256             pattern = strutils.escaped_str_to_bytes(pattern)
257         if isinstance(repl, str):
258             repl = strutils.escaped_str_to_bytes(repl)
259         replacements = 0
260         if self.content:
261             self.content, replacements = re.subn(
262                 pattern, repl, self.content, flags=flags, count=count
263             )
264         replacements += self.headers.replace(pattern, repl, flags=flags, count=count)
265         return replacements
266
```
--- 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/net/http/message.py b/mitmproxy/net/http/message.py
--- a/mitmproxy/net/http/message.py
+++ b/mitmproxy/net/http/message.py
@@ -226,8 +226,9 @@
         Raises:
             ValueError, when the content-encoding is invalid and strict is True.
         """
-        self.raw_content = self.get_content(strict)
+        decoded = self.get_content(strict)
         self.headers.pop("content-encoding", None)
+        self.content = decoded
 
     def encode(self, e):
         """

{"golden_diff": "diff --git a/mitmproxy/net/http/message.py b/mitmproxy/net/http/message.py\n--- a/mitmproxy/net/http/message.py\n+++ b/mitmproxy/net/http/message.py\n@@ -226,8 +226,9 @@\n         Raises:\n             ValueError, when the content-encoding is invalid and strict is True.\n         \"\"\"\n-        self.raw_content = self.get_content(strict)\n+        decoded = self.get_content(strict)\n         self.headers.pop(\"content-encoding\", None)\n+        self.content = decoded\n \n     def encode(self, e):\n         \"\"\"\n", "issue": "Unpacking request body (gzip) doesn't automatically update the Content-Length header\n##### Steps to reproduce the problem:\r\n\r\n1. Send a request with the body packed as gzip.\r\n2. Enter into the request in mitmproxy and notice Content-Length shows the packed length.\r\n3. Unpack the body (the z key) - notice the Content-Length header doesn't change, although the unpacked content length must be known at this point. Replying the request fails in my case as the server complains about the stream having more data than expected (the un-gzipped data has more bytes than gzipped).\r\n\r\nWhen the users goes into raw body edit mode ('e', than 'r') and just quits the editor, the Content-Length header is updated correctly.\r\n\r\n##### System information\r\n\r\nMitmproxy version: 2.0.2 (release version)\r\nPython version: 3.6.1\r\nPlatform: Darwin-14.5.0-x86_64-i386-64bit\r\nSSL version: OpenSSL 1.1.0e  16 Feb 2017\r\nMac version: 10.10.5 ('', '', '') x86_64\r\n\r\nThe same behavior observed on an up-to-date Arch linux.\n", "before_files": [{"content": "import re\nfrom typing import Optional, Union  # noqa\n\nfrom mitmproxy.utils import strutils\nfrom mitmproxy.net.http import encoding\nfrom mitmproxy.types import serializable\nfrom mitmproxy.net.http import headers\n\n\nclass MessageData(serializable.Serializable):\n    content = None  # type: bytes\n\n    def __eq__(self, other):\n        if isinstance(other, MessageData):\n            return self.__dict__ == other.__dict__\n        return False\n\n    def set_state(self, state):\n        for k, v in state.items():\n            if k == \"headers\":\n                v = headers.Headers.from_state(v)\n            setattr(self, k, v)\n\n    def get_state(self):\n        state = vars(self).copy()\n        state[\"headers\"] = state[\"headers\"].get_state()\n        return state\n\n    @classmethod\n    def from_state(cls, state):\n        state[\"headers\"] = headers.Headers.from_state(state[\"headers\"])\n        return cls(**state)\n\n\nclass Message(serializable.Serializable):\n    data = None  # type: MessageData\n\n    def __eq__(self, other):\n        if isinstance(other, Message):\n            return self.data == other.data\n        return False\n\n    def get_state(self):\n        return self.data.get_state()\n\n    def set_state(self, state):\n        self.data.set_state(state)\n\n    @classmethod\n    def from_state(cls, state):\n        state[\"headers\"] = headers.Headers.from_state(state[\"headers\"])\n        return cls(**state)\n\n    @property\n    def headers(self):\n        \"\"\"\n        Message headers object\n\n        Returns:\n            mitmproxy.net.http.Headers\n        \"\"\"\n        return self.data.headers\n\n    @headers.setter\n    def headers(self, h):\n        self.data.headers = h\n\n    @property\n    def raw_content(self) -> bytes:\n        \"\"\"\n        The raw (encoded) HTTP message body\n\n        See also: :py:attr:`content`, :py:class:`text`\n        \"\"\"\n        return self.data.content\n\n    @raw_content.setter\n    def raw_content(self, content):\n        self.data.content = content\n\n    def get_content(self, strict: bool=True) -> bytes:\n        \"\"\"\n        The HTTP message body decoded with the content-encoding header (e.g. gzip)\n\n        Raises:\n            ValueError, when the content-encoding is invalid and strict is True.\n\n        See also: :py:class:`raw_content`, :py:attr:`text`\n        \"\"\"\n        if self.raw_content is None:\n            return None\n        ce = self.headers.get(\"content-encoding\")\n        if ce:\n            try:\n                content = encoding.decode(self.raw_content, ce)\n                # A client may illegally specify a byte -> str encoding here (e.g. utf8)\n                if isinstance(content, str):\n                    raise ValueError(\"Invalid Content-Encoding: {}\".format(ce))\n                return content\n            except ValueError:\n                if strict:\n                    raise\n                return self.raw_content\n        else:\n            return self.raw_content\n\n    def set_content(self, value):\n        if value is None:\n            self.raw_content = None\n            return\n        if not isinstance(value, bytes):\n            raise TypeError(\n                \"Message content must be bytes, not {}. \"\n                \"Please use .text if you want to assign a str.\"\n                .format(type(value).__name__)\n            )\n        ce = self.headers.get(\"content-encoding\")\n        try:\n            self.raw_content = encoding.encode(value, ce or \"identity\")\n        except ValueError:\n            # So we have an invalid content-encoding?\n            # Let's remove it!\n            del self.headers[\"content-encoding\"]\n            self.raw_content = value\n        self.headers[\"content-length\"] = str(len(self.raw_content))\n\n    content = property(get_content, set_content)\n\n    @property\n    def http_version(self):\n        \"\"\"\n        Version string, e.g. \"HTTP/1.1\"\n        \"\"\"\n        return self.data.http_version.decode(\"utf-8\", \"surrogateescape\")\n\n    @http_version.setter\n    def http_version(self, http_version):\n        self.data.http_version = strutils.always_bytes(http_version, \"utf-8\", \"surrogateescape\")\n\n    @property\n    def timestamp_start(self):\n        \"\"\"\n        First byte timestamp\n        \"\"\"\n        return self.data.timestamp_start\n\n    @timestamp_start.setter\n    def timestamp_start(self, timestamp_start):\n        self.data.timestamp_start = timestamp_start\n\n    @property\n    def timestamp_end(self):\n        \"\"\"\n        Last byte timestamp\n        \"\"\"\n        return self.data.timestamp_end\n\n    @timestamp_end.setter\n    def timestamp_end(self, timestamp_end):\n        self.data.timestamp_end = timestamp_end\n\n    def _get_content_type_charset(self) -> Optional[str]:\n        ct = headers.parse_content_type(self.headers.get(\"content-type\", \"\"))\n        if ct:\n            return ct[2].get(\"charset\")\n        return None\n\n    def _guess_encoding(self) -> str:\n        enc = self._get_content_type_charset()\n        if enc:\n            return enc\n\n        if \"json\" in self.headers.get(\"content-type\", \"\"):\n            return \"utf8\"\n        else:\n            # We may also want to check for HTML meta tags here at some point.\n            # REGEX_ENCODING = re.compile(rb\"\"\"<meta[^>]+charset=['\"]?([^'\"]+)\"\"\")\n            return \"latin-1\"\n\n    def get_text(self, strict: bool=True) -> Optional[str]:\n        \"\"\"\n        The HTTP message body decoded with both content-encoding header (e.g. gzip)\n        and content-type header charset.\n\n        Raises:\n            ValueError, when either content-encoding or charset is invalid and strict is True.\n\n        See also: :py:attr:`content`, :py:class:`raw_content`\n        \"\"\"\n        if self.raw_content is None:\n            return None\n        enc = self._guess_encoding()\n\n        content = self.get_content(strict)\n        try:\n            return encoding.decode(content, enc)\n        except ValueError:\n            if strict:\n                raise\n            return content.decode(\"utf8\", \"surrogateescape\")\n\n    def set_text(self, text):\n        if text is None:\n            self.content = None\n            return\n        enc = self._guess_encoding()\n\n        try:\n            self.content = encoding.encode(text, enc)\n        except ValueError:\n            # Fall back to UTF-8 and update the content-type header.\n            ct = headers.parse_content_type(self.headers.get(\"content-type\", \"\")) or (\"text\", \"plain\", {})\n            ct[2][\"charset\"] = \"utf-8\"\n            self.headers[\"content-type\"] = headers.assemble_content_type(*ct)\n            enc = \"utf8\"\n            self.content = text.encode(enc, \"surrogateescape\")\n\n    text = property(get_text, set_text)\n\n    def decode(self, strict=True):\n        \"\"\"\n        Decodes body based on the current Content-Encoding header, then\n        removes the header. If there is no Content-Encoding header, no\n        action is taken.\n\n        Raises:\n            ValueError, when the content-encoding is invalid and strict is True.\n        \"\"\"\n        self.raw_content = self.get_content(strict)\n        self.headers.pop(\"content-encoding\", None)\n\n    def encode(self, e):\n        \"\"\"\n        Encodes body with the encoding e, where e is \"gzip\", \"deflate\", \"identity\", or \"br\".\n        Any existing content-encodings are overwritten,\n        the content is not decoded beforehand.\n\n        Raises:\n            ValueError, when the specified content-encoding is invalid.\n        \"\"\"\n        self.headers[\"content-encoding\"] = e\n        self.content = self.raw_content\n        if \"content-encoding\" not in self.headers:\n            raise ValueError(\"Invalid content encoding {}\".format(repr(e)))\n\n    def replace(self, pattern, repl, flags=0, count=0):\n        \"\"\"\n        Replaces a regular expression pattern with repl in both the headers\n        and the body of the message. Encoded body will be decoded\n        before replacement, and re-encoded afterwards.\n\n        Returns:\n            The number of replacements made.\n        \"\"\"\n        if isinstance(pattern, str):\n            pattern = strutils.escaped_str_to_bytes(pattern)\n        if isinstance(repl, str):\n            repl = strutils.escaped_str_to_bytes(repl)\n        replacements = 0\n        if self.content:\n            self.content, replacements = re.subn(\n                pattern, repl, self.content, flags=flags, count=count\n            )\n        replacements += self.headers.replace(pattern, repl, flags=flags, count=count)\n        return replacements\n", "path": "mitmproxy/net/http/message.py"}], "after_files": [{"content": "import re\nfrom typing import Optional, Union  # noqa\n\nfrom mitmproxy.utils import strutils\nfrom mitmproxy.net.http import encoding\nfrom mitmproxy.types import serializable\nfrom mitmproxy.net.http import headers\n\n\nclass MessageData(serializable.Serializable):\n    content = None  # type: bytes\n\n    def __eq__(self, other):\n        if isinstance(other, MessageData):\n            return self.__dict__ == other.__dict__\n        return False\n\n    def set_state(self, state):\n        for k, v in state.items():\n            if k == \"headers\":\n                v = headers.Headers.from_state(v)\n            setattr(self, k, v)\n\n    def get_state(self):\n        state = vars(self).copy()\n        state[\"headers\"] = state[\"headers\"].get_state()\n        return state\n\n    @classmethod\n    def from_state(cls, state):\n        state[\"headers\"] = headers.Headers.from_state(state[\"headers\"])\n        return cls(**state)\n\n\nclass Message(serializable.Serializable):\n    data = None  # type: MessageData\n\n    def __eq__(self, other):\n        if isinstance(other, Message):\n            return self.data == other.data\n        return False\n\n    def get_state(self):\n        return self.data.get_state()\n\n    def set_state(self, state):\n        self.data.set_state(state)\n\n    @classmethod\n    def from_state(cls, state):\n        state[\"headers\"] = headers.Headers.from_state(state[\"headers\"])\n        return cls(**state)\n\n    @property\n    def headers(self):\n        \"\"\"\n        Message headers object\n\n        Returns:\n            mitmproxy.net.http.Headers\n        \"\"\"\n        return self.data.headers\n\n    @headers.setter\n    def headers(self, h):\n        self.data.headers = h\n\n    @property\n    def raw_content(self) -> bytes:\n        \"\"\"\n        The raw (encoded) HTTP message body\n\n        See also: :py:attr:`content`, :py:class:`text`\n        \"\"\"\n        return self.data.content\n\n    @raw_content.setter\n    def raw_content(self, content):\n        self.data.content = content\n\n    def get_content(self, strict: bool=True) -> bytes:\n        \"\"\"\n        The HTTP message body decoded with the content-encoding header (e.g. gzip)\n\n        Raises:\n            ValueError, when the content-encoding is invalid and strict is True.\n\n        See also: :py:class:`raw_content`, :py:attr:`text`\n        \"\"\"\n        if self.raw_content is None:\n            return None\n        ce = self.headers.get(\"content-encoding\")\n        if ce:\n            try:\n                content = encoding.decode(self.raw_content, ce)\n                # A client may illegally specify a byte -> str encoding here (e.g. utf8)\n                if isinstance(content, str):\n                    raise ValueError(\"Invalid Content-Encoding: {}\".format(ce))\n                return content\n            except ValueError:\n                if strict:\n                    raise\n                return self.raw_content\n        else:\n            return self.raw_content\n\n    def set_content(self, value):\n        if value is None:\n            self.raw_content = None\n            return\n        if not isinstance(value, bytes):\n            raise TypeError(\n                \"Message content must be bytes, not {}. \"\n                \"Please use .text if you want to assign a str.\"\n                .format(type(value).__name__)\n            )\n        ce = self.headers.get(\"content-encoding\")\n        try:\n            self.raw_content = encoding.encode(value, ce or \"identity\")\n        except ValueError:\n            # So we have an invalid content-encoding?\n            # Let's remove it!\n            del self.headers[\"content-encoding\"]\n            self.raw_content = value\n        self.headers[\"content-length\"] = str(len(self.raw_content))\n\n    content = property(get_content, set_content)\n\n    @property\n    def http_version(self):\n        \"\"\"\n        Version string, e.g. \"HTTP/1.1\"\n        \"\"\"\n        return self.data.http_version.decode(\"utf-8\", \"surrogateescape\")\n\n    @http_version.setter\n    def http_version(self, http_version):\n        self.data.http_version = strutils.always_bytes(http_version, \"utf-8\", \"surrogateescape\")\n\n    @property\n    def timestamp_start(self):\n        \"\"\"\n        First byte timestamp\n        \"\"\"\n        return self.data.timestamp_start\n\n    @timestamp_start.setter\n    def timestamp_start(self, timestamp_start):\n        self.data.timestamp_start = timestamp_start\n\n    @property\n    def timestamp_end(self):\n        \"\"\"\n        Last byte timestamp\n        \"\"\"\n        return self.data.timestamp_end\n\n    @timestamp_end.setter\n    def timestamp_end(self, timestamp_end):\n        self.data.timestamp_end = timestamp_end\n\n    def _get_content_type_charset(self) -> Optional[str]:\n        ct = headers.parse_content_type(self.headers.get(\"content-type\", \"\"))\n        if ct:\n            return ct[2].get(\"charset\")\n        return None\n\n    def _guess_encoding(self) -> str:\n        enc = self._get_content_type_charset()\n        if enc:\n            return enc\n\n        if \"json\" in self.headers.get(\"content-type\", \"\"):\n            return \"utf8\"\n        else:\n            # We may also want to check for HTML meta tags here at some point.\n            # REGEX_ENCODING = re.compile(rb\"\"\"<meta[^>]+charset=['\"]?([^'\"]+)\"\"\")\n            return \"latin-1\"\n\n    def get_text(self, strict: bool=True) -> Optional[str]:\n        \"\"\"\n        The HTTP message body decoded with both content-encoding header (e.g. gzip)\n        and content-type header charset.\n\n        Raises:\n            ValueError, when either content-encoding or charset is invalid and strict is True.\n\n        See also: :py:attr:`content`, :py:class:`raw_content`\n        \"\"\"\n        if self.raw_content is None:\n            return None\n        enc = self._guess_encoding()\n\n        content = self.get_content(strict)\n        try:\n            return encoding.decode(content, enc)\n        except ValueError:\n            if strict:\n                raise\n            return content.decode(\"utf8\", \"surrogateescape\")\n\n    def set_text(self, text):\n        if text is None:\n            self.content = None\n            return\n        enc = self._guess_encoding()\n\n        try:\n            self.content = encoding.encode(text, enc)\n        except ValueError:\n            # Fall back to UTF-8 and update the content-type header.\n            ct = headers.parse_content_type(self.headers.get(\"content-type\", \"\")) or (\"text\", \"plain\", {})\n            ct[2][\"charset\"] = \"utf-8\"\n            self.headers[\"content-type\"] = headers.assemble_content_type(*ct)\n            enc = \"utf8\"\n            self.content = text.encode(enc, \"surrogateescape\")\n\n    text = property(get_text, set_text)\n\n    def decode(self, strict=True):\n        \"\"\"\n        Decodes body based on the current Content-Encoding header, then\n        removes the header. If there is no Content-Encoding header, no\n        action is taken.\n\n        Raises:\n            ValueError, when the content-encoding is invalid and strict is True.\n        \"\"\"\n        decoded = self.get_content(strict)\n        self.headers.pop(\"content-encoding\", None)\n        self.content = decoded\n\n    def encode(self, e):\n        \"\"\"\n        Encodes body with the encoding e, where e is \"gzip\", \"deflate\", \"identity\", or \"br\".\n        Any existing content-encodings are overwritten,\n        the content is not decoded beforehand.\n\n        Raises:\n            ValueError, when the specified content-encoding is invalid.\n        \"\"\"\n        self.headers[\"content-encoding\"] = e\n        self.content = self.raw_content\n        if \"content-encoding\" not in self.headers:\n            raise ValueError(\"Invalid content encoding {}\".format(repr(e)))\n\n    def replace(self, pattern, repl, flags=0, count=0):\n        \"\"\"\n        Replaces a regular expression pattern with repl in both the headers\n        and the body of the message. Encoded body will be decoded\n        before replacement, and re-encoded afterwards.\n\n        Returns:\n            The number of replacements made.\n        \"\"\"\n        if isinstance(pattern, str):\n            pattern = strutils.escaped_str_to_bytes(pattern)\n        if isinstance(repl, str):\n            repl = strutils.escaped_str_to_bytes(repl)\n        replacements = 0\n        if self.content:\n            self.content, replacements = re.subn(\n                pattern, repl, self.content, flags=flags, count=count\n            )\n        replacements += self.headers.replace(pattern, repl, flags=flags, count=count)\n        return replacements\n", "path": "mitmproxy/net/http/message.py"}]}