From 3331e2aecdbf575dd60abef4df79c52d78610a83 Mon Sep 17 00:00:00 2001
From: Bruce Merry <bmerry@ska.ac.za>
Date: Thu, 28 Jun 2018 16:38:42 +0200
Subject: [PATCH 1/2] Strip Authorization header whenever root URL changes

Previously the header was stripped only if the hostname changed, but in
an https -> http redirect that can leak the credentials on the wire
(#4716). Based on with RFC 7235 section 2.2, the header is now stripped
if the "canonical root URL" (scheme+authority) has changed, by checking
scheme, hostname and port.
---
From 857e9b7ac20c3accf4cc328f594aecb8b6a644a6 Mon Sep 17 00:00:00 2001
From: Bruce Merry <bmerry@ska.ac.za>
Date: Tue, 14 Aug 2018 13:30:43 +0200
Subject: [PATCH 2/2] Rework authorization stripping logic as discussed

The exception for http->https upgrade now requires the standard HTTP(S)
ports to be used, either implicitly (no port specified) or explicitly.
---
From ea9436a5d6a5934a906e91202349aabf9af75d15 Mon Sep 17 00:00:00 2001
From: Nate Prewitt <Nate.Prewitt@gmail.com>
Date: Sun, 28 Oct 2018 23:17:39 -0700
Subject: [PATCH] proper handling for default ports in auth stripping
---
diff --git a/requests/sessions.py b/requests/sessions.py
index dd525e2ac..702cd73ec 100644
--- a/requests/sessions.py
+++ b/requests/sessions.py
@@ -17,7 +17,7 @@
     cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
 from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
 from .hooks import default_hooks, dispatch_hook
-from .utils import to_key_val_list, default_headers, to_native_string
+from .utils import to_key_val_list, default_headers, to_native_string, DEFAULT_PORTS
 from .exceptions import (
     TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
 from .packages.urllib3._collections import RecentlyUsedContainer
@@ -85,6 +85,31 @@
 
 
 class SessionRedirectMixin(object):
+    def should_strip_auth(self, old_url, new_url):
+            """Decide whether Authorization header should be removed when redirecting"""
+            old_parsed = urlparse(old_url)
+            new_parsed = urlparse(new_url)
+            if old_parsed.hostname != new_parsed.hostname:
+                return True
+            # Special case: allow http -> https redirect when using the standard
+            # ports. This isn't specified by RFC 7235, but is kept to avoid
+            # breaking backwards compatibility with older versions of requests
+            # that allowed any redirects on the same host.
+            if (old_parsed.scheme == 'http' and old_parsed.port in (80, None)
+                    and new_parsed.scheme == 'https' and new_parsed.port in (443, None)):
+                return False
+
+            # Handle default port usage corresponding to scheme.
+            changed_port = old_parsed.port != new_parsed.port
+            changed_scheme = old_parsed.scheme != new_parsed.scheme
+            default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
+            if (not changed_scheme and old_parsed.port in default_port
+                    and new_parsed.port in default_port):
+                return False
+
+            # Standard case: root URI must match
+            return changed_port or changed_scheme
+
     def resolve_redirects(self, resp, req, stream=False, timeout=None,
                           verify=True, cert=None, proxies=None, **adapter_kwargs):
         """Receives a Response. Returns a generator of Responses."""
@@ -190,14 +215,10 @@
         headers = prepared_request.headers
         url = prepared_request.url
 
-        if 'Authorization' in headers:
+        if 'Authorization' in headers and self.should_strip_auth(response.request.url, url):
             # If we get redirected to a new host, we should strip out any
             # authentication headers.
-            original_parsed = urlparse(response.request.url)
-            redirect_parsed = urlparse(url)
-
-            if (original_parsed.hostname != redirect_parsed.hostname):
-                del headers['Authorization']
+            del headers['Authorization']
 
         # .netrc might have more auth for us on our new host.
         new_auth = get_netrc_auth(url) if self.trust_env else None
diff --git a/requests/utils.py b/requests/utils.py
index 0ce7fe115..04145c8eb 100644
--- a/requests/utils.py
+++ b/requests/utils.py
@@ -34,6 +34,7 @@
 
 DEFAULT_CA_BUNDLE_PATH = certs.where()
 
+DEFAULT_PORTS = {'http': 80, 'https': 443}
 
 def dict_to_sequence(d):
     """Returns an internal sequence dictionary update."""
