#!/usr/bin/env python2.7

import sys, os, re
from functools import partial

try:
    from lxml import etree
except ImportError:
    sys.stderr.write("** Could not import lxml!  Rule validation SKIPPED.\n")
    sys.stderr.write("** Caution: A resulting build MAY CONTAIN INVALID RULES.\n")
    sys.stderr.write("** Please install libxml2 and lxml to permit validation!\n")
    sys.exit(0)

try:
    from requests import head
    from requests.exceptions import SSLError, ConnectionError, Timeout
except ImportError:
    sys.stderr.write("** Could not import requests!  Rule validation SKIPPED.\n")
    sys.stderr.write("** Caution: A resulting build MAY CONTAIN SITES WHICH DO NOT SUPPORT HTTPS.\n")
    sys.stderr.write("** Please install requests to permit validation!\n")
    sys.exit(0)

if not (sys.argv[1:] and sys.argv[2:]):
    sys.stderr.write("Usage: %s ruleset report_file\n\n" % sys.argv[0])
    sys.exit(0)

# Error lists
certificate = []
timeout = []
redirect = []

ruleset = sys.argv[1]
report_file = sys.argv[2]

request = partial(head, verify=True, timeout=15.0,
        config={'keep_alive': False})
# 'GET' - method, verify - verify the certificate, timeout - 5.0 seconds

def test_response_no_redirect(to):
    """destination may not support HTTPS."""
    ret = True
    try:
        response = request(to, allow_redirects=False)
        if response.status_code in (300, 301, 302, 307, 308):
            find_redirect(to)
            ret = False
        if response.status_code != 200:
            ret = False
        del response
    except SSLError:
    #   sys.stdout.write("failure: %s certificate validity check failed.\n" % to)
        certificate.append(to)
        ret = False
    except (ConnectionError, Timeout):
    #   sys.stdout.write("failure: %s can not be reached.\n" % to)
        timeout.append('%s - timeout' % to)
        ret = False
    except Exception:
        ret = False
    return ret


def find_redirect(to):
    """Prints redirects"""
    try:
        response = request(to, allow_redirects=True)
        url_re = re.compile(re.escape(to))
        if response.status_code == 200 and not url_re.match(response.url):
            # i.e. it redirected and it didn't redirect from something like:
            #  https://www.eff.org/ -> https://www.eff.org/index.html
        #   sys.stdout.write("failure: %s redirects to %s.\n" % (to, response.url))
            redirect.append('%s -> %s' % (to, response.url))
    except SSLError:
        redirect.append('%s - ssl_error' % to)
    except (ConnectionError, Timeout):
    #   sys.stdout.write("failure: %s can not be reached to complete a redirect\n" % to)
        redirect.append('%s - timeout' % to)
    except Exception:
        pass


failure = 0
#failed = []
back_ref = re.compile('\$\d+')

if __name__ == "__main__":
    tree = etree.parse(ruleset)

    seen = []
    for rule in tree.xpath('/ruleset/rule'):
        to = rule.get('to')
        if rule.get('downgrade') or to in seen:
            continue
        if back_ref.search(to):
            continue
        if not test_response_no_redirect(to):
            #failed.append(to)
            failure = 1
        seen.append(to)

    if failure:
        sys.stdout.write("warning: %s failed test: %s\n" % (ruleset, test_response_no_redirect.__doc__))
        with open(report_file, 'a') as fd:
            #fd.write('%s: %s\n' % (ruleset, ', '.join(failed)))
            if certificate:
                fd.write('[%s] Certificate Errors:\n %s\n' %
                        (ruleset, ', '.join(certificate)))
            if redirect:
                fd.write('[%s] Redirect Failures:\n %s\n' %
                        (ruleset, ', '.join(redirect)))
            if timeout:
                fd.write('[%s] Timeout Failures:\n %s\n' %
                        (ruleset, ', '.join(timeout)))

    sys.exit(failure)
