utils.py 7.3 KB

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