1# -*- coding: utf-8 -*- #
2# Copyright 2015 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Helper function to open a url using a proxy using httlib2 connections."""
17
18from __future__ import absolute_import
19from __future__ import division
20from __future__ import unicode_literals
21
22from googlecloudsdk.core import http_proxy
23from googlecloudsdk.core import properties
24
25import httplib2
26from six.moves import urllib
27
28
29class HttplibConnectionHandler(urllib.request.HTTPHandler,
30                               urllib.request.HTTPSHandler):
31  """urllib2 Handler Class to use httplib2 connections.
32
33  This handler makes urllib2 use httplib2.HTTPSConnectionWithTimeout. The
34  httplib2 connections can handle both HTTP and SOCKS proxies, passed via the
35  ProxyInfo object. It also has CA_CERTS files and validates SSL certificates.
36
37  The handler also IDNA encodes the host it's connecting to. socks library with
38  socks5 proxy throws an odd encode exception even for ANSII hostnames if encode
39  is not called.
40  """
41
42  def http_open(self, req):
43    def build(host, **kwargs):
44      proxy_info = http_proxy.GetHttpProxyInfo()
45      if callable(proxy_info):
46        proxy_info = proxy_info('http')
47      return httplib2.HTTPConnectionWithTimeout(
48          host.encode('idna').decode(),
49          proxy_info=proxy_info,
50          **kwargs)
51    return self.do_open(build, req)
52
53  def https_open(self, req):
54    def build(host, **kwargs):
55      proxy_info = http_proxy.GetHttpProxyInfo()
56      if callable(proxy_info):
57        proxy_info = proxy_info('https')
58      ca_certs = properties.VALUES.core.custom_ca_certs_file.Get()
59      return httplib2.HTTPSConnectionWithTimeout(
60          host.encode('idna').decode(),
61          proxy_info=proxy_info,
62          ca_certs=ca_certs,
63          **kwargs)
64    return self.do_open(build, req)
65
66
67# TODO(b/120992538) Use urllib3 when PROXY/USE_URLLIB3_VIA_SHIM
68def urlopen(req, data=None, timeout=60):
69  """Helper function that mimics urllib2.urlopen, but adds proxy information."""
70
71  # We need to pass urllib2.ProxyHandler({}) to disable the proxy handling in
72  # urllib2 open. If we don't, then urllib will substitute the host with the
73  # proxy address and the HttplibConnectionHandler won't get the original host.
74  # (the default urllib2.HTTPSHandler needs this substitution trickery)
75  # We do the proxy detection in http_proxy.GetHttpProxyInfo and pass it to
76  # httplib2.HTTPSConnectionWithTimeout via proxy info object.
77  # httplib2.HTTPSConnectionWithTimeout takes care of handling proxies.
78  opener = urllib.request.build_opener(urllib.request.ProxyHandler({}),
79                                       HttplibConnectionHandler())
80  return opener.open(req, data, timeout)
81