from exploits.regex import redos
from exploits.regex.regex_common import RegexParser
from test.exploits.dummy_output import DummyOutput
import pytest


def test_run_redos_wikipedia_vuln1():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a+)+x'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1


def test_run_redos_wikipedia_vuln2():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '([a-zA-Z]+)*\.'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1


def test_run_redos_wikipedia_vuln3():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|aa)+[A-Z0-9]'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert output[0] == 'a' * length


def test_run_redos_wikipedia_vuln4():
    output = DummyOutput()
    length = 30
    redos.options['regex'] = '[0-5]{7}_(a|a?)+5'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    for i in range(7):
        assert (int(output[0][i]) >= 0) and (int(output[0][i]) <= 5)
    assert output[0][7:] == '_' + ('a' * (length - 8))


def test_run_redos_wikipedia_vuln6():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(([a-z])+.)+[A-Z]([a-z])+'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1


def test_run_redos_wikipedia_vuln7():
    output = DummyOutput()
    length = 50
    redos.options['regex'] = '([a-zA-Z0-9])(([-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1

def test_run_redos_wikipedia_vuln7_negations():
    output = DummyOutput()
    length = 50
    redos.options['regex'] = '([^a])(([-.]|[_]+)?([^a]+))*(@){1}[^a]+[.]{1}(([^a]{2,3})|([^a]{2,3}[.]{1}[^a]{2,3}))'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1


def test_run_redos_stackoverflow():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = r'((ab)*)+\\'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1


def test_run_redos():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|b|ab)*c'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = True
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0] == 'ab' * (length // len('ab'))

def test_run_redos_dot():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(.|.)*c'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length

    # dots are replaced with 'a' in stringify_result
    assert output[0] == 'a' * length


def test_run_redos_quadratic():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|b)*(a|c)*d'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = True
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0] == 'a' * length


def test_run_redos_quadratic_but_excluding_quadratic():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|b)*(a|c)*d'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['parallelize'] = False
    with pytest.raises(ValueError):
        redos.run(output)


def test_run_redos_no_vulnerability():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|b)*c'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['parallelize'] = False
    with pytest.raises(ValueError):
        redos.run(output)


def test_run_redos_longer_length():
    output = DummyOutput()
    length = 100
    redos.options['regex'] = '(a|b|ab)*c'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0] == 'ab' * (length // len('ab'))


def test_run_redos_no_vulnerability_exponential_missing_terminator():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|b|ab)*'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['parallelize'] = False
    with pytest.raises(ValueError):
        redos.run(output)

def test_run_redos_end_string_anchor():
    output = DummyOutput()
    length = 11
    redos.options['regex'] = '(a|b|ab)*$'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0][:10] == 'ab' * (length // len('ab'))
    assert output[0][10] != 'a'
    assert output[0][10] != 'b'

def test_run_redos_no_vulnerability_quadratic_missing_terminator():
    output = DummyOutput()
    length = 10
    redos.options['regex'] = '(a|b)*(a|c)*'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['parallelize'] = False
    with pytest.raises(ValueError):
        redos.run(output)

def test_exponential_whitespace_word_digit_whitespace_terminator():
    output = DummyOutput()
    length = 11
    redos.options['regex'] = r'^\s*(\w|\d)+\s*$'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    for i in output[0][:-2]:
        assert i >= '0' and i <= '9'
    assert not (output[0][length-1] >= '0' and output[0][length-1] <= '9')

def test_multicharacter_terminator():
    output = DummyOutput()
    length = 11
    redos.options['regex'] = r'(\w|\d)+(\s*|.)$'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = False
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)

    assert len(output) == 1
    assert len(output[0]) == length
    for i in output[0][:-2]:
        assert i >= '0' and i <= '9'
    assert not (output[0][length-2] >= '0' and output[0][length-2] <= '9')
    assert not (output[0][length-1] >= '0' and output[0][length-1] <= '9')

def test_redos_two_dots():
    output = DummyOutput()
    length = 11
    redos.options['regex'] = '.*b.*a'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = True
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0][1:] ==  ('b' * (length - 1))


def test_redos_negative_number():
    output = DummyOutput()
    length = 11
    redos.options['regex'] = '^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0][0] == '-'
    for i in output[0][1:-1]:
        assert (i >= '0' and i <= '9') or i == '.'
    assert (output[0][-1] < '0' or output[0][-1] > '9') and output[0][-1] != '.'

def test_redos_two_dots_not_vulnerable():
    output = DummyOutput()
    length = 11
    redos.options['regex'] = '.*bal.*a'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    with pytest.raises(ValueError):
        redos.run(output)

def test_redos_two_dots_anchor():
    output = DummyOutput()
    length = 12
    redos.options['regex'] = '.*bal.*a$'
    redos.options['max_length'] = length
    redos.options['show_nfa'] = False
    redos.options['include_quadratic'] = True
    redos.options['use_file'] = False
    redos.options['show_progress_bar'] = False
    redos.options['show_only_vulnerable'] = True
    redos.options['verify'] = False
    redos.options['parallelize'] = False
    redos.run(output)
    assert len(output) == 1
    assert len(output[0]) == length
    assert output[0] == 'bal' * (length // len('bal'))

def test_nfa_intersection_with_dot_accepting_state():
    nfa_1 = RegexParser().run('a.c')
    nfa_2 = RegexParser().run('abc')
    intersection_nfa = nfa_1.intersection(nfa_2)
    assert intersection_nfa.has_accepting_states()

def test_nfa_intersection_both_dot_accepting_state():
    nfa_1 = RegexParser().run('a.c')
    nfa_2 = RegexParser().run('.ac')
    intersection_nfa = nfa_1.intersection(nfa_2)
    assert intersection_nfa.has_accepting_states()

def test_nfa_intersection_with_dot_no_accepting_state():
    nfa_1 = RegexParser().run('a.c')
    nfa_2 = RegexParser().run('abd')
    intersection_nfa = nfa_1.intersection(nfa_2)
    assert not intersection_nfa.has_accepting_states()

def test_verify_string_non_vulnerable_string():
    with pytest.raises(RuntimeError):
        redos.verify_string('ab*c', 'a', 'b', 'c', True)
    with pytest.raises(RuntimeError):
        redos.verify_string('ab*c', 'a', 'b', 'c', False)

def test_verify_string_quadratic_regex_exponential_test():
    with pytest.raises(RuntimeError):
        redos.verify_string('(a|b)*(a|c)*d', '', 'a', '', True)

def test_verify_string_quadratic_regex():
    redos.verify_string('(a|b)*(a|c)*d', '', 'a', '', False)

def test_verify_string_quadratic_regex():
    redos.verify_string('(a|b|ab)*d', '', 'ab', '', True)
