Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/wrappers/response.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

269 statements  

1from __future__ import annotations 

2 

3import json 

4import typing as t 

5from http import HTTPStatus 

6from urllib.parse import urljoin 

7 

8from .._internal import _get_environ 

9from ..datastructures import Headers 

10from ..http import generate_etag 

11from ..http import http_date 

12from ..http import is_resource_modified 

13from ..http import parse_etags 

14from ..http import parse_range_header 

15from ..http import remove_entity_headers 

16from ..sansio.response import Response as _SansIOResponse 

17from ..urls import iri_to_uri 

18from ..utils import cached_property 

19from ..wsgi import _RangeWrapper 

20from ..wsgi import ClosingIterator 

21from ..wsgi import get_current_url 

22 

23if t.TYPE_CHECKING: 

24 from _typeshed.wsgi import StartResponse 

25 from _typeshed.wsgi import WSGIApplication 

26 from _typeshed.wsgi import WSGIEnvironment 

27 

28 from .request import Request 

29 

30 

31def _iter_encoded(iterable: t.Iterable[str | bytes]) -> t.Iterator[bytes]: 

32 for item in iterable: 

33 if isinstance(item, str): 

34 yield item.encode() 

35 else: 

36 yield item 

37 

38 

39class Response(_SansIOResponse): 

40 """Represents an outgoing WSGI HTTP response with body, status, and 

41 headers. Has properties and methods for using the functionality 

42 defined by various HTTP specs. 

43 

44 The response body is flexible to support different use cases. The 

45 simple form is passing bytes, or a string which will be encoded as 

46 UTF-8. Passing an iterable of bytes or strings makes this a 

47 streaming response. A generator is particularly useful for building 

48 a CSV file in memory or using SSE (Server Sent Events). A file-like 

49 object is also iterable, although the 

50 :func:`~werkzeug.utils.send_file` helper should be used in that 

51 case. 

52 

53 The response object is itself a WSGI application callable. When 

54 called (:meth:`__call__`) with ``environ`` and ``start_response``, 

55 it will pass its status and headers to ``start_response`` then 

56 return its body as an iterable. 

57 

58 .. code-block:: python 

59 

60 from werkzeug.wrappers.response import Response 

61 

62 def index(): 

63 return Response("Hello, World!") 

64 

65 def application(environ, start_response): 

66 path = environ.get("PATH_INFO") or "/" 

67 

68 if path == "/": 

69 response = index() 

70 else: 

71 response = Response("Not Found", status=404) 

72 

73 return response(environ, start_response) 

74 

75 :param response: The data for the body of the response. A string or 

76 bytes, or tuple or list of strings or bytes, for a fixed-length 

77 response, or any other iterable of strings or bytes for a 

78 streaming response. Defaults to an empty body. 

79 :param status: The status code for the response. Either an int, in 

80 which case the default status message is added, or a string in 

81 the form ``{code} {message}``, like ``404 Not Found``. Defaults 

82 to 200. 

83 :param headers: A :class:`~werkzeug.datastructures.Headers` object, 

84 or a list of ``(key, value)`` tuples that will be converted to a 

85 ``Headers`` object. 

86 :param mimetype: The mime type (content type without charset or 

87 other parameters) of the response. If the value starts with 

88 ``text/`` (or matches some other special cases), the charset 

89 will be added to create the ``content_type``. 

90 :param content_type: The full content type of the response. 

91 Overrides building the value from ``mimetype``. 

92 :param direct_passthrough: Pass the response body directly through 

93 as the WSGI iterable. This can be used when the body is a binary 

94 file or other iterator of bytes, to skip some unnecessary 

95 checks. Use :func:`~werkzeug.utils.send_file` instead of setting 

96 this manually. 

97 

98 .. versionchanged:: 2.1 

99 Old ``BaseResponse`` and mixin classes were removed. 

100 

101 .. versionchanged:: 2.0 

102 Combine ``BaseResponse`` and mixins into a single ``Response`` 

103 class. 

104 

105 .. versionchanged:: 0.5 

106 The ``direct_passthrough`` parameter was added. 

107 """ 

108 

109 #: if set to `False` accessing properties on the response object will 

110 #: not try to consume the response iterator and convert it into a list. 

111 #: 

112 #: .. versionadded:: 0.6.2 

113 #: 

114 #: That attribute was previously called `implicit_seqence_conversion`. 

115 #: (Notice the typo). If you did use this feature, you have to adapt 

116 #: your code to the name change. 

117 implicit_sequence_conversion = True 

118 

119 #: If a redirect ``Location`` header is a relative URL, make it an 

120 #: absolute URL, including scheme and domain. 

121 #: 

122 #: .. versionchanged:: 2.1 

123 #: This is disabled by default, so responses will send relative 

124 #: redirects. 

125 #: 

126 #: .. versionadded:: 0.8 

127 autocorrect_location_header = False 

128 

129 #: Should this response object automatically set the content-length 

130 #: header if possible? This is true by default. 

131 #: 

132 #: .. versionadded:: 0.8 

133 automatically_set_content_length = True 

134 

135 #: The response body to send as the WSGI iterable. A list of strings 

136 #: or bytes represents a fixed-length response, any other iterable 

137 #: is a streaming response. Strings are encoded to bytes as UTF-8. 

138 #: 

139 #: Do not set to a plain string or bytes, that will cause sending 

140 #: the response to be very inefficient as it will iterate one byte 

141 #: at a time. 

142 response: t.Iterable[str] | t.Iterable[bytes] 

143 

144 def __init__( 

145 self, 

146 response: t.Iterable[bytes] | bytes | t.Iterable[str] | str | None = None, 

147 status: int | str | HTTPStatus | None = None, 

148 headers: t.Mapping[str, str | t.Iterable[str]] 

149 | t.Iterable[tuple[str, str]] 

150 | None = None, 

151 mimetype: str | None = None, 

152 content_type: str | None = None, 

153 direct_passthrough: bool = False, 

154 ) -> None: 

155 super().__init__( 

156 status=status, 

157 headers=headers, 

158 mimetype=mimetype, 

159 content_type=content_type, 

160 ) 

161 

162 #: Pass the response body directly through as the WSGI iterable. 

163 #: This can be used when the body is a binary file or other 

164 #: iterator of bytes, to skip some unnecessary checks. Use 

165 #: :func:`~werkzeug.utils.send_file` instead of setting this 

166 #: manually. 

167 self.direct_passthrough = direct_passthrough 

168 self._on_close: list[t.Callable[[], t.Any]] = [] 

169 

170 # we set the response after the headers so that if a class changes 

171 # the charset attribute, the data is set in the correct charset. 

172 if response is None: 

173 self.response = [] 

174 elif isinstance(response, (str, bytes, bytearray)): 

175 self.set_data(response) 

176 else: 

177 self.response = response 

178 

179 def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]: 

180 """Adds a function to the internal list of functions that should 

181 be called as part of closing down the response. Since 0.7 this 

182 function also returns the function that was passed so that this 

183 can be used as a decorator. 

184 

185 .. versionadded:: 0.6 

186 """ 

187 self._on_close.append(func) 

188 return func 

189 

190 def __repr__(self) -> str: 

191 if self.is_sequence: 

192 body_info = f"{sum(map(len, self.iter_encoded()))} bytes" 

193 else: 

194 body_info = "streamed" if self.is_streamed else "likely-streamed" 

195 return f"<{type(self).__name__} {body_info} [{self.status}]>" 

196 

197 @classmethod 

198 def force_type( 

199 cls, response: Response, environ: WSGIEnvironment | None = None 

200 ) -> Response: 

201 """Enforce that the WSGI response is a response object of the current 

202 type. Werkzeug will use the :class:`Response` internally in many 

203 situations like the exceptions. If you call :meth:`get_response` on an 

204 exception you will get back a regular :class:`Response` object, even 

205 if you are using a custom subclass. 

206 

207 This method can enforce a given response type, and it will also 

208 convert arbitrary WSGI callables into response objects if an environ 

209 is provided:: 

210 

211 # convert a Werkzeug response object into an instance of the 

212 # MyResponseClass subclass. 

213 response = MyResponseClass.force_type(response) 

214 

215 # convert any WSGI application into a response object 

216 response = MyResponseClass.force_type(response, environ) 

217 

218 This is especially useful if you want to post-process responses in 

219 the main dispatcher and use functionality provided by your subclass. 

220 

221 Keep in mind that this will modify response objects in place if 

222 possible! 

223 

224 :param response: a response object or wsgi application. 

225 :param environ: a WSGI environment object. 

226 :return: a response object. 

227 """ 

228 if not isinstance(response, Response): 

229 if environ is None: 

230 raise TypeError( 

231 "cannot convert WSGI application into response" 

232 " objects without an environ" 

233 ) 

234 

235 from ..test import run_wsgi_app 

236 

237 response = Response(*run_wsgi_app(response, environ)) 

238 

239 response.__class__ = cls 

240 return response 

241 

242 @classmethod 

243 def from_app( 

244 cls, app: WSGIApplication, environ: WSGIEnvironment, buffered: bool = False 

245 ) -> Response: 

246 """Create a new response object from an application output. This 

247 works best if you pass it an application that returns a generator all 

248 the time. Sometimes applications may use the `write()` callable 

249 returned by the `start_response` function. This tries to resolve such 

250 edge cases automatically. But if you don't get the expected output 

251 you should set `buffered` to `True` which enforces buffering. 

252 

253 :param app: the WSGI application to execute. 

254 :param environ: the WSGI environment to execute against. 

255 :param buffered: set to `True` to enforce buffering. 

256 :return: a response object. 

257 """ 

258 from ..test import run_wsgi_app 

259 

260 return cls(*run_wsgi_app(app, environ, buffered)) 

261 

262 @t.overload 

263 def get_data(self, as_text: t.Literal[False] = False) -> bytes: ... 

264 

265 @t.overload 

266 def get_data(self, as_text: t.Literal[True]) -> str: ... 

267 

268 def get_data(self, as_text: bool = False) -> bytes | str: 

269 """The string representation of the response body. Whenever you call 

270 this property the response iterable is encoded and flattened. This 

271 can lead to unwanted behavior if you stream big data. 

272 

273 This behavior can be disabled by setting 

274 :attr:`implicit_sequence_conversion` to `False`. 

275 

276 If `as_text` is set to `True` the return value will be a decoded 

277 string. 

278 

279 .. versionadded:: 0.9 

280 """ 

281 self._ensure_sequence() 

282 rv = b"".join(self.iter_encoded()) 

283 

284 if as_text: 

285 return rv.decode() 

286 

287 return rv 

288 

289 def set_data(self, value: bytes | str) -> None: 

290 """Sets a new string as response. The value must be a string or 

291 bytes. If a string is set it's encoded to the charset of the 

292 response (utf-8 by default). 

293 

294 .. versionadded:: 0.9 

295 """ 

296 if isinstance(value, str): 

297 value = value.encode() 

298 self.response = [value] 

299 if self.automatically_set_content_length: 

300 self.headers["Content-Length"] = str(len(value)) 

301 

302 data = property( 

303 get_data, 

304 set_data, 

305 doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.", 

306 ) 

307 

308 def calculate_content_length(self) -> int | None: 

309 """Returns the content length if available or `None` otherwise.""" 

310 try: 

311 self._ensure_sequence() 

312 except RuntimeError: 

313 return None 

314 return sum(len(x) for x in self.iter_encoded()) 

315 

316 def _ensure_sequence(self, mutable: bool = False) -> None: 

317 """This method can be called by methods that need a sequence. If 

318 `mutable` is true, it will also ensure that the response sequence 

319 is a standard Python list. 

320 

321 .. versionadded:: 0.6 

322 """ 

323 if self.is_sequence: 

324 # if we need a mutable object, we ensure it's a list. 

325 if mutable and not isinstance(self.response, list): 

326 self.response = list(self.response) # type: ignore 

327 return 

328 if self.direct_passthrough: 

329 raise RuntimeError( 

330 "Attempted implicit sequence conversion but the" 

331 " response object is in direct passthrough mode." 

332 ) 

333 if not self.implicit_sequence_conversion: 

334 raise RuntimeError( 

335 "The response object required the iterable to be a" 

336 " sequence, but the implicit conversion was disabled." 

337 " Call make_sequence() yourself." 

338 ) 

339 self.make_sequence() 

340 

341 def make_sequence(self) -> None: 

342 """Converts the response iterator in a list. By default this happens 

343 automatically if required. If `implicit_sequence_conversion` is 

344 disabled, this method is not automatically called and some properties 

345 might raise exceptions. This also encodes all the items. 

346 

347 .. versionadded:: 0.6 

348 """ 

349 if not self.is_sequence: 

350 # if we consume an iterable we have to ensure that the close 

351 # method of the iterable is called if available when we tear 

352 # down the response 

353 close = getattr(self.response, "close", None) 

354 self.response = list(self.iter_encoded()) 

355 if close is not None: 

356 self.call_on_close(close) 

357 

358 def iter_encoded(self) -> t.Iterator[bytes]: 

359 """Iter the response encoded with the encoding of the response. 

360 If the response object is invoked as WSGI application the return 

361 value of this method is used as application iterator unless 

362 :attr:`direct_passthrough` was activated. 

363 """ 

364 # Encode in a separate function so that self.response is fetched 

365 # early. This allows us to wrap the response with the return 

366 # value from get_app_iter or iter_encoded. 

367 return _iter_encoded(self.response) 

368 

369 @property 

370 def is_streamed(self) -> bool: 

371 """If the response is streamed (the response is not an iterable with 

372 a length information) this property is `True`. In this case streamed 

373 means that there is no information about the number of iterations. 

374 This is usually `True` if a generator is passed to the response object. 

375 

376 This is useful for checking before applying some sort of post 

377 filtering that should not take place for streamed responses. 

378 """ 

379 try: 

380 len(self.response) # type: ignore 

381 except (TypeError, AttributeError): 

382 return True 

383 return False 

384 

385 @property 

386 def is_sequence(self) -> bool: 

387 """If the iterator is buffered, this property will be `True`. A 

388 response object will consider an iterator to be buffered if the 

389 response attribute is a list or tuple. 

390 

391 .. versionadded:: 0.6 

392 """ 

393 return isinstance(self.response, (tuple, list)) 

394 

395 def close(self) -> None: 

396 """Close the wrapped response if possible. You can also use the object 

397 in a with statement which will automatically close it. 

398 

399 .. versionadded:: 0.9 

400 Can now be used in a with statement. 

401 """ 

402 if hasattr(self.response, "close"): 

403 self.response.close() 

404 for func in self._on_close: 

405 func() 

406 

407 def __enter__(self) -> Response: 

408 return self 

409 

410 def __exit__(self, exc_type, exc_value, tb): # type: ignore 

411 self.close() 

412 

413 def freeze(self) -> None: 

414 """Make the response object ready to be pickled. Does the 

415 following: 

416 

417 * Buffer the response into a list, ignoring 

418 :attr:`implicity_sequence_conversion` and 

419 :attr:`direct_passthrough`. 

420 * Set the ``Content-Length`` header. 

421 * Generate an ``ETag`` header if one is not already set. 

422 

423 .. versionchanged:: 2.1 

424 Removed the ``no_etag`` parameter. 

425 

426 .. versionchanged:: 2.0 

427 An ``ETag`` header is always added. 

428 

429 .. versionchanged:: 0.6 

430 The ``Content-Length`` header is set. 

431 """ 

432 # Always freeze the encoded response body, ignore 

433 # implicit_sequence_conversion and direct_passthrough. 

434 self.response = list(self.iter_encoded()) 

435 self.headers["Content-Length"] = str(sum(map(len, self.response))) 

436 self.add_etag() 

437 

438 def get_wsgi_headers(self, environ: WSGIEnvironment) -> Headers: 

439 """This is automatically called right before the response is started 

440 and returns headers modified for the given environment. It returns a 

441 copy of the headers from the response with some modifications applied 

442 if necessary. 

443 

444 For example the location header (if present) is joined with the root 

445 URL of the environment. Also the content length is automatically set 

446 to zero here for certain status codes. 

447 

448 .. versionchanged:: 0.6 

449 Previously that function was called `fix_headers` and modified 

450 the response object in place. Also since 0.6, IRIs in location 

451 and content-location headers are handled properly. 

452 

453 Also starting with 0.6, Werkzeug will attempt to set the content 

454 length if it is able to figure it out on its own. This is the 

455 case if all the strings in the response iterable are already 

456 encoded and the iterable is buffered. 

457 

458 :param environ: the WSGI environment of the request. 

459 :return: returns a new :class:`~werkzeug.datastructures.Headers` 

460 object. 

461 """ 

462 headers = Headers(self.headers) 

463 location: str | None = None 

464 content_location: str | None = None 

465 content_length: str | int | None = None 

466 status = self.status_code 

467 

468 # iterate over the headers to find all values in one go. Because 

469 # get_wsgi_headers is used each response that gives us a tiny 

470 # speedup. 

471 for key, value in headers: 

472 ikey = key.lower() 

473 if ikey == "location": 

474 location = value 

475 elif ikey == "content-location": 

476 content_location = value 

477 elif ikey == "content-length": 

478 content_length = value 

479 

480 if location is not None: 

481 location = iri_to_uri(location) 

482 

483 if self.autocorrect_location_header: 

484 # Make the location header an absolute URL. 

485 current_url = get_current_url(environ, strip_querystring=True) 

486 current_url = iri_to_uri(current_url) 

487 location = urljoin(current_url, location) 

488 

489 headers["Location"] = location 

490 

491 # make sure the content location is a URL 

492 if content_location is not None: 

493 headers["Content-Location"] = iri_to_uri(content_location) 

494 

495 if 100 <= status < 200 or status == 204: 

496 # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a 

497 # Content-Length header field in any response with a status 

498 # code of 1xx (Informational) or 204 (No Content)." 

499 headers.remove("Content-Length") 

500 elif status == 304: 

501 remove_entity_headers(headers) 

502 

503 # if we can determine the content length automatically, we 

504 # should try to do that. But only if this does not involve 

505 # flattening the iterator or encoding of strings in the 

506 # response. We however should not do that if we have a 304 

507 # response. 

508 if ( 

509 self.automatically_set_content_length 

510 and self.is_sequence 

511 and content_length is None 

512 and status not in (204, 304) 

513 and not (100 <= status < 200) 

514 ): 

515 content_length = sum(len(x) for x in self.iter_encoded()) 

516 headers["Content-Length"] = str(content_length) 

517 

518 return headers 

519 

520 def get_app_iter(self, environ: WSGIEnvironment) -> t.Iterable[bytes]: 

521 """Returns the application iterator for the given environ. Depending 

522 on the request method and the current status code the return value 

523 might be an empty response rather than the one from the response. 

524 

525 If the request method is `HEAD` or the status code is in a range 

526 where the HTTP specification requires an empty response, an empty 

527 iterable is returned. 

528 

529 .. versionadded:: 0.6 

530 

531 :param environ: the WSGI environment of the request. 

532 :return: a response iterable. 

533 """ 

534 status = self.status_code 

535 if ( 

536 environ["REQUEST_METHOD"] == "HEAD" 

537 or 100 <= status < 200 

538 or status in (204, 304) 

539 ): 

540 iterable: t.Iterable[bytes] = () 

541 elif self.direct_passthrough: 

542 return self.response # type: ignore 

543 else: 

544 iterable = self.iter_encoded() 

545 return ClosingIterator(iterable, self.close) 

546 

547 def get_wsgi_response( 

548 self, environ: WSGIEnvironment 

549 ) -> tuple[t.Iterable[bytes], str, list[tuple[str, str]]]: 

550 """Returns the final WSGI response as tuple. The first item in 

551 the tuple is the application iterator, the second the status and 

552 the third the list of headers. The response returned is created 

553 specially for the given environment. For example if the request 

554 method in the WSGI environment is ``'HEAD'`` the response will 

555 be empty and only the headers and status code will be present. 

556 

557 .. versionadded:: 0.6 

558 

559 :param environ: the WSGI environment of the request. 

560 :return: an ``(app_iter, status, headers)`` tuple. 

561 """ 

562 headers = self.get_wsgi_headers(environ) 

563 app_iter = self.get_app_iter(environ) 

564 return app_iter, self.status, headers.to_wsgi_list() 

565 

566 def __call__( 

567 self, environ: WSGIEnvironment, start_response: StartResponse 

568 ) -> t.Iterable[bytes]: 

569 """Process this response as WSGI application. 

570 

571 :param environ: the WSGI environment. 

572 :param start_response: the response callable provided by the WSGI 

573 server. 

574 :return: an application iterator 

575 """ 

576 app_iter, status, headers = self.get_wsgi_response(environ) 

577 start_response(status, headers) 

578 return app_iter 

579 

580 # JSON 

581 

582 #: A module or other object that has ``dumps`` and ``loads`` 

583 #: functions that match the API of the built-in :mod:`json` module. 

584 json_module = json 

585 

586 @property 

587 def json(self) -> t.Any | None: 

588 """The parsed JSON data if :attr:`mimetype` indicates JSON 

589 (:mimetype:`application/json`, see :attr:`is_json`). 

590 

591 Calls :meth:`get_json` with default arguments. 

592 """ 

593 return self.get_json() 

594 

595 @t.overload 

596 def get_json(self, force: bool = ..., silent: t.Literal[False] = ...) -> t.Any: ... 

597 

598 @t.overload 

599 def get_json(self, force: bool = ..., silent: bool = ...) -> t.Any | None: ... 

600 

601 def get_json(self, force: bool = False, silent: bool = False) -> t.Any | None: 

602 """Parse :attr:`data` as JSON. Useful during testing. 

603 

604 If the mimetype does not indicate JSON 

605 (:mimetype:`application/json`, see :attr:`is_json`), this 

606 returns ``None``. 

607 

608 Unlike :meth:`Request.get_json`, the result is not cached. 

609 

610 :param force: Ignore the mimetype and always try to parse JSON. 

611 :param silent: Silence parsing errors and return ``None`` 

612 instead. 

613 """ 

614 if not (force or self.is_json): 

615 return None 

616 

617 data = self.get_data() 

618 

619 try: 

620 return self.json_module.loads(data) 

621 except ValueError: 

622 if not silent: 

623 raise 

624 

625 return None 

626 

627 # Stream 

628 

629 @cached_property 

630 def stream(self) -> ResponseStream: 

631 """The response iterable as write-only stream.""" 

632 return ResponseStream(self) 

633 

634 def _wrap_range_response(self, start: int, length: int) -> None: 

635 """Wrap existing Response in case of Range Request context.""" 

636 if self.status_code == 206: 

637 self.response = _RangeWrapper(self.response, start, length) # type: ignore 

638 

639 def _is_range_request_processable(self, environ: WSGIEnvironment) -> bool: 

640 """Return ``True`` if `Range` header is present and if underlying 

641 resource is considered unchanged when compared with `If-Range` header. 

642 """ 

643 return ( 

644 "HTTP_IF_RANGE" not in environ 

645 or not is_resource_modified( 

646 environ, 

647 self.headers.get("etag"), 

648 None, 

649 self.headers.get("last-modified"), 

650 ignore_if_range=False, 

651 ) 

652 ) and "HTTP_RANGE" in environ 

653 

654 def _process_range_request( 

655 self, 

656 environ: WSGIEnvironment, 

657 complete_length: int | None, 

658 accept_ranges: bool | str, 

659 ) -> bool: 

660 """Handle Range Request related headers (RFC7233). If `Accept-Ranges` 

661 header is valid, and Range Request is processable, we set the headers 

662 as described by the RFC, and wrap the underlying response in a 

663 RangeWrapper. 

664 

665 Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise. 

666 

667 :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` 

668 if `Range` header could not be parsed or satisfied. 

669 

670 .. versionchanged:: 2.0 

671 Returns ``False`` if the length is 0. 

672 """ 

673 from ..exceptions import RequestedRangeNotSatisfiable 

674 

675 if ( 

676 not accept_ranges 

677 or complete_length is None 

678 or complete_length == 0 

679 or not self._is_range_request_processable(environ) 

680 ): 

681 return False 

682 

683 if accept_ranges is True: 

684 accept_ranges = "bytes" 

685 

686 parsed_range = parse_range_header(environ.get("HTTP_RANGE")) 

687 

688 if parsed_range is None: 

689 raise RequestedRangeNotSatisfiable(complete_length) 

690 

691 range_tuple = parsed_range.range_for_length(complete_length) 

692 content_range_header = parsed_range.to_content_range_header(complete_length) 

693 

694 if range_tuple is None or content_range_header is None: 

695 raise RequestedRangeNotSatisfiable(complete_length) 

696 

697 content_length = range_tuple[1] - range_tuple[0] 

698 self.headers["Content-Length"] = str(content_length) 

699 self.headers["Accept-Ranges"] = accept_ranges 

700 self.content_range = content_range_header # type: ignore 

701 self.status_code = 206 

702 self._wrap_range_response(range_tuple[0], content_length) 

703 return True 

704 

705 def make_conditional( 

706 self, 

707 request_or_environ: WSGIEnvironment | Request, 

708 accept_ranges: bool | str = False, 

709 complete_length: int | None = None, 

710 ) -> Response: 

711 """Make the response conditional to the request. This method works 

712 best if an etag was defined for the response already. The `add_etag` 

713 method can be used to do that. If called without etag just the date 

714 header is set. 

715 

716 This does nothing if the request method in the request or environ is 

717 anything but GET or HEAD. 

718 

719 For optimal performance when handling range requests, it's recommended 

720 that your response data object implements `seekable`, `seek` and `tell` 

721 methods as described by :py:class:`io.IOBase`. Objects returned by 

722 :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods. 

723 

724 It does not remove the body of the response because that's something 

725 the :meth:`__call__` function does for us automatically. 

726 

727 Returns self so that you can do ``return resp.make_conditional(req)`` 

728 but modifies the object in-place. 

729 

730 :param request_or_environ: a request object or WSGI environment to be 

731 used to make the response conditional 

732 against. 

733 :param accept_ranges: This parameter dictates the value of 

734 `Accept-Ranges` header. If ``False`` (default), 

735 the header is not set. If ``True``, it will be set 

736 to ``"bytes"``. If it's a string, it will use this 

737 value. 

738 :param complete_length: Will be used only in valid Range Requests. 

739 It will set `Content-Range` complete length 

740 value and compute `Content-Length` real value. 

741 This parameter is mandatory for successful 

742 Range Requests completion. 

743 :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` 

744 if `Range` header could not be parsed or satisfied. 

745 

746 .. versionchanged:: 2.0 

747 Range processing is skipped if length is 0 instead of 

748 raising a 416 Range Not Satisfiable error. 

749 """ 

750 environ = _get_environ(request_or_environ) 

751 if environ["REQUEST_METHOD"] in ("GET", "HEAD"): 

752 # if the date is not in the headers, add it now. We however 

753 # will not override an already existing header. Unfortunately 

754 # this header will be overridden by many WSGI servers including 

755 # wsgiref. 

756 if "date" not in self.headers: 

757 self.headers["Date"] = http_date() 

758 is206 = self._process_range_request(environ, complete_length, accept_ranges) 

759 if not is206 and not is_resource_modified( 

760 environ, 

761 self.headers.get("etag"), 

762 None, 

763 self.headers.get("last-modified"), 

764 ): 

765 if parse_etags(environ.get("HTTP_IF_MATCH")): 

766 self.status_code = 412 

767 else: 

768 self.status_code = 304 

769 if ( 

770 self.automatically_set_content_length 

771 and "content-length" not in self.headers 

772 ): 

773 length = self.calculate_content_length() 

774 if length is not None: 

775 self.headers["Content-Length"] = str(length) 

776 return self 

777 

778 def add_etag(self, overwrite: bool = False, weak: bool = False) -> None: 

779 """Add an etag for the current response if there is none yet. 

780 

781 .. versionchanged:: 2.0 

782 SHA-1 is used to generate the value. MD5 may not be 

783 available in some environments. 

784 """ 

785 if overwrite or "etag" not in self.headers: 

786 self.set_etag(generate_etag(self.get_data()), weak) 

787 

788 

789class ResponseStream: 

790 """A file descriptor like object used by :meth:`Response.stream` to 

791 represent the body of the stream. It directly pushes into the 

792 response iterable of the response object. 

793 """ 

794 

795 mode = "wb+" 

796 

797 def __init__(self, response: Response): 

798 self.response = response 

799 self.closed = False 

800 

801 def write(self, value: bytes) -> int: 

802 if self.closed: 

803 raise ValueError("I/O operation on closed file") 

804 self.response._ensure_sequence(mutable=True) 

805 self.response.response.append(value) # type: ignore 

806 self.response.headers.pop("Content-Length", None) 

807 return len(value) 

808 

809 def writelines(self, seq: t.Iterable[bytes]) -> None: 

810 for item in seq: 

811 self.write(item) 

812 

813 def close(self) -> None: 

814 self.closed = True 

815 

816 def flush(self) -> None: 

817 if self.closed: 

818 raise ValueError("I/O operation on closed file") 

819 

820 def isatty(self) -> bool: 

821 if self.closed: 

822 raise ValueError("I/O operation on closed file") 

823 return False 

824 

825 def tell(self) -> int: 

826 self.response._ensure_sequence() 

827 return sum(map(len, self.response.response)) 

828 

829 @property 

830 def encoding(self) -> str: 

831 return "utf-8"