import cookielib
import httplib
import logging
from optparse import OptionParser
import os
import urllib2
import urlparse

from OpenSSL import SSL

from urllib2pyopenssl.urllib2_build_opener import urllib2_build_opener
from urllib2pyopenssl.https import HTTPSContextHandler
import urllib2pyopenssl.ssl_context_util as ssl_context_util
from urllib2pyopenssl.ssl_peer_verification import ServerSSLCertVerification

def fetch_from_url(url, config):
    """Returns data retrieved from a URL.
    @param url - URL to attempt to open
    @param config - configuration
    @return data retrieved from URL or None
    """
    (return_code, return_message, response) = open_url(url, config)
    if return_code and return_code == httplib.OK:
        return_data = response.read()
        response.close()
        return return_data
    else:
        raise Exception(return_message)

def fetch_from_url_to_file(url, config, output_file):
    """Writes data retrieved from a URL to a file.
    @param url - URL to attempt to open
    @param config - configuration
    @param output_file - output file
    @return tuple (
        returned HTTP status code or 0 if an error occurred
        returned message
        boolean indicating whether access was successful
    )
    """
    (return_code, return_message, response) = open_url(url, config)
    if return_code == httplib.OK:
        return_data = response.read()
        response.close()
        outfile = open(output_file, "w")
        outfile.write(return_data)
        outfile.close()
    return (return_code, return_message, (return_code == httplib.OK))

def open_url(url, config):
    """Attempts to open a connection to a specified URL.
    @param url - URL to attempt to open
    @param config - configuration
    @return tuple (
        returned HTTP status code or 0 if an error occurred
        returned message or error description
        response object
    )
    """
    debuglevel = 1 if config.debug else 0

    # Set up handlers for URL opener.
    cj = cookielib.CookieJar()
    cookie_handler = urllib2.HTTPCookieProcessor(cj)

    handlers = [cookie_handler]

    if config.debug:
        http_handler = urllib2.HTTPHandler(debuglevel=debuglevel)
        https_handler = HTTPSContextHandler(config.ssl_context, debuglevel=debuglevel)
        handlers.extend([http_handler, https_handler])

    # Explicitly remove proxy handling if the host is one listed in the value of the no_proxy
    # environment variable because urllib2 does use proxy settings set via http_proxy and
    # https_proxy, but does not take the no_proxy value into account.
    if not _should_use_proxy(url):
        handlers.append(urllib2.ProxyHandler({}))
        if config.debug:
            print "Not using proxy"

    opener = urllib2_build_opener(config.ssl_context, *handlers)

    # Open the URL and check the response.
    return_code = 0
    return_message = ''
    response = None
    try:
        response = opener.open(url)
        if response.url == url:
            return_message = response.msg
            return_code = response.code
        else:
            return_message = ('Redirected (%s  %s)' % (response.code, response.url))
        if config.debug:
            for index, cookie in enumerate(cj):
                print index, '  :  ', cookie        
    except urllib2.HTTPError, exc:
        return_code = exc.code
        return_message = ("Error: %s" % exc.msg)
        if config.debug:
            print exc.code, exc.msg
    except Exception, exc:
        return_message = ("Error: %s" % exc.__str__())
        if config.debug:
            print exc.__class__, exc.__str__()
    return (return_code, return_message, response)

def _should_use_proxy(url):
    """Determines whether a proxy should be used to open a connection to the specified URL, based on
        the value of the no_proxy environment variable.
    @param url - URL string
    """
    no_proxy = os.environ.get('no_proxy', '')

    urlObj = urlparse.urlparse(url)
    for np in [h.strip() for h in no_proxy.split(',')]:
        if urlObj.hostname == np:
            return False

    return True

class Configuration(object):
    """Checker configuration.
    """
    def __init__(self, ssl_context, debug):
        """
        @param key_file - file containing the user's private key
        @param cert_file - file containing the user's certificate
        @param debug - if True, output debugging information
        """
        self.ssl_context = ssl_context
        self.debug = debug

def main():
    '''Utility to fetch data using HTTP or HTTPS GET from a specified URL.
    '''
    parser = OptionParser(usage="%prog [options] url")
    parser.add_option("-k", "--private-key", dest="key_file", metavar="FILE",
                      default=None,
                      help="Private key file.")
    parser.add_option("-c", "--certificate", dest="cert_file", metavar="FILE",
                      default=os.path.expanduser("~/credentials.pem"),
                      help="Certificate file.")
    parser.add_option("-t", "--ca-certificate-dir", dest="ca_dir", metavar="PATH",
                      default=None,
                      help="Trusted CA certificate file directory.")
    parser.add_option("-d", "--debug", action="store_true", dest="debug", default=False,
                      help="Print debug information.")
    parser.add_option("-f", "--fetch", dest="output_file", metavar="FILE",
                      default=None, help="Output file.")
    parser.add_option("-v", "--verify-peer", action="store_true", dest="verify_peer", default=False,
                      help="Verify peer certificate.")
    (options, args) = parser.parse_args()
    if len(args) != 1:
        parser.error("Incorrect number of arguments")

    url = args[0]
    # If a private key file is not specified, the key is assumed to be stored in the certificate file.
    ssl_context = ssl_context_util.make_ssl_context(
        options.key_file if options.key_file and os.path.exists(options.key_file) else None,
        options.cert_file if options.cert_file and os.path.exists(options.cert_file) else None,
        None,
        options.ca_dir if options.ca_dir and os.path.exists(options.ca_dir) else None,
        options.verify_peer, url)

    config = Configuration(ssl_context, options.debug)
    if options.output_file:
        (return_code, return_message, success) = fetch_from_url_to_file(url, config,
            options.output_file)
        print return_code, return_message
    else:
        data = fetch_from_url(url, config)
        print data

if __name__=='__main__':
    logging.basicConfig()
    main()