Skip to content

Commit

Permalink
the types sharpened again and the tests improved
Browse files Browse the repository at this point in the history
  • Loading branch information
heikoschmidt committed Mar 9, 2024
1 parent 8269e53 commit 1dc6947
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 35 deletions.
13 changes: 7 additions & 6 deletions src/examples/unittests.pac
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
function FindProxyForURL(url, host) {
host = host.toLowerCase();
url = url.toLowerCase();
if (
isInNet(host, "93.184.0.0", "255.255.0.0")
|| localHostOrDomainIs(host, "example.net")
|| dnsResolve(host) === "192.0.0.170"
|| dnsResolve(host) === "127.0.0.1"
) { return "PROXY netmask.example.com"; }
if (
localHostOrDomainIs(host, "example.com")
|| localHostOrDomainIs(host, "foo.example.com")
|| localHostOrDomainIs(host, "bar.example.com")
) { return "PROXY company.example.com"; }
if (
isInNet(host, "93.184.0.0", "255.255.0.0")
|| localHostOrDomainIs(host, "example.net")
|| isInNet(host, "192.0.0.170", "255.255.255.255")
|| isInNet(host, "127.0.0.1", "255.255.255.255")
) { return "PROXY netmask.example.com"; }
if (
dnsDomainIs(host, ".example.com")
) { return "DIRECT"; }
Expand Down
61 changes: 49 additions & 12 deletions src/pypacer/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,60 @@
location = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))


def is_ipaddress(ip) -> bool:
try:
ipaddress.ip_address(ip)
return True
except ValueError:
return False


def is_network(ip) -> bool:
try:
ipaddress.ip_network(ip)
return True
except ValueError:
return False


def is_hostname(hostname):
if len(hostname) > 255 or not hostname:
return False
hostname = hostname.rstrip(".") if hostname.endswith(".") else hostname
hostname = hostname.lstrip(".") if hostname.startswith(".") else hostname
regex = r"^((?!-)[\w-]" + "{1,63}(?<!-)\\.)" + "+[A-Za-z]{2,6}"
p = re.compile(regex)
if re.search(p, hostname):
return True
return False


def is_regex(regex):
if not re.match(r"^/.+/[ailmsux]*$", regex, re.IGNORECASE):
return False
try:
re.compile(regex)
return True
except re.error:
return False


def get_target_type(target: str) -> str:
if re.fullmatch(r"[0-9.]+", target) and target.count(".") == 3:
return "IP4"
elif re.fullmatch(r"[0-9./]+", target):
return "NET_MASK"
elif target.startswith("."):
return "HOSTS"
return "HOST"
if is_ipaddress(target):
return "IP"
elif is_network(target):
return "NETWORK"
elif is_hostname(target):
if target.startswith("."):
return "HOSTS"
return "HOST"
elif is_regex(target):
return "REGEX"
return "STRING"


def sort_by_rating(proxy):
rating = 0
for target in proxy.targets:
rating += target.rating
return rating


def compute_netmask(netmask) -> list[str]:
ip4_network = ipaddress.ip_network(netmask)
return ip4_network.with_netmask.split("/")
45 changes: 37 additions & 8 deletions src/pypacer/helpers_test.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,55 @@
import unittest

from pypacer.helpers import get_target_type, sort_by_rating, compute_netmask
from pypacer.helpers import get_target_type, sort_by_rating, is_ipaddress, is_network, \
is_hostname, is_regex
from pypacer.pypacerconfig import PyPacerConfig


class TestHelpers(unittest.TestCase):

def test_is_ipaddress(self):
self.assertTrue(is_ipaddress("10.11.12.13"))
self.assertFalse(is_ipaddress("10.11.12.13.14"))
self.assertFalse(is_ipaddress("example.com"))
self.assertTrue(is_ipaddress("2001:0db8:85a3:08d3:1319:8a2e:0370:7344"))

def test_is_network(self):
self.assertTrue(is_network("10.11.12.13/32"))
self.assertFalse(is_network("10.11.12.13.14"))
self.assertFalse(is_network("example.com"))
self.assertTrue(is_network("2001:0db8:85a3:08d3::/64"))

def test_target_type_ip_mask(self):
self.assertEqual(get_target_type("127.0.0.0/24"), "NET_MASK")
self.assertEqual(get_target_type("127.0.0.0/24"), "NETWORK")

def test_target_type_ip(self):
self.assertEqual(get_target_type("127.0.0.1"), "IP4")
self.assertEqual(get_target_type("127.0.0.1"), "IP")

def test_target_type_hosts(self):
self.assertEqual(get_target_type(".example.com"), "HOSTS")

def test_target_type_host(self):
self.assertEqual(get_target_type("example.com"), "HOST")
self.assertEqual(get_target_type("example.com."), "HOST")

def test_target_type_regex(self):
self.assertEqual(get_target_type("/10./"), "REGEX")

def test_target_type_string(self):
self.assertEqual(get_target_type("10."), "STRING")

def test_is_hostname(self):
self.assertTrue(is_hostname("example.com"))
self.assertTrue(is_hostname("foo.example.com"))
self.assertTrue(is_hostname("foo.bar.example"))
self.assertTrue(is_hostname("test-123.foo.bar.example-de"))
self.assertTrue(is_hostname("exämple.com"))
self.assertTrue(is_hostname(".exämple.com"))
self.assertTrue(is_hostname(".exämple.com."))
self.assertTrue(is_hostname("exämple.com."))

def test_is_regex(self):
self.assertTrue(is_regex("/10./"))

def test_sort_by_rating(self):
config = {"proxies": {"A": {"route": "A", "targets": [".example.com"]},
Expand All @@ -25,10 +58,6 @@ def test_sort_by_rating(self):
config.validate()
proxies = [p for p in config.proxies.values()]
self.assertEqual(proxies[0].route, "A")
self.assertEqual(proxies[0].targets[0].rating, 2)
self.assertEqual(proxies[0].targets[0].rating, 1)
proxies.sort(key=sort_by_rating)
self.assertEqual(proxies[0].route, "B")

def test_compute_netmask(self):
self.assertEqual(compute_netmask("99.77.128.0/18"), ["99.77.128.0", "255.255.192.0"])
self.assertEqual(compute_netmask("93.184.0.0/16"), ["93.184.0.0", "255.255.0.0"])
13 changes: 5 additions & 8 deletions src/pypacer/pypacerconfig.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ipaddress
from dataclasses import dataclass, field

from pypacer.helpers import get_target_type, compute_netmask
from pypacer.helpers import get_target_type


class Target:
Expand All @@ -9,18 +10,14 @@ def __init__(self, target: str):
self.rating = 0
self.type = get_target_type(target)
self.netmask = None
if self.type == "NET_MASK":
self.netmask = compute_netmask(self.target)
elif self.type == "IP4":
self.netmask = compute_netmask(f"{self.target}/32")
if self.type == "NETWORK":
self.netmask = ipaddress.ip_network(self.target).with_netmask.split("/")

def recognize_overlaps(self, targets: list):
if self.type == "HOSTS":
for target in targets:
if target.type == "HOST" and target.target.endswith(self.target):
self.rating = target.rating + 2
if self.type == "NET_MASK":
self.rating = self.rating + 1
self.rating = target.rating + 1


@dataclass
Expand Down
5 changes: 4 additions & 1 deletion src/pypacer/template.js.jinja
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
function FindProxyForURL(url, host) {
host = host.toLowerCase();
url = url.toLowerCase();
{%- for proxy in proxies %}
if (
{%- for target in proxy.targets %}
{% if loop.index > 1 %}|| {% else %} {% endif -%}
{%- if target.type == "HOST" -%}localHostOrDomainIs(host, "{{ target.target }}")
{%- elif target.type == "HOSTS" -%}dnsDomainIs(host, "{{ target.target }}")
{%- elif target.type in ("NET_MASK", "IP4") -%}isInNet(host, "{{ target.netmask[0] }}", "{{ target.netmask[1] }}")
{%- elif target.type == "NETWORK" -%}isInNet(host, "{{ target.netmask[0] }}", "{{ target.netmask[1] }}")
{%- elif target.type == "REGEX" -%}shExpMatch(url, "*{{ target.target }}*")
{%- elif target.type == "IP" -%}dnsResolve(host) === "{{ target.target }}"
{%- else -%}host === "{{ target.target }}"
{%- endif -%}
{%- endfor %}
Expand Down

0 comments on commit 1dc6947

Please sign in to comment.