Update cipd.py with changes from downstream

Change-Id: I88cabc042b5907d4b8a7693d1d502fba37e941e5
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/prebuilt/+/561135
Reviewed-by: Oliver Newman <olivernewman@google.com>
diff --git a/cipd.py b/cipd.py
index c2ef723..5c45101 100755
--- a/cipd.py
+++ b/cipd.py
@@ -18,16 +18,42 @@
 import ssl
 import subprocess
 import sys
+import base64
 
 try:
-    import httplib
+    import httplib  # Python 2
 except ImportError:
-    import http.client as httplib  # type: ignore
+    import http.client as httplib
 
 try:
-    import urlparse  # Python 2.
+    import urlparse  # Python 2
 except ImportError:
-    import urllib.parse as urlparse  # type: ignore
+    import urllib.parse as urlparse
+
+# Generated from the following command. May need to be periodically rerun.
+# $ cipd ls infra/tools/cipd | perl -pe "s[.*/][];s/^/    '/;s/\s*$/',\n/;"
+SUPPORTED_PLATFORMS = (
+    "aix-ppc64",
+    "linux-386",
+    "linux-amd64",
+    "linux-arm64",
+    "linux-armv6l",
+    "linux-mips64",
+    "linux-mips64le",
+    "linux-mipsle",
+    "linux-ppc64",
+    "linux-ppc64le",
+    "linux-s390x",
+    "mac-amd64",
+    "mac-arm64",
+    "windows-386",
+    "windows-amd64",
+)
+
+
+class UnsupportedPlatform(Exception):
+    pass
+
 
 SCRIPT_DIR = os.path.dirname(__file__)
 VERSION_FILE = os.path.join(SCRIPT_DIR, ".cipd_version")
@@ -43,9 +69,12 @@
 
     try:
         os_name = platform.system().lower()
-        return {"linux": "linux", "mac": "mac", "darwin": "mac", "windows": "windows",}[
-            os_name
-        ]
+        return {
+            "linux": "linux",
+            "mac": "mac",
+            "darwin": "mac",
+            "windows": "windows",
+        }[os_name]
     except KeyError:
         raise Exception("unrecognized os: {}".format(os_name))
 
@@ -104,6 +133,30 @@
     raise Exception("platform {} not in {}".format(expected_plat, DIGESTS_FILE))
 
 
+def https_connect_with_proxy(target_url):
+    """Create HTTPSConnection with proxy support."""
+
+    proxy_env = os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy")
+    if proxy_env in (None, ""):
+        conn = httplib.HTTPSConnection(target_url)
+        return conn
+
+    url = urlparse.urlparse(proxy_env)
+    conn = httplib.HTTPSConnection(url.hostname, url.port)
+    headers = {}
+    if url.username and url.password:
+        auth = "%s:%s" % (url.username, url.password)
+        py_version = sys.version_info.major
+        if py_version >= 3:
+            headers["Proxy-Authorization"] = "Basic " + str(
+                base64.b64encode(auth.encode()).decode()
+            )
+        else:
+            headers["Proxy-Authorization"] = "Basic " + base64.b64encode(auth)
+    conn.set_tunnel(target_url, 443, headers)
+    return conn
+
+
 def client_bytes():
     """Pull down the CIPD client and return it as a bytes object.
 
@@ -116,7 +169,7 @@
         version = ins.read().strip()
 
     try:
-        conn = httplib.HTTPSConnection(CIPD_HOST)
+        conn = https_connect_with_proxy(CIPD_HOST)
     except AttributeError:
         print("=" * 70)
         print(
@@ -132,9 +185,11 @@
         print("=" * 70)
         raise
 
-    path = "/client?platform={platform}-{arch}&version={version}".format(
-        platform=platform_normalized(), arch=arch_normalized(), version=version
-    )
+    full_platform = "{}-{}".format(platform_normalized(), arch_normalized())
+    if full_platform not in SUPPORTED_PLATFORMS:
+        raise UnsupportedPlatform(full_platform)
+
+    path = "/client?platform={}&version={}".format(full_platform, version)
 
     for _ in range(10):
         try:
@@ -151,12 +206,21 @@
                 "\n"
                 "    sudo pip install certifi\n"
                 "\n"
+                "And if on the system Python on a Mac try\n"
+                "\n"
+                "    /Applications/Python 3.6/Install Certificates.command\n"
+                "\n"
                 "If using Homebrew Python try\n"
                 "\n"
                 "    brew install openssl\n"
                 "    brew uninstall python\n"
                 "    brew install python\n"
                 "\n"
+                "If those don't work, address all the potential issues shown \n"
+                "by the following command.\n"
+                "\n"
+                "    brew doctor\n"
+                "\n"
                 "Otherwise, check that your machine's Python can use SSL, "
                 "testing with the httplib module on Python 2 or http.client on "
                 "Python 3.",
@@ -173,14 +237,16 @@
             location = res.getheader("location")
             url = urlparse.urlparse(location)
             if url.netloc != conn.host:
-                conn = httplib.HTTPSConnection(url.netloc)
+                conn = https_connect_with_proxy(url.netloc)
             path = "{}?{}".format(url.path, url.query)
 
         # Some kind of error in this response.
         else:
             break
 
-    raise Exception("failed to download client")
+    raise Exception(
+        "failed to download client from https://{}{}".format(CIPD_HOST, path)
+    )
 
 
 def bootstrap(client, silent=False):
@@ -233,19 +299,28 @@
 
     os.environ["CIPD_HTTP_USER_AGENT_PREFIX"] = user_agent()
 
-    try:
-        if not os.path.isfile(client):
-            bootstrap(client, silent)
+    if not os.path.isfile(client):
+        bootstrap(client, silent)
 
-        try:
-            selfupdate(client)
-        except subprocess.CalledProcessError:
-            print(
-                "CIPD selfupdate failed. Bootstrapping then retrying...",
-                file=sys.stderr,
-            )
-            bootstrap(client)
-            selfupdate(client)
+    try:
+        selfupdate(client)
+    except subprocess.CalledProcessError:
+        print("CIPD selfupdate failed. Bootstrapping then retrying...", file=sys.stderr)
+        bootstrap(client)
+        selfupdate(client)
+
+    return client
+
+
+def main(client=CLIENT, silent=False):
+    """Install/update cipd client."""
+
+    try:
+        init(client=client, silent=silent)
+
+    except UnsupportedPlatform:
+        # Don't show help message below for this exception.
+        raise
 
     except Exception:
         print(
@@ -253,7 +328,9 @@
             "`CIPD_HTTP_USER_AGENT_PREFIX={user_agent}/manual {client} "
             "selfupdate -version-file '{version_file}'` "
             "to diagnose if this is persistent.".format(
-                user_agent=user_agent(), client=client, version_file=VERSION_FILE,
+                user_agent=user_agent(),
+                client=client,
+                version_file=VERSION_FILE,
             ),
             file=sys.stderr,
         )
@@ -263,5 +340,5 @@
 
 
 if __name__ == "__main__":
-    client_exe = init()
+    client_exe = main()
     subprocess.check_call([client_exe] + sys.argv[1:])