r"""Updates the dictionary with a single header line. >>> h = HTTPHeaders() >>> h.parse_line("Content-Type: text/html") >>> h.get('content-type') 'text/html' >>> h.parse_line("Content-Length: 42\r\n") >>> h.get('content-type') 'text/html'
(self, line: str, *, _chars_are_bytes: bool = True)
| 243 | yield (name, value) |
| 244 | |
| 245 | def parse_line(self, line: str, *, _chars_are_bytes: bool = True) -> None: |
| 246 | r"""Updates the dictionary with a single header line. |
| 247 | |
| 248 | >>> h = HTTPHeaders() |
| 249 | >>> h.parse_line("Content-Type: text/html") |
| 250 | >>> h.get('content-type') |
| 251 | 'text/html' |
| 252 | >>> h.parse_line("Content-Length: 42\r\n") |
| 253 | >>> h.get('content-type') |
| 254 | 'text/html' |
| 255 | |
| 256 | .. versionchanged:: 6.5 |
| 257 | Now supports lines with or without the trailing CRLF, making it possible |
| 258 | to pass lines from AsyncHTTPClient's header_callback directly to this method. |
| 259 | |
| 260 | .. deprecated:: 6.5 |
| 261 | In Tornado 7.0, certain deprecated features of HTTP will become errors. |
| 262 | Specifically, line folding and the use of LF (with CR) as a line separator |
| 263 | will be removed. |
| 264 | """ |
| 265 | if m := re.search(r"\r?\n$", line): |
| 266 | # RFC 9112 section 2.2: a recipient MAY recognize a single LF as a line |
| 267 | # terminator and ignore any preceding CR. |
| 268 | # TODO(7.0): Remove this support for LF-only line endings. |
| 269 | line = line[: m.start()] |
| 270 | if not line: |
| 271 | # Empty line, or the final CRLF of a header block. |
| 272 | return |
| 273 | if line[0] in HTTP_WHITESPACE: |
| 274 | # continuation of a multi-line header |
| 275 | # TODO(7.0): Remove support for line folding. |
| 276 | if self._last_key is None: |
| 277 | raise HTTPInputError("first header line cannot start with whitespace") |
| 278 | new_part = " " + line.strip(HTTP_WHITESPACE) |
| 279 | if _chars_are_bytes: |
| 280 | if not _ABNF.field_value.fullmatch(new_part[1:]): |
| 281 | raise HTTPInputError("Invalid header continuation %r" % new_part) |
| 282 | else: |
| 283 | if _FORBIDDEN_HEADER_CHARS_RE.search(new_part): |
| 284 | raise HTTPInputError("Invalid header value %r" % new_part) |
| 285 | self._as_list[self._last_key][-1] += new_part |
| 286 | self._combined_cache.pop(self._last_key, None) |
| 287 | else: |
| 288 | try: |
| 289 | name, value = line.split(":", 1) |
| 290 | except ValueError: |
| 291 | raise HTTPInputError("no colon in header line") |
| 292 | self.add( |
| 293 | name, value.strip(HTTP_WHITESPACE), _chars_are_bytes=_chars_are_bytes |
| 294 | ) |
| 295 | |
| 296 | @classmethod |
| 297 | def parse(cls, headers: str, *, _chars_are_bytes: bool = True) -> "HTTPHeaders": |