| 138 | self.buffer.extend(data) |
| 139 | |
| 140 | def next_event(self) -> Event: |
| 141 | event: Event = NEED_DATA |
| 142 | if self.state == State.PREAMBLE: |
| 143 | match = self.preamble_re.search(self.buffer, self._search_position) |
| 144 | if match is not None: |
| 145 | if match.group(1).startswith(b"--"): |
| 146 | self.state = State.EPILOGUE |
| 147 | else: |
| 148 | self.state = State.PART |
| 149 | data = bytes(self.buffer[: match.start()]) |
| 150 | del self.buffer[: match.end()] |
| 151 | event = Preamble(data=data) |
| 152 | self._search_position = 0 |
| 153 | else: |
| 154 | # Update the search start position to be equal to the |
| 155 | # current buffer length (already searched) minus a |
| 156 | # safe buffer for part of the search target. |
| 157 | self._search_position = max( |
| 158 | 0, len(self.buffer) - len(self.boundary) - SEARCH_EXTRA_LENGTH |
| 159 | ) |
| 160 | |
| 161 | elif self.state == State.PART: |
| 162 | match = BLANK_LINE_RE.search(self.buffer, self._search_position) |
| 163 | if match is not None: |
| 164 | headers = self._parse_headers(self.buffer[: match.start()]) |
| 165 | # The final header ends with a single CRLF, however a |
| 166 | # blank line indicates the start of the |
| 167 | # body. Therefore the end is after the first CRLF. |
| 168 | headers_end = (match.start() + match.end()) // 2 |
| 169 | del self.buffer[:headers_end] |
| 170 | |
| 171 | if "content-disposition" not in headers: |
| 172 | raise ValueError("Missing Content-Disposition header") |
| 173 | |
| 174 | disposition, extra = parse_options_header( |
| 175 | headers["content-disposition"] |
| 176 | ) |
| 177 | name = t.cast(str, extra.get("name")) |
| 178 | filename = extra.get("filename") |
| 179 | if filename is not None: |
| 180 | event = File( |
| 181 | filename=filename, |
| 182 | headers=headers, |
| 183 | name=name, |
| 184 | ) |
| 185 | else: |
| 186 | event = Field( |
| 187 | headers=headers, |
| 188 | name=name, |
| 189 | ) |
| 190 | self.state = State.DATA_START |
| 191 | self._search_position = 0 |
| 192 | self._parts_decoded += 1 |
| 193 | |
| 194 | if self.max_parts is not None and self._parts_decoded > self.max_parts: |
| 195 | raise RequestEntityTooLarge() |
| 196 | else: |
| 197 | # Update the search start position to be equal to the |