utils.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import cookielib
  2. import httplib
  3. import logging
  4. from optparse import OptionParser
  5. import os
  6. import sys
  7. import urllib2
  8. if sys.version_info < (2, 6, 2):
  9. from ndg.httpsclient.urllib2_proxy import HTTPHandler
  10. else:
  11. from urllib2 import HTTPHandler
  12. import urlparse
  13. from ndg.httpsclient.urllib2_build_opener import build_opener
  14. from ndg.httpsclient.https import HTTPSContextHandler
  15. from ndg.httpsclient import ssl_context_util
  16. class URLFetchError(Exception):
  17. """Error fetching content from URL"""
  18. def fetch_from_url(url, config):
  19. """Returns data retrieved from a URL.
  20. @param url - URL to attempt to open
  21. @param config - configuration
  22. @return data retrieved from URL or None
  23. """
  24. return_code, return_message, response = open_url(url, config)
  25. if return_code and return_code == httplib.OK:
  26. return_data = response.read()
  27. response.close()
  28. return return_data
  29. else:
  30. raise URLFetchError(return_message)
  31. def fetch_from_url_to_file(url, config, output_file):
  32. """Writes data retrieved from a URL to a file.
  33. @param url - URL to attempt to open
  34. @param config - configuration
  35. @param output_file - output file
  36. @return tuple (
  37. returned HTTP status code or 0 if an error occurred
  38. returned message
  39. boolean indicating whether access was successful
  40. )
  41. """
  42. return_code, return_message, response = open_url(url, config)
  43. if return_code == httplib.OK:
  44. return_data = response.read()
  45. response.close()
  46. outfile = open(output_file, "w")
  47. outfile.write(return_data)
  48. outfile.close()
  49. return return_code, return_message, return_code == httplib.OK
  50. def open_url(url, config):
  51. """Attempts to open a connection to a specified URL.
  52. @param url - URL to attempt to open
  53. @param config - configuration
  54. @return tuple (
  55. returned HTTP status code or 0 if an error occurred
  56. returned message or error description
  57. response object
  58. )
  59. """
  60. debuglevel = 1 if config.debug else 0
  61. # Set up handlers for URL opener.
  62. cj = cookielib.CookieJar()
  63. cookie_handler = urllib2.HTTPCookieProcessor(cj)
  64. handlers = [cookie_handler]
  65. if config.debug:
  66. http_handler = HTTPHandler(debuglevel=debuglevel)
  67. https_handler = HTTPSContextHandler(config.ssl_context,
  68. debuglevel=debuglevel)
  69. handlers.extend([http_handler, https_handler])
  70. # Explicitly remove proxy handling if the host is one listed in the value of
  71. # the no_proxy environment variable because urllib2 does use proxy settings
  72. # set via http_proxy and https_proxy, but does not take the no_proxy value
  73. # into account.
  74. if not _should_use_proxy(url):
  75. handlers.append(urllib2.ProxyHandler({}))
  76. if config.debug:
  77. print "Not using proxy"
  78. opener = build_opener(config.ssl_context, *handlers)
  79. # Open the URL and check the response.
  80. return_code = 0
  81. return_message = ''
  82. response = None
  83. try:
  84. response = opener.open(url)
  85. if response.url == url:
  86. return_message = response.msg
  87. return_code = response.code
  88. else:
  89. return_message = 'Redirected (%s %s)' % response.code, response.url
  90. if config.debug:
  91. for index, cookie in enumerate(cj):
  92. print index, ' : ', cookie
  93. except urllib2.HTTPError, exc:
  94. return_code = exc.code
  95. return_message = "Error: %s" % exc.msg
  96. if config.debug:
  97. print exc.code, exc.msg
  98. except Exception, exc:
  99. return_message = "Error: %s" % exc.__str__()
  100. if config.debug:
  101. print exc.__class__, exc.__str__()
  102. return (return_code, return_message, response)
  103. def _should_use_proxy(url):
  104. """Determines whether a proxy should be used to open a connection to the
  105. specified URL, based on the value of the no_proxy environment variable.
  106. @param url - URL string
  107. """
  108. no_proxy = os.environ.get('no_proxy', '')
  109. urlObj = urlparse.urlparse(url)
  110. for np in [h.strip() for h in no_proxy.split(',')]:
  111. if urlObj.hostname == np:
  112. return False
  113. return True
  114. class Configuration(object):
  115. """Checker configuration.
  116. """
  117. def __init__(self, ssl_context, debug):
  118. """
  119. @param key_file - file containing the user's private key
  120. @param cert_file - file containing the user's certificate
  121. @param debug - if True, output debugging information
  122. """
  123. self.ssl_context = ssl_context
  124. self.debug = debug
  125. def main():
  126. '''Utility to fetch data using HTTP or HTTPS GET from a specified URL.
  127. '''
  128. parser = OptionParser(usage="%prog [options] url")
  129. parser.add_option("-k", "--private-key", dest="key_file", metavar="FILE",
  130. default=None,
  131. help="Private key file.")
  132. parser.add_option("-c", "--certificate", dest="cert_file", metavar="FILE",
  133. default=os.path.expanduser("~/credentials.pem"),
  134. help="Certificate file.")
  135. parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir",
  136. metavar="PATH",
  137. default=None,
  138. help="Trusted CA certificate file directory.")
  139. parser.add_option("-d", "--debug", action="store_true", dest="debug",
  140. default=False,
  141. help="Print debug information.")
  142. parser.add_option("-f", "--fetch", dest="output_file", metavar="FILE",
  143. default=None, help="Output file.")
  144. parser.add_option("-n", "--no-verify-peer", action="store_true",
  145. dest="no_verify_peer", default=False,
  146. help="Skip verification of peer certificate.")
  147. (options, args) = parser.parse_args()
  148. if len(args) != 1:
  149. parser.error("Incorrect number of arguments")
  150. url = args[0]
  151. if options.key_file and os.path.exists(options.key_file):
  152. key_file = options.key_file
  153. else:
  154. key_file = None
  155. if options.cert_file and os.path.exists(options.cert_file):
  156. cert_file = options.cert_file
  157. else:
  158. cert_file = None
  159. if options.ca_dir and os.path.exists(options.ca_dir):
  160. ca_dir = options.ca_dir
  161. else:
  162. ca_dir = None
  163. verify_peer = not options.no_verify_peer
  164. # If a private key file is not specified, the key is assumed to be stored in
  165. # the certificate file.
  166. ssl_context = ssl_context_util.make_ssl_context(key_file,
  167. cert_file,
  168. None,
  169. ca_dir,
  170. verify_peer,
  171. url)
  172. config = Configuration(ssl_context, options.debug)
  173. if options.output_file:
  174. return_code, return_message = fetch_from_url_to_file(url,
  175. config,
  176. options.output_file)[:2]
  177. raise SystemExit(return_code, return_message)
  178. else:
  179. data = fetch_from_url(url, config)
  180. print(data)
  181. if __name__=='__main__':
  182. logging.basicConfig()
  183. main()