utils.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. import traceback
  99. print traceback.format_exc()
  100. return (return_code, return_message, response)
  101. def _should_use_proxy(url):
  102. """Determines whether a proxy should be used to open a connection to the
  103. specified URL, based on the value of the no_proxy environment variable.
  104. @param url: URL
  105. @type url: basestring
  106. """
  107. no_proxy = os.environ.get('no_proxy', '')
  108. urlObj = urlparse.urlparse(url)
  109. for np in [h.strip() for h in no_proxy.split(',')]:
  110. if urlObj.hostname == np:
  111. return False
  112. return True
  113. class Configuration(object):
  114. """Checker configuration.
  115. """
  116. def __init__(self, ssl_context, debug):
  117. """
  118. @param ssl_context: SSL context to use with this configuration
  119. @type ssl_context: OpenSSL.SSL.Contex @param debug: if True, output debugging information
  120. @type debug: boo
  121. """
  122. self.ssl_context = ssl_context
  123. self.debug = debug
  124. def main():
  125. '''Utility to fetch data using HTTP or HTTPS GET from a specified URL.
  126. '''
  127. parser = OptionParser(usage="%prog [options] url")
  128. parser.add_option("-k", "--private-key", dest="key_file", metavar="FILE",
  129. default=None,
  130. help="Private key file.")
  131. parser.add_option("-c", "--certificate", dest="cert_file", metavar="FILE",
  132. default=os.path.expanduser("~/credentials.pem"),
  133. help="Certificate file.")
  134. parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir",
  135. metavar="PATH",
  136. default=None,
  137. help="Trusted CA certificate file directory.")
  138. parser.add_option("-d", "--debug", action="store_true", dest="debug",
  139. default=False,
  140. help="Print debug information.")
  141. parser.add_option("-f", "--fetch", dest="output_file", metavar="FILE",
  142. default=None, help="Output file.")
  143. parser.add_option("-n", "--no-verify-peer", action="store_true",
  144. dest="no_verify_peer", default=False,
  145. help="Skip verification of peer certificate.")
  146. (options, args) = parser.parse_args()
  147. if len(args) != 1:
  148. parser.error("Incorrect number of arguments")
  149. url = args[0]
  150. if options.key_file and os.path.exists(options.key_file):
  151. key_file = options.key_file
  152. else:
  153. key_file = None
  154. if options.cert_file and os.path.exists(options.cert_file):
  155. cert_file = options.cert_file
  156. else:
  157. cert_file = None
  158. if options.ca_dir and os.path.exists(options.ca_dir):
  159. ca_dir = options.ca_dir
  160. else:
  161. ca_dir = None
  162. verify_peer = not options.no_verify_peer
  163. # If a private key file is not specified, the key is assumed to be stored in
  164. # the certificate file.
  165. ssl_context = ssl_context_util.make_ssl_context(key_file,
  166. cert_file,
  167. None,
  168. ca_dir,
  169. verify_peer,
  170. url)
  171. config = Configuration(ssl_context, options.debug)
  172. if options.output_file:
  173. return_code, return_message = fetch_from_url_to_file(url,
  174. config,
  175. options.output_file)[:2]
  176. raise SystemExit(return_code, return_message)
  177. else:
  178. data = fetch_from_url(url, config)
  179. print(data)
  180. if __name__=='__main__':
  181. logging.basicConfig()
  182. main()