(self)
| 620 | return False |
| 621 | |
| 622 | def finish(self) -> None: |
| 623 | assert self.code is not None |
| 624 | data = b"".join(self.chunks) |
| 625 | self._remove_timeout() |
| 626 | original_request = getattr(self.request, "original_request", self.request) |
| 627 | if self._should_follow_redirect(): |
| 628 | assert isinstance(self.request, _RequestProxy) |
| 629 | assert self.headers is not None |
| 630 | new_request = copy.copy(self.request.request) |
| 631 | new_request.url = urllib.parse.urljoin( |
| 632 | self.request.url, self.headers["Location"] |
| 633 | ) |
| 634 | new_request.headers = self.request.headers.copy() |
| 635 | parsed_orig_url = urllib.parse.urlsplit(original_request.url) |
| 636 | parsed_new_url = urllib.parse.urlsplit(new_request.url) |
| 637 | if ( |
| 638 | parsed_orig_url.scheme != parsed_new_url.scheme |
| 639 | or parsed_orig_url.netloc != parsed_new_url.netloc |
| 640 | ): |
| 641 | # Cross-origin redirect: strip auth headers. |
| 642 | # Note that while there is no formal specification of headers that should be |
| 643 | # stripped here, libcurl strips the Authorization and Cookie headers, so we |
| 644 | # do the same. |
| 645 | # Reference: |
| 646 | # https://github.com/curl/curl/blob/01d8191b25a05e8fa91553a6c0d48acb99907d26/lib/http.c#L1827-L1828 |
| 647 | # |
| 648 | # Note that checking for cross-origin redirects is a crude heuristic. It is both |
| 649 | # too weak (e.g. cookies that have a path attribute may need to be stripped even on |
| 650 | # same-origin redirects) and too strong (e.g. cookies may be kept on cross-host |
| 651 | # redirects within the same domain). However, we cannot know the full details of |
| 652 | # the cookie policy at this layer, so we use the same heuristic as libcurl. |
| 653 | # Applications that need more control over behavior on redirects can set |
| 654 | # follow_redirects=False and handle 3xx responses themselves. |
| 655 | new_request.auth_username = None |
| 656 | new_request.auth_password = None |
| 657 | if "@" in parsed_new_url.netloc: |
| 658 | if parsed_new_url.port is not None: |
| 659 | new_netloc = f"{parsed_new_url.hostname}:{parsed_new_url.port}" |
| 660 | else: |
| 661 | assert parsed_new_url.hostname is not None |
| 662 | new_netloc = parsed_new_url.hostname |
| 663 | parsed_new_url = parsed_new_url._replace(netloc=new_netloc) |
| 664 | new_request.url = urllib.parse.urlunsplit(parsed_new_url) |
| 665 | for h in ["Authorization", "Cookie"]: |
| 666 | try: |
| 667 | del new_request.headers[h] |
| 668 | except KeyError: |
| 669 | pass |
| 670 | assert self.request.max_redirects is not None |
| 671 | new_request.max_redirects = self.request.max_redirects - 1 |
| 672 | del new_request.headers["Host"] |
| 673 | # https://tools.ietf.org/html/rfc7231#section-6.4 |
| 674 | # |
| 675 | # The original HTTP spec said that after a 301 or 302 |
| 676 | # redirect, the request method should be preserved. |
| 677 | # However, browsers implemented this by changing the |
| 678 | # method to GET, and the behavior stuck. 303 redirects |
| 679 | # always specified this POST-to-GET behavior, arguably |
no test coverage detected