1from __future__ import annotations
2
3import logging
4import os
5import shutil
6import sys
7import tempfile
8from enum import IntEnum, global_enum
9from io import BufferedRandom, BytesIO
10from numbers import Number
11from typing import TYPE_CHECKING, cast
12
13from .decoders import Base64Decoder, QuotedPrintableDecoder
14from .exceptions import FileError, FormParserError, MultipartParseError, QuerystringParseError
15
16if TYPE_CHECKING:
17 from collections.abc import Callable
18 from typing import Any, Literal, Protocol, TypeAlias, TypedDict
19
20 class SupportsRead(Protocol):
21 def read(self, __n: int) -> bytes: ...
22
23 class QuerystringCallbacks(TypedDict, total=False):
24 on_field_start: Callable[[], None]
25 on_field_name: Callable[[bytes, int, int], None]
26 on_field_data: Callable[[bytes, int, int], None]
27 on_field_end: Callable[[], None]
28 on_end: Callable[[], None]
29
30 class OctetStreamCallbacks(TypedDict, total=False):
31 on_start: Callable[[], None]
32 on_data: Callable[[bytes, int, int], None]
33 on_end: Callable[[], None]
34
35 class MultipartCallbacks(TypedDict, total=False):
36 on_part_begin: Callable[[], None]
37 on_part_data: Callable[[bytes, int, int], None]
38 on_part_end: Callable[[], None]
39 on_header_begin: Callable[[], None]
40 on_header_field: Callable[[bytes, int, int], None]
41 on_header_value: Callable[[bytes, int, int], None]
42 on_header_end: Callable[[], None]
43 on_headers_finished: Callable[[], None]
44 on_end: Callable[[], None]
45
46 class FileConfig(TypedDict, total=False):
47 UPLOAD_DIR: str | bytes | None
48 UPLOAD_DELETE_TMP: bool
49 UPLOAD_KEEP_FILENAME: bool
50 UPLOAD_KEEP_EXTENSIONS: bool
51 MAX_MEMORY_FILE_SIZE: int
52
53 class FormParserConfig(FileConfig):
54 UPLOAD_ERROR_ON_BAD_CTE: bool
55 MAX_BODY_SIZE: float
56 MAX_FIELDS: int | float
57 MAX_FIELD_SIZE: int | float
58 MAX_HEADER_COUNT: int
59 MAX_HEADER_SIZE: int
60
61 CallbackName: TypeAlias = Literal[
62 "start",
63 "data",
64 "end",
65 "field_start",
66 "field_name",
67 "field_data",
68 "field_end",
69 "part_begin",
70 "part_data",
71 "part_end",
72 "header_begin",
73 "header_field",
74 "header_value",
75 "header_end",
76 "headers_finished",
77 ]
78
79# Unique missing object.
80_missing = object()
81
82
83def _noop_event() -> None:
84 pass
85
86
87def _noop_data(_data: bytes, _start: int, _end: int) -> None:
88 pass
89
90
91@global_enum
92class QuerystringState(IntEnum):
93 """Querystring parser states.
94
95 These are used to keep track of the state of the parser, and are used to determine
96 what to do when new data is encountered.
97 """
98
99 BEFORE_FIELD = 0
100 FIELD_NAME = 1
101 FIELD_DATA = 2
102
103
104@global_enum
105class MultipartState(IntEnum):
106 """Multipart parser states.
107
108 These are used to keep track of the state of the parser, and are used to determine
109 what to do when new data is encountered.
110 """
111
112 START = 0
113 START_BOUNDARY = 1
114 HEADER_FIELD_START = 2
115 HEADER_FIELD = 3
116 HEADER_VALUE_START = 4
117 HEADER_VALUE = 5
118 HEADER_VALUE_ALMOST_DONE = 6
119 HEADERS_ALMOST_DONE = 7
120 PART_DATA_START = 8
121 PART_DATA = 9
122 PART_DATA_END = 10
123 END_BOUNDARY = 11
124 END = 12
125
126
127if TYPE_CHECKING:
128 BEFORE_FIELD = QuerystringState.BEFORE_FIELD
129 FIELD_NAME = QuerystringState.FIELD_NAME
130 FIELD_DATA = QuerystringState.FIELD_DATA
131
132 START = MultipartState.START
133 START_BOUNDARY = MultipartState.START_BOUNDARY
134 HEADER_FIELD_START = MultipartState.HEADER_FIELD_START
135 HEADER_FIELD = MultipartState.HEADER_FIELD
136 HEADER_VALUE_START = MultipartState.HEADER_VALUE_START
137 HEADER_VALUE = MultipartState.HEADER_VALUE
138 HEADER_VALUE_ALMOST_DONE = MultipartState.HEADER_VALUE_ALMOST_DONE
139 HEADERS_ALMOST_DONE = MultipartState.HEADERS_ALMOST_DONE
140 PART_DATA_START = MultipartState.PART_DATA_START
141 PART_DATA = MultipartState.PART_DATA
142 PART_DATA_END = MultipartState.PART_DATA_END
143 END_BOUNDARY = MultipartState.END_BOUNDARY
144 END = MultipartState.END
145
146# Flags for the multipart parser.
147FLAG_PART_BOUNDARY = 1
148FLAG_LAST_BOUNDARY = 2
149
150# Get constants. Since iterating over a str on Python 2 gives you a 1-length
151# string, but iterating over a bytes object on Python 3 gives you an integer,
152# we need to save these constants.
153CR = b"\r"[0]
154LF = b"\n"[0]
155COLON = b":"[0]
156SPACE = b" "[0]
157HYPHEN = b"-"[0]
158AMPERSAND = b"&"[0]
159LOWER_A = b"a"[0]
160LOWER_Z = b"z"[0]
161NULL = b"\x00"[0]
162
163# fmt: off
164# Mask for ASCII characters that can be http tokens.
165# Per RFC7230 - 3.2.6, this is all alpha-numeric characters
166# and these: !#$%&'*+-.^_`|~
167TOKEN_CHARS = (
168 b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
169 b"abcdefghijklmnopqrstuvwxyz"
170 b"0123456789"
171 b"!#$%&'*+-.^_`|~")
172TOKEN_CHARS_SET = frozenset(TOKEN_CHARS)
173# fmt: on
174
175DEFAULT_MAX_HEADER_COUNT = 8
176"""Default maximum number of headers allowed per multipart part."""
177
178DEFAULT_MAX_HEADER_SIZE = 4096 + 128
179"""Default maximum size of a single multipart header line, including syntax overhead."""
180
181MAX_BOUNDARY_LENGTH = 256
182"""Maximum allowed length of a multipart boundary.
183
184[RFC 2046 §5.1.1](https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.1)
185recommends boundaries be at most 70 bytes. 256 bytes is generous headroom over
186every HTTP client.
187"""
188
189
190def _parseparam(s: str) -> list[str]:
191 # Vendored from the standard library's
192 # [`email.message._parseparam`](https://github.com/python/cpython/blob/v3.14.2/Lib/email/message.py#L73-L96)
193 # to split a header into its `;`-separated parts without treating a `;` inside a double-quoted string as a
194 # separator - and without the RFC 2231 decoding that `email.message.Message.get_params` would apply on top.
195 s = ";" + s
196 plist: list[str] = []
197 start = 0
198 while s.find(";", start) == start:
199 start += 1
200 end = s.find(";", start)
201 ind, diff = start, 0
202 while end > 0:
203 diff += s.count('"', ind, end) - s.count('\\"', ind, end)
204 if diff % 2 == 0:
205 break
206 end, ind = ind, s.find(";", end + 1)
207 if end < 0:
208 end = len(s)
209 i = s.find("=", start, end)
210 if i == -1:
211 f = s[start:end]
212 else:
213 f = s[start:i].rstrip().lower() + "=" + s[i + 1 : end].lstrip()
214 plist.append(f.strip())
215 start = end
216 return plist
217
218
219def parse_options_header(value: str | bytes | None) -> tuple[bytes, dict[bytes, bytes]]:
220 """Parses a Content-Type header into a value in the following format: (content_type, {parameters})."""
221 if not value:
222 return (b"", {})
223
224 # If we are passed bytes, we assume that it conforms to WSGI, encoding in latin-1.
225 if isinstance(value, bytes): # pragma: no cover
226 value = value.decode("latin-1")
227
228 # For types
229 assert isinstance(value, str), "Value should be a string by now"
230
231 # If we have no options, return the string as-is.
232 if ";" not in value:
233 return (value.lower().strip().encode("latin-1"), {})
234
235 ctype, *segments = _parseparam(value)
236 options: dict[bytes, bytes] = {}
237 for segment in segments:
238 key, _, val = segment.partition("=")
239 # [RFC 7578 §4.2](https://datatracker.ietf.org/doc/html/rfc7578#section-4.2)
240 # forbids the RFC 5987/2231 extended syntax (`key*=`, `key*0`, ...) in
241 # multipart/form-data, so we ignore those parameters and keep the plain
242 # `key` authoritative.
243 if "*" in key:
244 continue
245 if len(val) >= 2 and val[0] == '"' and val[-1] == '"':
246 val = val[1:-1].replace("\\\\", "\\").replace('\\"', '"')
247 # Work around an IE6 bug where the full file path is sent instead of
248 # just the filename.
249 if key == "filename" and (val[1:3] == ":\\" or val[:2] == "\\\\"):
250 val = val.split("\\")[-1]
251 options[key.encode("latin-1")] = val.encode("latin-1")
252 return ctype.lower().encode("latin-1"), options
253
254
255class Field:
256 """A Field object represents a (parsed) form field. It represents a single
257 field with a corresponding name and value.
258
259 The name that a :class:`Field` will be instantiated with is the same name
260 that would be found in the following HTML::
261
262 <input name="name_goes_here" type="text"/>
263
264 This class defines two methods, :meth:`on_data` and :meth:`on_end`, that
265 will be called when data is written to the Field, and when the Field is
266 finalized, respectively.
267
268 Args:
269 name: The name of the form field.
270 content_type: The value of the Content-Type header for this field.
271 """
272
273 def __init__(self, name: bytes | None, *, content_type: str | None = None) -> None:
274 self._name = name
275 self._value: list[bytes] = []
276 self._content_type = content_type
277
278 # We cache the joined version of _value for speed.
279 self._cache = _missing
280
281 @classmethod
282 def from_value(cls, name: bytes, value: bytes | None) -> Field:
283 """Create an instance of a :class:`Field`, and set the corresponding
284 value - either None or an actual value. This method will also
285 finalize the Field itself.
286
287 Args:
288 name: the name of the form field.
289 value: the value of the form field - either a bytestring or None.
290
291 Returns:
292 A new instance of a [`Field`][python_multipart.Field].
293 """
294
295 f = cls(name)
296 if value is None:
297 f.set_none()
298 else:
299 f.write(value)
300 f.finalize()
301 return f
302
303 def write(self, data: bytes) -> int:
304 """Write some data into the form field.
305
306 Args:
307 data: The data to write to the field.
308
309 Returns:
310 The number of bytes written.
311 """
312 return self.on_data(data)
313
314 def on_data(self, data: bytes) -> int:
315 """This method is a callback that will be called whenever data is
316 written to the Field.
317
318 Args:
319 data: The data to write to the field.
320
321 Returns:
322 The number of bytes written.
323 """
324 self._value.append(data)
325 self._cache = _missing
326 return len(data)
327
328 def on_end(self) -> None:
329 """This method is called whenever the Field is finalized."""
330 if self._cache is _missing:
331 self._cache = b"".join(self._value)
332
333 def finalize(self) -> None:
334 """Finalize the form field."""
335 self.on_end()
336
337 def close(self) -> None:
338 """Close the Field object. This will free any underlying cache."""
339 # Free our value array.
340 if self._cache is _missing:
341 self._cache = b"".join(self._value)
342
343 del self._value
344
345 def set_none(self) -> None:
346 """Some fields in a querystring can possibly have a value of None - for
347 example, the string "foo&bar=&baz=asdf" will have a field with the
348 name "foo" and value None, one with name "bar" and value "", and one
349 with name "baz" and value "asdf". Since the write() interface doesn't
350 support writing None, this function will set the field value to None.
351 """
352 self._cache = None
353
354 @property
355 def field_name(self) -> bytes | None:
356 """This property returns the name of the field."""
357 return self._name
358
359 @property
360 def value(self) -> bytes | None:
361 """This property returns the value of the form field."""
362 if self._cache is _missing:
363 self._cache = b"".join(self._value)
364
365 assert isinstance(self._cache, bytes) or self._cache is None
366 return self._cache
367
368 @property
369 def content_type(self) -> str | None:
370 """This property returns the content_type value of the field."""
371 return self._content_type
372
373 def __eq__(self, other: object) -> bool:
374 if isinstance(other, Field):
375 return self.field_name == other.field_name and self.value == other.value
376 else:
377 return NotImplemented
378
379 def __repr__(self) -> str:
380 if self.value is not None and len(self.value) > 97:
381 # We get the repr, and then insert three dots before the final
382 # quote.
383 v = repr(self.value[:97])[:-1] + "...'"
384 else:
385 v = repr(self.value)
386
387 return f"{self.__class__.__name__}(field_name={self.field_name!r}, value={v})"
388
389
390class File:
391 """This class represents an uploaded file. It handles writing file data to
392 either an in-memory file or a temporary file on-disk, if the optional
393 threshold is passed.
394
395 There are some options that can be passed to the File to change behavior
396 of the class. Valid options are as follows:
397
398 | Name | Type | Default | Description |
399 |-----------------------|-------|---------|-------------|
400 | UPLOAD_DIR | `str` | None | The directory to store uploaded files in. If this is None, a temporary file will be created in the system's standard location. |
401 | UPLOAD_DELETE_TMP | `bool`| True | Delete automatically created TMP file |
402 | UPLOAD_KEEP_FILENAME | `bool`| False | Whether or not to keep the filename of the uploaded file. If True, then the filename is reduced to its basename (directory components are stripped) and the file is saved with that name. Otherwise, a temporary name will be used. |
403 | UPLOAD_KEEP_EXTENSIONS| `bool`| False | Whether or not to keep the uploaded file's extension. If False, the file will be saved with the default temporary extension (usually ".tmp"). Otherwise, the file's extension will be maintained. Note that this will properly combine with the UPLOAD_KEEP_FILENAME setting. |
404 | MAX_MEMORY_FILE_SIZE | `int` | 1 MiB | The maximum number of bytes of a File to keep in memory. By default, the contents of a File are kept into memory until a certain limit is reached, after which the contents of the File are written to a temporary file. This behavior can be disabled by setting this value to an appropriately large value (or, for example, infinity, such as `float('inf')`. |
405
406 Args:
407 file_name: The name of the file that this [`File`][python_multipart.File] represents.
408 field_name: The name of the form field that this file was uploaded with. This can be None, if, for example,
409 the file was uploaded with Content-Type application/octet-stream.
410 config: The configuration for this File. See above for valid configuration keys and their corresponding values.
411 content_type: The value of the Content-Type header.
412 """ # noqa: E501
413
414 def __init__(
415 self,
416 file_name: bytes | None,
417 field_name: bytes | None = None,
418 config: FileConfig = {},
419 *,
420 content_type: str | None = None,
421 ) -> None:
422 # Save configuration, set other variables default.
423 self.logger = logging.getLogger(__name__)
424 self._config = config
425 self._in_memory = True
426 self._bytes_written = 0
427 self._fileobj: BytesIO | BufferedRandom = BytesIO()
428
429 # Save the provided field/file name and content type.
430 self._field_name = field_name
431 self._file_name = file_name
432 self._content_type = content_type
433
434 # Our actual file name is None by default, since, depending on our
435 # config, we may not actually use the provided name.
436 self._actual_file_name: bytes | None = None
437
438 # Nameless uploads (e.g. `application/octet-stream`) have no base or extension.
439 self._file_base: bytes | None = None
440 self._ext = b""
441 if file_name is not None:
442 # Extract just the basename to avoid directory traversal
443 basename = os.path.basename(file_name)
444 base, ext = os.path.splitext(basename)
445 self._file_base = base
446 self._ext = ext
447
448 @property
449 def field_name(self) -> bytes | None:
450 """The form field associated with this file. May be None if there isn't
451 one, for example when we have an application/octet-stream upload.
452 """
453 return self._field_name
454
455 @property
456 def file_name(self) -> bytes | None:
457 """The file name given in the upload request."""
458 return self._file_name
459
460 @property
461 def actual_file_name(self) -> bytes | None:
462 """The file name that this file is saved as. Will be None if it's not
463 currently saved on disk.
464 """
465 return self._actual_file_name
466
467 @property
468 def file_object(self) -> BytesIO | BufferedRandom:
469 """The file object that we're currently writing to. Note that this
470 will either be an instance of a :class:`io.BytesIO`, or a regular file
471 object.
472 """
473 return self._fileobj
474
475 @property
476 def size(self) -> int:
477 """The total size of this file, counted as the number of bytes that
478 currently have been written to the file.
479 """
480 return self._bytes_written
481
482 @property
483 def in_memory(self) -> bool:
484 """A boolean representing whether or not this file object is currently
485 stored in-memory or on-disk.
486 """
487 return self._in_memory
488
489 @property
490 def content_type(self) -> str | None:
491 """The Content-Type value for this part, if it was set."""
492 return self._content_type
493
494 def flush_to_disk(self) -> None:
495 """If the file is already on-disk, do nothing. Otherwise, copy from
496 the in-memory buffer to a disk file, and then reassign our internal
497 file object to this new disk file.
498
499 Note that if you attempt to flush a file that is already on-disk, a
500 warning will be logged to this module's logger.
501 """
502 if not self._in_memory:
503 self.logger.warning("Trying to flush to disk when we're not in memory")
504 return
505
506 # Go back to the start of our file.
507 self._fileobj.seek(0)
508
509 # Open a new file.
510 new_file = self._get_disk_file()
511
512 # Copy the file objects.
513 shutil.copyfileobj(self._fileobj, new_file)
514
515 # Seek to the new position in our new file.
516 new_file.seek(self._bytes_written)
517
518 # Reassign the fileobject.
519 old_fileobj = self._fileobj
520 self._fileobj = new_file
521
522 # We're no longer in memory.
523 self._in_memory = False
524
525 # Close the old file object.
526 old_fileobj.close()
527
528 def _get_disk_file(self) -> BufferedRandom:
529 """This function is responsible for getting a file object on-disk for us."""
530 self.logger.info("Opening a file on disk")
531
532 file_dir = self._config.get("UPLOAD_DIR")
533 keep_filename = self._config.get("UPLOAD_KEEP_FILENAME", False)
534 keep_extensions = self._config.get("UPLOAD_KEEP_EXTENSIONS", False)
535 delete_tmp = self._config.get("UPLOAD_DELETE_TMP", True)
536 tmp_file: None | BufferedRandom = None
537
538 # If we have a directory and a filename to keep...
539 if file_dir is not None and keep_filename and self._file_base is not None:
540 self.logger.info("Saving with filename in: %r", file_dir)
541
542 # Build our filename.
543 fname = self._file_base + self._ext if keep_extensions else self._file_base
544
545 path = os.path.join(os.fsencode(file_dir), fname)
546 try:
547 self.logger.info("Opening file: %r", path)
548 tmp_file = open(path, "w+b")
549 except OSError:
550 tmp_file = None
551
552 self.logger.exception("Error opening temporary file")
553 raise FileError("Error opening temporary file: %r" % path)
554 else:
555 # Build options array.
556 # Note that on Python 3, tempfile doesn't support byte names. We
557 # encode our paths using the default filesystem encoding.
558 suffix = self._ext.decode(sys.getfilesystemencoding()) if keep_extensions else None
559
560 if file_dir is None:
561 dir = None
562 elif isinstance(file_dir, bytes):
563 dir = file_dir.decode(sys.getfilesystemencoding())
564 else:
565 dir = file_dir # pragma: no cover
566
567 # Create a temporary (named) file with the appropriate settings.
568 self.logger.info(
569 "Creating a temporary file with options: %r", {"suffix": suffix, "delete": delete_tmp, "dir": dir}
570 )
571 try:
572 tmp_file = cast(BufferedRandom, tempfile.NamedTemporaryFile(suffix=suffix, delete=delete_tmp, dir=dir))
573 except OSError:
574 self.logger.exception("Error creating named temporary file")
575 raise FileError("Error creating named temporary file")
576
577 assert tmp_file is not None
578 # Encode filename as bytes.
579 if isinstance(tmp_file.name, str):
580 fname = tmp_file.name.encode(sys.getfilesystemencoding())
581 else:
582 fname = cast(bytes, tmp_file.name) # pragma: no cover
583
584 self._actual_file_name = fname
585 return tmp_file
586
587 def write(self, data: bytes) -> int:
588 """Write some data to the File.
589
590 :param data: a bytestring
591 """
592 return self.on_data(data)
593
594 def on_data(self, data: bytes) -> int:
595 """This method is a callback that will be called whenever data is
596 written to the File.
597
598 Args:
599 data: The data to write to the file.
600
601 Returns:
602 The number of bytes written.
603 """
604 bwritten = self._fileobj.write(data)
605
606 # If the bytes written isn't the same as the length, just return.
607 if bwritten != len(data):
608 self.logger.warning("bwritten != len(data) (%d != %d)", bwritten, len(data))
609 return bwritten
610
611 # Keep track of how many bytes we've written.
612 self._bytes_written += bwritten
613
614 # If we're in-memory and are over our limit, we create a file.
615 max_memory_file_size = self._config.get("MAX_MEMORY_FILE_SIZE")
616 if self._in_memory and max_memory_file_size is not None and (self._bytes_written > max_memory_file_size):
617 self.logger.info("Flushing to disk")
618 self.flush_to_disk()
619
620 # Return the number of bytes written.
621 return bwritten
622
623 def on_end(self) -> None:
624 """This method is called whenever the Field is finalized."""
625 # Flush the underlying file object
626 self._fileobj.flush()
627
628 def finalize(self) -> None:
629 """Finalize the form file. This will not close the underlying file,
630 but simply signal that we are finished writing to the File.
631 """
632 self.on_end()
633
634 def close(self) -> None:
635 """Close the File object. This will actually close the underlying
636 file object (whether it's a :class:`io.BytesIO` or an actual file
637 object).
638 """
639 self._fileobj.close()
640
641 def __repr__(self) -> str:
642 return f"{self.__class__.__name__}(file_name={self.file_name!r}, field_name={self.field_name!r})"
643
644
645class BaseParser:
646 """This class is the base class for all parsers. It contains the logic for
647 calling and adding callbacks.
648
649 A callback can be one of two different forms. "Notification callbacks" are
650 callbacks that are called when something happens - for example, when a new
651 part of a multipart message is encountered by the parser. "Data callbacks"
652 are called when we get some sort of data - for example, part of the body of
653 a multipart chunk. Notification callbacks are called with no parameters,
654 whereas data callbacks are called with three, as follows::
655
656 data_callback(data, start, end)
657
658 The "data" parameter is a bytestring (i.e. "foo" on Python 2, or b"foo" on
659 Python 3). "start" and "end" are integer indexes into the "data" string
660 that represent the data of interest. Thus, in a data callback, the slice
661 `data[start:end]` represents the data that the callback is "interested in".
662 The callback is not passed a copy of the data, since copying severely hurts
663 performance.
664 """
665
666 def __init__(self) -> None:
667 self.logger = logging.getLogger(__name__)
668 self.callbacks: QuerystringCallbacks | OctetStreamCallbacks | MultipartCallbacks = {}
669
670 def callback(
671 self, name: CallbackName, data: bytes | None = None, start: int | None = None, end: int | None = None
672 ) -> None:
673 """This function calls a provided callback with some data. If the
674 callback is not set, will do nothing.
675
676 Args:
677 name: The name of the callback to call (as a string).
678 data: Data to pass to the callback. If None, then it is assumed that the callback is a notification
679 callback, and no parameters are given.
680 end: An integer that is passed to the data callback.
681 start: An integer that is passed to the data callback.
682 """
683 func = self.callbacks.get("on_" + name)
684 if func is None:
685 return
686 func = cast("Callable[..., Any]", func)
687 # Depending on whether we're given a buffer...
688 if data is not None:
689 # Don't do anything if we have start == end.
690 if start is not None and start == end:
691 return
692 func(data, start, end)
693 else:
694 func()
695
696 def set_callback(self, name: CallbackName, new_func: Callable[..., Any] | None) -> None:
697 """Update the function for a callback. Removes from the callbacks dict
698 if new_func is None.
699
700 :param name: The name of the callback to call (as a string).
701
702 :param new_func: The new function for the callback. If None, then the
703 callback will be removed (with no error if it does not
704 exist).
705 """
706 if new_func is None:
707 self.callbacks.pop("on_" + name, None) # type: ignore[misc]
708 else:
709 self.callbacks["on_" + name] = new_func # type: ignore[literal-required]
710
711 def close(self) -> None:
712 pass # pragma: no cover
713
714 def finalize(self) -> None:
715 pass # pragma: no cover
716
717 def __repr__(self) -> str:
718 return "%s()" % self.__class__.__name__
719
720
721class OctetStreamParser(BaseParser):
722 """This parser parses an octet-stream request body and calls callbacks when
723 incoming data is received. Callbacks are as follows:
724
725 | Callback Name | Parameters | Description |
726 |----------------|-----------------|-----------------------------------------------------|
727 | on_start | None | Called when the first data is parsed. |
728 | on_data | data, start, end| Called for each data chunk that is parsed. |
729 | on_end | None | Called when the parser is finished parsing all data.|
730
731 Args:
732 callbacks: A dictionary of callbacks. See the documentation for [`BaseParser`][python_multipart.BaseParser].
733 max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded.
734 """
735
736 def __init__(self, callbacks: OctetStreamCallbacks = {}, max_size: float = float("inf")):
737 super().__init__()
738 self.callbacks = callbacks
739 self._started = False
740
741 if not isinstance(max_size, Number) or max_size < 1:
742 raise ValueError("max_size must be a positive number, not %r" % max_size)
743 self.max_size: int | float = max_size
744 self._current_size = 0
745
746 def write(self, data: bytes) -> int:
747 """Write some data to the parser, which will perform size verification,
748 and then pass the data to the underlying callback.
749
750 Args:
751 data: The data to write to the parser.
752
753 Returns:
754 The number of bytes written.
755 """
756 if not self._started:
757 self.callback("start")
758 self._started = True
759
760 # Truncate data length.
761 data_len = len(data)
762 if (self._current_size + data_len) > self.max_size:
763 # We truncate the length of data that we are to process.
764 new_size = int(self.max_size - self._current_size)
765 self.logger.warning(
766 "Current size is %d (max %d), so truncating data length from %d to %d",
767 self._current_size,
768 self.max_size,
769 data_len,
770 new_size,
771 )
772 data_len = new_size
773
774 # Increment size, then callback, in case there's an exception.
775 self._current_size += data_len
776 self.callback("data", data, 0, data_len)
777 return data_len
778
779 def finalize(self) -> None:
780 """Finalize this parser, which signals to that we are finished parsing,
781 and sends the on_end callback.
782 """
783 self.callback("end")
784
785 def __repr__(self) -> str:
786 return "%s()" % self.__class__.__name__
787
788
789class QuerystringParser(BaseParser):
790 """This is a streaming querystring parser. It will consume data, and call
791 the callbacks given when it has data.
792
793 | Callback Name | Parameters | Description |
794 |----------------|-----------------|-----------------------------------------------------|
795 | on_field_start | None | Called when a new field is encountered. |
796 | on_field_name | data, start, end| Called when a portion of a field's name is encountered. |
797 | on_field_data | data, start, end| Called when a portion of a field's data is encountered. |
798 | on_field_end | None | Called when the end of a field is encountered. |
799 | on_end | None | Called when the parser is finished parsing all data.|
800
801 Args:
802 callbacks: A dictionary of callbacks. See the documentation for [`BaseParser`][python_multipart.BaseParser].
803 strict_parsing: Whether or not to parse the body strictly. Defaults to False. If this is set to True, then the
804 behavior of the parser changes as the following: if a field has a value with an equal sign
805 (e.g. "foo=bar", or "foo="), it is always included. If a field has no equals sign (e.g. "...&name&..."),
806 it will be treated as an error if 'strict_parsing' is True, otherwise included. If an error is encountered,
807 then a [`QuerystringParseError`][python_multipart.exceptions.QuerystringParseError] will be raised.
808 max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded.
809 """ # noqa: E501
810
811 state: QuerystringState
812
813 def __init__(
814 self, callbacks: QuerystringCallbacks = {}, strict_parsing: bool = False, max_size: float = float("inf")
815 ) -> None:
816 super().__init__()
817 self.state = BEFORE_FIELD
818 self._found_sep = False
819
820 self.callbacks = callbacks
821
822 # Max-size stuff
823 if not isinstance(max_size, Number) or max_size < 1:
824 raise ValueError("max_size must be a positive number, not %r" % max_size)
825 self.max_size: int | float = max_size
826 self._current_size = 0
827
828 # Should parsing be strict?
829 self.strict_parsing = strict_parsing
830
831 def write(self, data: bytes) -> int:
832 """Write some data to the parser, which will perform size verification,
833 parse into either a field name or value, and then pass the
834 corresponding data to the underlying callback. If an error is
835 encountered while parsing, a QuerystringParseError will be raised. The
836 "offset" attribute of the raised exception will be set to the offset in
837 the input data chunk (NOT the overall stream) that caused the error.
838
839 Args:
840 data: The data to write to the parser.
841
842 Returns:
843 The number of bytes written.
844 """
845 # Handle sizing.
846 data_len = len(data)
847 if (self._current_size + data_len) > self.max_size:
848 # We truncate the length of data that we are to process.
849 new_size = int(self.max_size - self._current_size)
850 self.logger.warning(
851 "Current size is %d (max %d), so truncating data length from %d to %d",
852 self._current_size,
853 self.max_size,
854 data_len,
855 new_size,
856 )
857 data_len = new_size
858
859 l = 0
860 try:
861 l = self._internal_write(data, data_len)
862 finally:
863 self._current_size += l
864
865 return l
866
867 def _internal_write(self, data: bytes, length: int) -> int:
868 state = self.state
869 strict_parsing = self.strict_parsing
870 found_sep = self._found_sep
871 callbacks = cast("QuerystringCallbacks", self.callbacks)
872 on_field_start = callbacks.get("on_field_start")
873 on_field_name = callbacks.get("on_field_name")
874 on_field_data = callbacks.get("on_field_data")
875 on_field_end = callbacks.get("on_field_end")
876 if on_field_start is None:
877 on_field_start = _noop_event
878 if on_field_name is None:
879 on_field_name = _noop_data
880 if on_field_data is None:
881 on_field_data = _noop_data
882 if on_field_end is None:
883 on_field_end = _noop_event
884
885 i = 0
886 while i < length:
887 ch = data[i]
888
889 # Depending on our state...
890 if state == BEFORE_FIELD:
891 # If the 'found_sep' flag is set, we've already encountered
892 # and skipped a single separator. If so, we check our strict
893 # parsing flag and decide what to do. Otherwise, we haven't
894 # yet reached a separator, and thus, if we do, we need to skip
895 # it as it will be the boundary between fields that's supposed
896 # to be there.
897 if ch == AMPERSAND:
898 if found_sep:
899 # If we're parsing strictly, we disallow blank chunks.
900 if strict_parsing:
901 raise QuerystringParseError("Skipping duplicate ampersand at %d" % i, offset=i)
902 else:
903 self.logger.debug("Skipping duplicate ampersand at %d", i)
904 else:
905 # This case is when we're skipping the (first)
906 # separator between fields, so we just set our flag
907 # and continue on.
908 found_sep = True
909 else:
910 # Emit a field-start event, and go to that state. Also,
911 # reset the "found_sep" flag, for the next time we get to
912 # this state.
913 on_field_start()
914 i -= 1
915 state = FIELD_NAME
916 found_sep = False
917
918 elif state == FIELD_NAME:
919 # Try and find a separator - we ensure that, if we do, we only
920 # look for the equal sign before it.
921 sep_pos = data.find(b"&", i, length)
922
923 # See if we can find an equals sign in the remaining data. If
924 # so, we can immediately emit the field name and jump to the
925 # data state.
926 if sep_pos != -1:
927 equals_pos = data.find(b"=", i, sep_pos)
928 else:
929 equals_pos = data.find(b"=", i, length)
930
931 if equals_pos != -1:
932 # Emit this name.
933 if i != equals_pos:
934 on_field_name(data, i, equals_pos)
935
936 # Jump i to this position. Note that it will then have 1
937 # added to it below, which means the next iteration of this
938 # loop will inspect the character after the equals sign.
939 i = equals_pos
940 state = FIELD_DATA
941 else:
942 # No equals sign found.
943 if not strict_parsing:
944 # See also comments in the QuerystringState.FIELD_DATA case below.
945 # If we found the separator, we emit the name and just
946 # end - there's no data callback at all (not even with
947 # a blank value).
948 if sep_pos != -1:
949 if i != sep_pos:
950 on_field_name(data, i, sep_pos)
951 on_field_end()
952
953 i = sep_pos - 1
954 state = BEFORE_FIELD
955 else:
956 # Otherwise, no separator in this block, so the
957 # rest of this chunk must be a name.
958 if i != length:
959 on_field_name(data, i, length)
960 i = length
961
962 else:
963 # We're parsing strictly. If we find a separator,
964 # this is an error - we require an equals sign.
965 if sep_pos != -1:
966 raise QuerystringParseError(
967 "When strict_parsing is True, we require an "
968 "equals sign in all field chunks. Did not "
969 "find one in the chunk that starts at %d" % (i,),
970 offset=i,
971 )
972
973 # No separator in the rest of this chunk, so it's just
974 # a field name.
975 if i != length:
976 on_field_name(data, i, length)
977 i = length
978
979 elif state == FIELD_DATA:
980 # Try finding an ampersand after this position.
981 sep_pos = data.find(b"&", i, length)
982
983 # If we found it, callback this bit as data and then go back
984 # to expecting to find a field.
985 if sep_pos != -1:
986 if i != sep_pos:
987 on_field_data(data, i, sep_pos)
988 on_field_end()
989
990 # Note that we go to the separator, which brings us to the
991 # "before field" state. This allows us to properly emit
992 # "field_start" events only when we actually have data for
993 # a field of some sort.
994 i = sep_pos - 1
995 state = BEFORE_FIELD
996
997 # Otherwise, emit the rest as data and finish.
998 else:
999 if i != length:
1000 on_field_data(data, i, length)
1001 i = length
1002
1003 else: # pragma: no cover (error case)
1004 msg = "Reached an unknown state %d at %d" % (state, i)
1005 self.logger.warning(msg)
1006 raise QuerystringParseError(msg, offset=i)
1007
1008 i += 1
1009
1010 self.state = state
1011 self._found_sep = found_sep
1012 return length
1013
1014 def finalize(self) -> None:
1015 """Finalize this parser, which signals to that we are finished parsing,
1016 if we're still in the middle of a field, an on_field_end callback, and
1017 then the on_end callback.
1018 """
1019 callbacks = cast("QuerystringCallbacks", self.callbacks)
1020 # If we're currently in the middle of a field, we finish it.
1021 if self.state in (FIELD_DATA, FIELD_NAME):
1022 on_field_end = callbacks.get("on_field_end")
1023 if on_field_end is None:
1024 on_field_end = _noop_event
1025 on_field_end()
1026 on_end = callbacks.get("on_end")
1027 if on_end is None:
1028 on_end = _noop_event
1029 on_end()
1030
1031 def __repr__(self) -> str:
1032 return "{}(strict_parsing={!r}, max_size={!r})".format(
1033 self.__class__.__name__, self.strict_parsing, self.max_size
1034 )
1035
1036
1037class MultipartParser(BaseParser):
1038 """This class is a streaming multipart/form-data parser.
1039
1040 | Callback Name | Parameters | Description |
1041 |--------------------|-----------------|-------------|
1042 | on_part_begin | None | Called when a new part of the multipart message is encountered. |
1043 | on_part_data | data, start, end| Called when a portion of a part's data is encountered. |
1044 | on_part_end | None | Called when the end of a part is reached. |
1045 | on_header_begin | None | Called when we've found a new header in a part of a multipart message |
1046 | on_header_field | data, start, end| Called each time an additional portion of a header is read (i.e. the part of the header that is before the colon; the "Foo" in "Foo: Bar"). |
1047 | on_header_value | data, start, end| Called when we get data for a header. |
1048 | on_header_end | None | Called when the current header is finished - i.e. we've reached the newline at the end of the header. |
1049 | on_headers_finished| None | Called when all headers are finished, and before the part data starts. |
1050 | on_end | None | Called when the parser is finished parsing all data. |
1051
1052 Args:
1053 boundary: The multipart boundary. This is required, and must match what is given in the HTTP request - usually in the Content-Type header.
1054 callbacks: A dictionary of callbacks. See the documentation for [`BaseParser`][python_multipart.BaseParser].
1055 max_size: The maximum size of body to parse. Defaults to infinity - i.e. unbounded.
1056 max_header_count: The maximum number of headers allowed per part.
1057 max_header_size: The maximum size of a single header line (excluding the trailing CRLF).
1058 """ # noqa: E501
1059
1060 def __init__(
1061 self,
1062 boundary: bytes | str,
1063 callbacks: MultipartCallbacks = {},
1064 max_size: float = float("inf"),
1065 *,
1066 max_header_count: int = DEFAULT_MAX_HEADER_COUNT,
1067 max_header_size: int = DEFAULT_MAX_HEADER_SIZE,
1068 ) -> None:
1069 # Initialize parser state.
1070 super().__init__()
1071 self.state = START
1072 self.index = self.flags = 0
1073
1074 self.callbacks = callbacks
1075
1076 if not isinstance(max_size, Number) or max_size < 1:
1077 raise ValueError("max_size must be a positive number, not %r" % max_size)
1078 self.max_size = max_size
1079 self._current_size = 0
1080
1081 self.max_header_count = max_header_count
1082 self._current_header_count = 0
1083
1084 self.max_header_size = max_header_size
1085 self._current_header_size = 0
1086
1087 # Setup marks. These are used to track the state of data received.
1088 self.marks: dict[str, int] = {}
1089
1090 # Save our boundary.
1091 if isinstance(boundary, str): # pragma: no cover
1092 boundary = boundary.encode("latin-1")
1093 if len(boundary) > MAX_BOUNDARY_LENGTH:
1094 raise FormParserError(f"Boundary length {len(boundary)} exceeds maximum of {MAX_BOUNDARY_LENGTH}")
1095 self.boundary = b"\r\n--" + boundary
1096
1097 def write(self, data: bytes) -> int:
1098 """Write some data to the parser, which will perform size verification,
1099 and then parse the data into the appropriate location (e.g. header,
1100 data, etc.), and pass this on to the underlying callback. If an error
1101 is encountered, a MultipartParseError will be raised. The "offset"
1102 attribute on the raised exception will be set to the offset of the byte
1103 in the input chunk that caused the error.
1104
1105 Args:
1106 data: The data to write to the parser.
1107
1108 Returns:
1109 The number of bytes written.
1110 """
1111 # Handle sizing.
1112 data_len = len(data)
1113 if (self._current_size + data_len) > self.max_size:
1114 # We truncate the length of data that we are to process.
1115 new_size = int(self.max_size - self._current_size)
1116 self.logger.warning(
1117 "Current size is %d (max %d), so truncating data length from %d to %d",
1118 self._current_size,
1119 self.max_size,
1120 data_len,
1121 new_size,
1122 )
1123 data_len = new_size
1124
1125 l = 0
1126 try:
1127 l = self._internal_write(data, data_len)
1128 finally:
1129 self._current_size += l
1130
1131 return l
1132
1133 def _internal_write(self, data: bytes, length: int) -> int:
1134 # Get values from locals.
1135 boundary = self.boundary
1136 boundary_length = len(boundary)
1137
1138 # Get our state, flags and index. These are persisted between calls to
1139 # this function.
1140 state = self.state
1141 index = self.index
1142 flags = self.flags
1143 current_header_count = self._current_header_count
1144 current_header_size = self._current_header_size
1145
1146 # Our index defaults to 0.
1147 i = 0
1148
1149 def advance_header_size(amount: int = 1) -> None:
1150 nonlocal current_header_size
1151 current_header_size += amount
1152 if current_header_size > self.max_header_size:
1153 raise MultipartParseError("Maximum header size exceeded", offset=i)
1154
1155 # Set a mark.
1156 def set_mark(name: str) -> None:
1157 self.marks[name] = i
1158
1159 # Remove a mark.
1160 def delete_mark(name: str, reset: bool = False) -> None:
1161 self.marks.pop(name, None)
1162
1163 # Helper function that makes calling a callback with data easier. The
1164 # 'remaining' parameter will callback from the marked value until the
1165 # end of the buffer, and reset the mark, instead of deleting it. This
1166 # is used at the end of the function to call our callbacks with any
1167 # remaining data in this chunk.
1168 def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> None:
1169 marked_index = self.marks.get(name)
1170 if marked_index is None:
1171 return
1172
1173 # Otherwise, we call it from the mark to the current byte we're
1174 # processing.
1175 if end_i <= marked_index:
1176 # There is no additional data to send.
1177 pass
1178 elif marked_index >= 0:
1179 # We are emitting data from the local buffer.
1180 self.callback(name, data, marked_index, end_i)
1181 else:
1182 # Some of the data comes from a partial boundary match.
1183 # and requires look-behind.
1184 # We need to use self.flags (and not flags) because we care about
1185 # the state when we entered the loop.
1186 lookbehind_len = -marked_index
1187 if lookbehind_len <= boundary_length:
1188 self.callback(name, boundary, 0, lookbehind_len)
1189 elif self.flags & FLAG_PART_BOUNDARY:
1190 lookback = boundary + b"\r\n"
1191 self.callback(name, lookback, 0, lookbehind_len)
1192 elif self.flags & FLAG_LAST_BOUNDARY:
1193 lookback = boundary + b"--\r\n"
1194 self.callback(name, lookback, 0, lookbehind_len)
1195 else: # pragma: no cover (error case)
1196 self.logger.warning("Look-back buffer error")
1197
1198 if end_i > 0:
1199 self.callback(name, data, 0, end_i)
1200 # If we're getting remaining data, we have got all the data we
1201 # can be certain is not a boundary, leaving only a partial boundary match.
1202 if remaining:
1203 self.marks[name] = end_i - length
1204 else:
1205 self.marks.pop(name, None)
1206
1207 # For each byte...
1208 while i < length:
1209 c = data[i]
1210
1211 if state == START:
1212 # Skip leading newlines
1213 if c == CR or c == LF:
1214 i = data.find(b"-", i)
1215 if i == -1:
1216 # No boundary candidate in this chunk, so ignore the content after the leading CR/LF.
1217 i = length
1218 break
1219 continue
1220
1221 # index is used as in index into our boundary. Set to 0.
1222 index = 0
1223
1224 # Move to the next state, but decrement i so that we re-process
1225 # this character.
1226 state = START_BOUNDARY
1227 i -= 1
1228
1229 elif state == START_BOUNDARY:
1230 if index == 0 and data.startswith(boundary[2:], i, length):
1231 index = boundary_length - 2
1232 i += index
1233 continue
1234
1235 # Check to ensure that the last 2 characters in our boundary
1236 # are CRLF.
1237 if index == boundary_length - 2:
1238 if c == HYPHEN:
1239 # Potential empty message.
1240 state = END_BOUNDARY
1241 elif c != CR:
1242 # Error!
1243 msg = "Did not find CR at end of boundary (%d)" % (i,)
1244 self.logger.warning(msg)
1245 raise MultipartParseError(msg, offset=i)
1246
1247 index += 1
1248
1249 elif index == boundary_length - 1:
1250 if c != LF:
1251 msg = "Did not find LF at end of boundary (%d)" % (i,)
1252 self.logger.warning(msg)
1253 raise MultipartParseError(msg, offset=i)
1254
1255 # The index is now used for indexing into our boundary.
1256 index = 0
1257
1258 # Callback for the start of a part.
1259 self.callback("part_begin")
1260 current_header_count = 0
1261 current_header_size = 0
1262
1263 # Move to the next character and state.
1264 state = HEADER_FIELD_START
1265
1266 else:
1267 # Check to ensure our boundary matches
1268 if c != boundary[index + 2]:
1269 msg = "Expected boundary character %r, got %r at index %d" % (boundary[index + 2], c, index + 2)
1270 self.logger.warning(msg)
1271 raise MultipartParseError(msg, offset=i)
1272
1273 # Increment index into boundary and continue.
1274 index += 1
1275
1276 elif state == HEADER_FIELD_START:
1277 # Mark the start of a header field here, reset the index, and
1278 # continue parsing our header field.
1279 index = 0
1280
1281 if c != CR:
1282 current_header_count += 1
1283 if current_header_count > self.max_header_count:
1284 raise MultipartParseError("Maximum header count exceeded", offset=i)
1285 current_header_size = 0
1286
1287 # Set a mark of our header field.
1288 set_mark("header_field")
1289
1290 # Notify that we're starting a header if the next character is
1291 # not a CR; a CR at the beginning of the header will cause us
1292 # to stop parsing headers in the MultipartState.HEADER_FIELD state,
1293 # below.
1294 if c != CR:
1295 self.callback("header_begin")
1296
1297 # Move to parsing header fields.
1298 state = HEADER_FIELD
1299 i -= 1
1300
1301 elif state == HEADER_FIELD:
1302 # If we've reached a CR at the beginning of a header, it means
1303 # that we've reached the second of 2 newlines, and so there are
1304 # no more headers to parse.
1305 if c == CR and index == 0:
1306 delete_mark("header_field")
1307 state = HEADERS_ALMOST_DONE
1308 i += 1
1309 continue
1310
1311 # The field name runs until the colon; jump straight to it and
1312 # validate the whole span at once instead of byte by byte.
1313 colon = data.find(b":", i, length)
1314 end = colon if colon != -1 else length
1315
1316 # Enforce the size limit before slicing and validating, so an oversized header
1317 # name fails fast instead of copying and scanning a potentially huge span.
1318 advance_header_size(end - i if colon == -1 else end - i + 1)
1319
1320 field = data[i:end]
1321 if field.translate(None, TOKEN_CHARS):
1322 bad = next(b for b in field if b not in TOKEN_CHARS_SET)
1323 bad_i = i + field.index(bad)
1324 msg = "Found invalid character %r in header at %d" % (bad, bad_i)
1325 self.logger.warning(msg)
1326 raise MultipartParseError(msg, offset=bad_i)
1327
1328 index += end - i
1329 if colon == -1:
1330 # Field name continues into the next chunk.
1331 i = length
1332 else:
1333 # A 0-length header is an error.
1334 if index == 0:
1335 msg = "Found 0-length header at %d" % (i,)
1336 self.logger.warning(msg)
1337 raise MultipartParseError(msg, offset=i)
1338
1339 # Call our callback with the header field.
1340 i = colon
1341 data_callback("header_field", i)
1342
1343 # Move to parsing the header value.
1344 state = HEADER_VALUE_START
1345
1346 elif state == HEADER_VALUE_START:
1347 # Skip leading spaces.
1348 if c == SPACE:
1349 advance_header_size()
1350 i += 1
1351 continue
1352
1353 # Mark the start of the header value.
1354 set_mark("header_value")
1355
1356 # Move to the header-value state, reprocessing this character.
1357 state = HEADER_VALUE
1358 i -= 1
1359
1360 elif state == HEADER_VALUE:
1361 # The value runs until the terminating CR; jump straight to it
1362 # instead of inspecting every byte.
1363 cr = data.find(b"\r", i, length)
1364 end = cr if cr != -1 else length
1365 advance_header_size(end - i)
1366 if cr != -1:
1367 i = cr
1368 data_callback("header_value", i)
1369 self.callback("header_end")
1370 current_header_size = 0
1371 state = HEADER_VALUE_ALMOST_DONE
1372 else:
1373 i = length
1374
1375 elif state == HEADER_VALUE_ALMOST_DONE:
1376 # The last character should be a LF. If not, it's an error.
1377 if c != LF:
1378 msg = f"Did not find LF character at end of header (found {c!r})"
1379 self.logger.warning(msg)
1380 raise MultipartParseError(msg, offset=i)
1381
1382 # Move back to the start of another header. Note that if that
1383 # state detects ANOTHER newline, it'll trigger the end of our
1384 # headers.
1385 state = HEADER_FIELD_START
1386
1387 elif state == HEADERS_ALMOST_DONE:
1388 # We're almost done our headers. This is reached when we parse
1389 # a CR at the beginning of a header, so our next character
1390 # should be a LF, or it's an error.
1391 if c != LF:
1392 msg = f"Did not find LF at end of headers (found {c!r})"
1393 self.logger.warning(msg)
1394 raise MultipartParseError(msg, offset=i)
1395
1396 self.callback("headers_finished")
1397 state = PART_DATA_START
1398
1399 elif state == PART_DATA_START:
1400 # Mark the start of our part data.
1401 set_mark("part_data")
1402
1403 # Start processing part data, including this character.
1404 state = PART_DATA
1405 i -= 1
1406
1407 elif state == PART_DATA:
1408 # We're processing our part data right now. During this, we
1409 # need to efficiently search for our boundary, since any data
1410 # on any number of lines can be a part of the current data.
1411
1412 # Save the current value of our index. We use this in case we
1413 # find part of a boundary, but it doesn't match fully.
1414 prev_index = index
1415
1416 # If our index is 0, we're starting a new part, so start our
1417 # search.
1418 if index == 0:
1419 # The most common case is likely to be that the whole
1420 # boundary is present in the buffer.
1421 # Calling `find` is much faster than iterating here.
1422 i0 = data.find(boundary, i, length)
1423 if i0 >= 0:
1424 # We matched the whole boundary string.
1425 index = boundary_length - 1
1426 i = i0 + boundary_length - 1
1427 c = data[i]
1428 else:
1429 # No whole boundary, but the tail may hold a partial one
1430 # that completes in the next chunk. Boundary starts with
1431 # CR, which an RFC boundary contains nowhere else, so the
1432 # last CR in the tail is the only candidate prefix start.
1433 k = data.rfind(boundary[:1], max(i, length - boundary_length + 1), length)
1434 if k != -1 and boundary.startswith(data[k:length]):
1435 index = length - k
1436 # Carry the partial via index; the end-of-chunk flush
1437 # emits the data before it and re-marks the lookbehind.
1438 i = length
1439 continue
1440
1441 # Now, we have a couple of cases here. If our index is before
1442 # the end of the boundary...
1443 if index < boundary_length:
1444 # If the character matches...
1445 if boundary[index] == c:
1446 # The current character matches, so continue!
1447 index += 1
1448 else:
1449 index = 0
1450
1451 # Our index is equal to the length of our boundary!
1452 elif index == boundary_length:
1453 # First we increment it.
1454 index += 1
1455
1456 # Now, if we've reached a newline, we need to set this as
1457 # the potential end of our boundary.
1458 if c == CR:
1459 flags |= FLAG_PART_BOUNDARY
1460
1461 # Otherwise, if this is a hyphen, we might be at the last
1462 # of all boundaries.
1463 elif c == HYPHEN:
1464 flags |= FLAG_LAST_BOUNDARY
1465
1466 # Otherwise, we reset our index, since this isn't either a
1467 # newline or a hyphen.
1468 else:
1469 index = 0
1470
1471 # Our index is right after the part boundary, which should be
1472 # a LF.
1473 elif index == boundary_length + 1:
1474 # If we're at a part boundary (i.e. we've seen a CR
1475 # character already)...
1476 if flags & FLAG_PART_BOUNDARY:
1477 # We need a LF character next.
1478 if c == LF:
1479 # Unset the part boundary flag.
1480 flags &= ~FLAG_PART_BOUNDARY
1481
1482 # We have identified a boundary, callback for any data before it.
1483 data_callback("part_data", i - index)
1484 # Callback indicating that we've reached the end of
1485 # a part, and are starting a new one.
1486 self.callback("part_end")
1487 self.callback("part_begin")
1488 current_header_count = 0
1489 current_header_size = 0
1490
1491 # Move to parsing new headers.
1492 index = 0
1493 state = HEADER_FIELD_START
1494 i += 1
1495 continue
1496
1497 # We didn't find an LF character, so no match. Reset
1498 # our index and clear our flag.
1499 index = 0
1500 flags &= ~FLAG_PART_BOUNDARY
1501
1502 # Otherwise, if we're at the last boundary (i.e. we've
1503 # seen a hyphen already)...
1504 elif flags & FLAG_LAST_BOUNDARY:
1505 # We need a second hyphen here.
1506 if c == HYPHEN:
1507 # We have identified a boundary, callback for any data before it.
1508 data_callback("part_data", i - index)
1509 # Callback to end the current part, and then the
1510 # message.
1511 self.callback("part_end")
1512 self.callback("end")
1513 state = END
1514 else:
1515 # No match, so reset index.
1516 index = 0
1517
1518 # Otherwise, our index is 0. If the previous index is not, it
1519 # means we reset something, and we need to take the data we
1520 # thought was part of our boundary and send it along as actual
1521 # data.
1522 if index == 0 and prev_index > 0:
1523 # Overwrite our previous index.
1524 prev_index = 0
1525
1526 # Re-consider the current character, since this could be
1527 # the start of the boundary itself.
1528 i -= 1
1529
1530 elif state == END_BOUNDARY:
1531 if index == boundary_length - 1:
1532 if c != HYPHEN:
1533 msg = "Did not find - at end of boundary (%d)" % (i,)
1534 self.logger.warning(msg)
1535 raise MultipartParseError(msg, offset=i)
1536 index += 1
1537 self.callback("end")
1538 state = END
1539
1540 elif state == END:
1541 # Silently discard any epilogue data (RFC 2046 section 5.1.1 allows a CRLF and optional
1542 # epilogue after the closing boundary). Django and Werkzeug do the same.
1543 i = length
1544 break
1545
1546 else: # pragma: no cover (error case)
1547 # We got into a strange state somehow! Just stop processing.
1548 msg = "Reached an unknown state %d at %d" % (state, i)
1549 self.logger.warning(msg)
1550 raise MultipartParseError(msg, offset=i)
1551
1552 # Move to the next byte.
1553 i += 1
1554
1555 # We call our callbacks with any remaining data. Note that we pass
1556 # the 'remaining' flag, which sets the mark back to 0 instead of
1557 # deleting it, if it's found. This is because, if the mark is found
1558 # at this point, we assume that there's data for one of these things
1559 # that has been parsed, but not yet emitted. And, as such, it implies
1560 # that we haven't yet reached the end of this 'thing'. So, by setting
1561 # the mark to 0, we cause any data callbacks that take place in future
1562 # calls to this function to start from the beginning of that buffer.
1563 data_callback("header_field", length, True)
1564 data_callback("header_value", length, True)
1565 data_callback("part_data", length - index, True)
1566
1567 # Save values to locals.
1568 self.state = state
1569 self.index = index
1570 self.flags = flags
1571 self._current_header_count = current_header_count
1572 self._current_header_size = current_header_size
1573
1574 # Return our data length to indicate no errors, and that we processed
1575 # all of it.
1576 return length
1577
1578 def finalize(self) -> None:
1579 """Finalize this parser, which signals to that we are finished parsing.
1580
1581 Note: It does not currently, but in the future, it will verify that we
1582 are in the final state of the parser (i.e. the end of the multipart
1583 message is well-formed), and, if not, throw an error.
1584 """
1585 # TODO: verify that we're in the state MultipartState.END, otherwise throw an
1586 # error or otherwise state that we're not finished parsing.
1587 pass
1588
1589 def __repr__(self) -> str:
1590 return f"{self.__class__.__name__}(boundary={self.boundary!r})"
1591
1592
1593class FormParser:
1594 """This class is the all-in-one form parser. Given all the information
1595 necessary to parse a form, it will instantiate the correct parser, create
1596 the proper :class:`Field` and :class:`File` classes to store the data that
1597 is parsed, and call the two given callbacks with each field and file as
1598 they become available.
1599
1600 Args:
1601 content_type: The Content-Type of the incoming request. This is used to select the appropriate parser.
1602 on_field: The callback to call when a field has been parsed and is ready for usage. See above for parameters.
1603 on_file: The callback to call when a file has been parsed and is ready for usage. See above for parameters.
1604 on_end: An optional callback to call when all fields and files in a request has been parsed. Can be None.
1605 boundary: If the request is a multipart/form-data request, this should be the boundary of the request, as given
1606 in the Content-Type header, as a bytestring.
1607 file_name: If the request is of type application/octet-stream, then the body of the request will not contain any
1608 information about the uploaded file. In such cases, you can provide the file name of the uploaded file
1609 manually.
1610 config: Configuration to use for this FormParser. The default values are taken from the DEFAULT_CONFIG value,
1611 and then any keys present in this dictionary will overwrite the default values.
1612 """
1613
1614 #: This is the default configuration for our form parser.
1615 #: Note: all file sizes should be in bytes.
1616 DEFAULT_CONFIG: FormParserConfig = {
1617 "MAX_BODY_SIZE": float("inf"),
1618 "MAX_FIELDS": float("inf"),
1619 "MAX_FIELD_SIZE": float("inf"),
1620 "MAX_HEADER_COUNT": DEFAULT_MAX_HEADER_COUNT,
1621 "MAX_HEADER_SIZE": DEFAULT_MAX_HEADER_SIZE,
1622 "MAX_MEMORY_FILE_SIZE": 1 * 1024 * 1024,
1623 "UPLOAD_DIR": None,
1624 "UPLOAD_DELETE_TMP": True,
1625 "UPLOAD_KEEP_FILENAME": False,
1626 "UPLOAD_KEEP_EXTENSIONS": False,
1627 # Error on invalid Content-Transfer-Encoding?
1628 "UPLOAD_ERROR_ON_BAD_CTE": False,
1629 }
1630
1631 def __init__(
1632 self,
1633 content_type: str,
1634 on_field: Callable[[Field], None] | None,
1635 on_file: Callable[[File], None] | None,
1636 on_end: Callable[[], None] | None = None,
1637 boundary: bytes | str | None = None,
1638 file_name: bytes | None = None,
1639 config: dict[Any, Any] = {},
1640 ) -> None:
1641 self.logger = logging.getLogger(__name__)
1642
1643 # Save variables.
1644 self.content_type = content_type
1645 self.boundary = boundary
1646 self.bytes_received = 0
1647 self.parser = None
1648
1649 # Save callbacks.
1650 self.on_field = on_field
1651 self.on_file = on_file
1652 self.on_end = on_end
1653
1654 # Set configuration options.
1655 self.config: FormParserConfig = self.DEFAULT_CONFIG.copy()
1656 self.config.update(config) # type: ignore[typeddict-item]
1657
1658 parser: OctetStreamParser | MultipartParser | QuerystringParser | None = None
1659
1660 # Depending on the Content-Type, we instantiate the correct parser.
1661 if content_type == "application/octet-stream":
1662 file: File | None = None
1663
1664 def on_start() -> None:
1665 nonlocal file
1666 file = File(file_name, None, config=self.config)
1667
1668 def on_data(data: bytes, start: int, end: int) -> None:
1669 nonlocal file
1670 assert file is not None
1671 file.write(data[start:end])
1672
1673 def _on_end() -> None:
1674 nonlocal file
1675 assert file is not None
1676 # Finalize the file itself.
1677 file.finalize()
1678
1679 # Call our callback.
1680 if on_file:
1681 on_file(file)
1682
1683 # Call the on-end callback.
1684 if self.on_end is not None:
1685 self.on_end()
1686
1687 # Instantiate an octet-stream parser
1688 parser = OctetStreamParser(
1689 callbacks={"on_start": on_start, "on_data": on_data, "on_end": _on_end},
1690 max_size=self.config["MAX_BODY_SIZE"],
1691 )
1692
1693 elif content_type == "application/x-www-form-urlencoded" or content_type == "application/x-url-encoded":
1694 max_fields = self.config["MAX_FIELDS"]
1695 max_field_size = self.config["MAX_FIELD_SIZE"]
1696 for key, limit in (("MAX_FIELDS", max_fields), ("MAX_FIELD_SIZE", max_field_size)):
1697 if not isinstance(limit, (int, float)) or not limit >= 0:
1698 raise ValueError(f"{key} must be a non-negative number, not {limit!r}")
1699
1700 name_buffer: list[bytes] = []
1701 field_count = 0
1702 field_size = 0
1703 f: Field | None = None
1704
1705 def on_field_start() -> None:
1706 nonlocal field_count, field_size
1707 field_count += 1
1708 if field_count > max_fields:
1709 raise FormParserError(f"Too many fields. Maximum number of fields is {max_fields}.")
1710 field_size = 0
1711
1712 def on_field_name(data: bytes, start: int, end: int) -> None:
1713 nonlocal field_size
1714 field_size += end - start
1715 if field_size > max_field_size:
1716 raise FormParserError(f"Field exceeded maximum size of {max_field_size} bytes.")
1717 name_buffer.append(data[start:end])
1718
1719 def on_field_data(data: bytes, start: int, end: int) -> None:
1720 nonlocal f, field_size
1721 field_size += end - start
1722 if field_size > max_field_size:
1723 raise FormParserError(f"Field exceeded maximum size of {max_field_size} bytes.")
1724 if f is None:
1725 f = Field(b"".join(name_buffer))
1726 del name_buffer[:]
1727 f.write(data[start:end])
1728
1729 def on_field_end() -> None:
1730 nonlocal f
1731 # Finalize and call callback.
1732 if f is None:
1733 # If we get here, it's because there was no field data.
1734 # We create a field, set it to None, and then continue.
1735 f = Field(b"".join(name_buffer))
1736 del name_buffer[:]
1737 f.set_none()
1738
1739 f.finalize()
1740 if on_field:
1741 on_field(f)
1742 f = None
1743
1744 def _on_end() -> None:
1745 if self.on_end is not None:
1746 self.on_end()
1747
1748 # Instantiate parser.
1749 parser = QuerystringParser(
1750 callbacks={
1751 "on_field_start": on_field_start,
1752 "on_field_name": on_field_name,
1753 "on_field_data": on_field_data,
1754 "on_field_end": on_field_end,
1755 "on_end": _on_end,
1756 },
1757 max_size=self.config["MAX_BODY_SIZE"],
1758 )
1759
1760 elif content_type == "multipart/form-data":
1761 if boundary is None:
1762 self.logger.error("No boundary given")
1763 raise FormParserError("No boundary given")
1764
1765 header_name: list[bytes] = []
1766 header_value: list[bytes] = []
1767 headers: dict[bytes, bytes] = {}
1768
1769 f_multi: File | Field | None = None
1770 writer: File | Field | Base64Decoder | QuotedPrintableDecoder | None = None
1771 is_file = False
1772
1773 def on_part_begin() -> None:
1774 # Reset headers in case this isn't the first part.
1775 nonlocal headers
1776 headers = {}
1777
1778 def on_part_data(data: bytes, start: int, end: int) -> None:
1779 nonlocal writer
1780 assert writer is not None
1781 writer.write(data[start:end])
1782 # TODO: check for error here.
1783
1784 def on_part_end() -> None:
1785 assert f_multi is not None
1786 assert writer is not None
1787 writer.finalize()
1788 if is_file:
1789 if on_file:
1790 assert isinstance(f_multi, File)
1791 on_file(f_multi)
1792 else:
1793 if on_field:
1794 assert isinstance(f_multi, Field)
1795 on_field(f_multi)
1796
1797 def on_header_field(data: bytes, start: int, end: int) -> None:
1798 header_name.append(data[start:end])
1799
1800 def on_header_value(data: bytes, start: int, end: int) -> None:
1801 header_value.append(data[start:end])
1802
1803 def on_header_end() -> None:
1804 headers[b"".join(header_name).lower()] = b"".join(header_value)
1805 del header_name[:]
1806 del header_value[:]
1807
1808 def on_headers_finished() -> None:
1809 nonlocal is_file, f_multi, writer
1810 # Reset the 'is file' flag.
1811 is_file = False
1812
1813 # Parse the content-disposition header.
1814 content_disp = headers.get(b"content-disposition")
1815 disp, options = parse_options_header(content_disp)
1816
1817 # Get the field and filename.
1818 field_name = options.get(b"name")
1819 file_name = options.get(b"filename")
1820 # RFC 7578 §4.2: each part MUST have a Content-Disposition header with a "name" parameter.
1821 if field_name is None:
1822 raise FormParserError(f'Field name not found in Content-Disposition: "{content_disp!r}"')
1823
1824 # Create the proper class.
1825 content_type_b = headers.get(b"content-type")
1826 content_type = content_type_b.decode("latin-1") if content_type_b is not None else None
1827 if file_name is None:
1828 f_multi = Field(field_name, content_type=content_type)
1829 else:
1830 f_multi = File(file_name, field_name, config=self.config, content_type=content_type)
1831 is_file = True
1832
1833 # Parse the given Content-Transfer-Encoding to determine what
1834 # we need to do with the incoming data.
1835 # TODO: check that we properly handle 8bit / 7bit encoding.
1836 # RFC 2045 section 6.1: Content-Transfer-Encoding values are case-insensitive.
1837 # https://www.rfc-editor.org/rfc/rfc2045#section-6.1
1838 transfer_encoding = headers.get(b"content-transfer-encoding", b"7bit").lower()
1839
1840 if transfer_encoding in (b"binary", b"8bit", b"7bit"):
1841 writer = f_multi
1842
1843 elif transfer_encoding == b"base64":
1844 writer = Base64Decoder(f_multi)
1845
1846 elif transfer_encoding == b"quoted-printable":
1847 writer = QuotedPrintableDecoder(f_multi)
1848
1849 else:
1850 self.logger.warning("Unknown Content-Transfer-Encoding: %r", transfer_encoding)
1851 if self.config["UPLOAD_ERROR_ON_BAD_CTE"]:
1852 raise FormParserError(f'Unknown Content-Transfer-Encoding "{transfer_encoding!r}"')
1853 else:
1854 # If we aren't erroring, then we just treat this as an
1855 # unencoded Content-Transfer-Encoding.
1856 writer = f_multi
1857
1858 def _on_end() -> None:
1859 if self.on_end is not None:
1860 self.on_end()
1861
1862 # Instantiate a multipart parser.
1863 parser = MultipartParser(
1864 boundary,
1865 callbacks={
1866 "on_part_begin": on_part_begin,
1867 "on_part_data": on_part_data,
1868 "on_part_end": on_part_end,
1869 "on_header_field": on_header_field,
1870 "on_header_value": on_header_value,
1871 "on_header_end": on_header_end,
1872 "on_headers_finished": on_headers_finished,
1873 "on_end": _on_end,
1874 },
1875 max_size=self.config["MAX_BODY_SIZE"],
1876 max_header_count=self.config["MAX_HEADER_COUNT"],
1877 max_header_size=self.config["MAX_HEADER_SIZE"],
1878 )
1879
1880 else:
1881 self.logger.warning("Unknown Content-Type: %r", content_type)
1882 raise FormParserError(f"Unknown Content-Type: {content_type}")
1883
1884 self.parser = parser
1885
1886 def write(self, data: bytes) -> int:
1887 """Write some data. The parser will forward this to the appropriate
1888 underlying parser.
1889
1890 Args:
1891 data: The data to write.
1892
1893 Returns:
1894 The number of bytes processed.
1895 """
1896 self.bytes_received += len(data)
1897 # TODO: check the parser's return value for errors?
1898 assert self.parser is not None
1899 return self.parser.write(data)
1900
1901 def finalize(self) -> None:
1902 """Finalize the parser."""
1903 if self.parser is not None and hasattr(self.parser, "finalize"):
1904 self.parser.finalize()
1905
1906 def close(self) -> None:
1907 """Close the parser."""
1908 if self.parser is not None and hasattr(self.parser, "close"):
1909 self.parser.close()
1910
1911 def __repr__(self) -> str:
1912 return f"{self.__class__.__name__}(content_type={self.content_type!r}, parser={self.parser!r})"
1913
1914
1915def create_form_parser(
1916 headers: dict[str, bytes],
1917 on_field: Callable[[Field], None] | None,
1918 on_file: Callable[[File], None] | None,
1919 config: dict[Any, Any] = {},
1920) -> FormParser:
1921 """This function is a helper function to aid in creating a FormParser
1922 instances. Given a dictionary-like headers object, it will determine
1923 the correct information needed, instantiate a FormParser with the
1924 appropriate values and given callbacks, and then return the corresponding
1925 parser.
1926
1927 Args:
1928 headers: A dictionary-like object of HTTP headers. The only required header is Content-Type.
1929 on_field: Callback to call with each parsed field.
1930 on_file: Callback to call with each parsed file.
1931 config: Configuration variables to pass to the FormParser.
1932 """
1933 content_type: str | bytes | None = headers.get("Content-Type")
1934 if content_type is None:
1935 logging.getLogger(__name__).warning("No Content-Type header given")
1936 raise ValueError("No Content-Type header given!")
1937
1938 # Boundaries are optional (the FormParser will raise if one is needed
1939 # but not given).
1940 content_type, params = parse_options_header(content_type)
1941 boundary = params.get(b"boundary")
1942
1943 # We need content_type to be a string, not a bytes object.
1944 content_type = content_type.decode("latin-1")
1945
1946 # Instantiate a form parser.
1947 form_parser = FormParser(content_type, on_field, on_file, boundary=boundary, config=config)
1948
1949 # Return our parser.
1950 return form_parser
1951
1952
1953def parse_form(
1954 headers: dict[str, bytes],
1955 input_stream: SupportsRead,
1956 on_field: Callable[[Field], None] | None,
1957 on_file: Callable[[File], None] | None,
1958 chunk_size: int = 1048576,
1959) -> None:
1960 """This function is useful if you just want to parse a request body,
1961 without too much work. Pass it a dictionary-like object of the request's
1962 headers, and a file-like object for the input stream, along with two
1963 callbacks that will get called whenever a field or file is parsed.
1964
1965 Args:
1966 headers: A dictionary-like object of HTTP headers. The only required header is Content-Type.
1967 input_stream: A file-like object that represents the request body. The read() method must return bytestrings.
1968 on_field: Callback to call with each parsed field.
1969 on_file: Callback to call with each parsed file.
1970 chunk_size: The maximum size to read from the input stream and write to the parser at one time.
1971 Defaults to 1 MiB.
1972 """
1973 if chunk_size < 1:
1974 raise ValueError(f"chunk_size must be a positive number, not {chunk_size!r}")
1975
1976 # Create our form parser.
1977 parser = create_form_parser(headers, on_field, on_file)
1978
1979 # Read chunks of 1MiB and write to the parser, but never read more than
1980 # the given Content-Length, if any.
1981 content_length: int | float | bytes | None = headers.get("Content-Length")
1982 if content_length is not None:
1983 content_length = int(content_length)
1984 if content_length < 0:
1985 raise ValueError("Content-Length must be non-negative")
1986 else:
1987 content_length = float("inf")
1988 bytes_read = 0
1989
1990 while True:
1991 # Read only up to the Content-Length given.
1992 max_readable = int(min(content_length - bytes_read, chunk_size))
1993 buff = input_stream.read(max_readable)
1994
1995 # Write to the parser and update our length.
1996 parser.write(buff)
1997 bytes_read += len(buff)
1998
1999 # If we get a buffer that's smaller than the size requested, or if we
2000 # have read up to our content length, we're done.
2001 if len(buff) != max_readable or bytes_read == content_length:
2002 break
2003
2004 # Tell our parser that we're done writing data.
2005 parser.finalize()