Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/pymod/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include pacparser_py.c
include pacparser/__init__.py
include pacparser/pactester.py
include pacparser.h
include pacparser.c
include pac_utils.h
Expand Down
203 changes: 203 additions & 0 deletions src/pymod/pacparser/pactester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Copyright (C) 2007-2026 Manu Garg.
# Author: Manu Garg <manugarg@gmail.com>
#
# pacparser is a library that provides methods to parse proxy auto-config
# (PAC) files. Please read README file included with this package for more
# information about this library.
#
# pacparser is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# pacparser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

"""
Command line tool to test PAC files. It provides the same interface as the
C pactester tool:

pactester <-p pacfile> <-u url> [-h host] [-c client_ip] [-e]
pactester <-p pacfile> <-f urlslist> [-c client_ip] [-e]
"""

import getopt
import sys

import pacparser

PACMAX = 1024 * 1024 # Max size of the PAC script (1 MiB)

PROGNAME = "pactester"

def usage():
sys.stderr.write("\nUsage: %s <-p pacfile> <-u url> [-h host] "
"[-c client_ip] [-e]\n" % PROGNAME)
sys.stderr.write(" %s <-p pacfile> <-f urlslist> "
"[-c client_ip] [-e]\n\n" % PROGNAME)
sys.stderr.write("Options:\n")
sys.stderr.write(" -p pacfile : PAC file to test (specify '-' to read "
"from standard input)\n")
sys.stderr.write(" -u url : URL to test for\n")
sys.stderr.write(" -h host : Host part of the URL\n")
sys.stderr.write(" -c client_ip : client IP address (as returned by "
"myIpAddres() function\n")
sys.stderr.write(" in PAC files), defaults to IP address "
"on which it is running.\n")
sys.stderr.write(" -e : Deprecated: IPv6 extensions are enabled"
"by default now.\n")
sys.stderr.write(" -f urlslist : a file containing list of URLs to be "
"tested.\n")
sys.stderr.write(" -v : print version and exit\n")
sys.exit(1)

def get_host_from_url(url):
# Extract the host part of the URL, like the C pactester does. Prints an
# error and returns None if the URL is not in a recognized form.
idx = url.find(':')
if idx < 0 or url[idx + 1:idx + 3] != '//':
sys.stderr.write("pactester: Not a proper URL\n")

Check failure on line 66 in src/pymod/pacparser/pactester.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "pactester: Not a proper URL\n" 3 times.

See more on https://sonarcloud.io/project/issues?id=manugarg_pacparser&issues=AaArWI8ckY5_Ufzf0dpm&open=AaArWI8ckY5_Ufzf0dpm&pullRequest=257
return None
start = idx + 3
if start >= len(url) or url[start] in ('/', ':'):
sys.stderr.write("pactester: Not a proper URL\n")
return None
for end in range(start, len(url)):
if url[end] in ('/', ':'):
break
else:
end = len(url)
host = url[start:end]
if not host:
sys.stderr.write("pactester: Not a proper URL\n")
return None
return host

def main():

Check failure on line 83 in src/pymod/pacparser/pactester.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 53 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=manugarg_pacparser&issues=AaArWI8ckY5_Ufzf0dpn&open=AaArWI8ckY5_Ufzf0dpn&pullRequest=257
argv = sys.argv[1:]
if argv and argv[0] in ("--help", "--helpshort"):
usage()

pacfile = url = host = urlslist = client_ip = None

try:
opts, _ = getopt.getopt(argv, "evp:u:h:f:c:")
except getopt.GetoptError:
usage()

for opt, arg in opts:
if opt == "-v":
print(pacparser.version())
return 0
elif opt == "-p":
pacfile = arg
elif opt == "-u":
url = arg
elif opt == "-h":
host = arg
elif opt == "-f":
urlslist = arg
elif opt == "-c":
client_ip = arg
elif opt == "-e":
pass

Check warning on line 110 in src/pymod/pacparser/pactester.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either remove or fill this block of code.

See more on https://sonarcloud.io/project/issues?id=manugarg_pacparser&issues=AaArWI8ckY5_Ufzf0dpo&open=AaArWI8ckY5_Ufzf0dpo&pullRequest=257

if not pacfile:
sys.stderr.write("pactester: You didn't specify the PAC file\n")
usage()
if not url and not urlslist:
sys.stderr.write("pactester: You didn't specify the URL\n")
usage()

try:
pacparser.init()
except Exception:
sys.stderr.write("pactester: Could not initialize pacparser\n")
return 1

if pacfile == "-":
script = sys.stdin.read()
if len(script) > PACMAX:
sys.stderr.write("Input file is too big. Maximum allowed size is: %d"
% PACMAX)
pacparser.cleanup()
return 1
try:
pacparser.parse_pac_string(script)
except Exception:
sys.stderr.write("pactester: Could not parse the pac script: %s\n"
% script)
pacparser.cleanup()
return 1
else:
try:
pacparser.parse_pac_file(pacfile)
except Exception:
sys.stderr.write("pactester: Could not parse the pac file: %s\n"
% pacfile)
pacparser.cleanup()
return 1

if client_ip:
pacparser.setmyip(client_ip)

if url:
if not host:
host = get_host_from_url(url)
if not host:
pacparser.cleanup()
return 1
try:
proxy = pacparser.find_proxy(url, host)
except Exception:
sys.stderr.write("pactester: Problem in finding proxy for %s.\n" % url)
pacparser.cleanup()
return 1
print(proxy)
pacparser.cleanup()
return 0

if urlslist:
try:
fp = open(urlslist, "r")
except IOError:
sys.stderr.write("pactester: Could not open urlslist: %s" % urlslist)
pacparser.cleanup()
return 1
for line in fp:
u = line.lstrip(" \t")
# Skip comment lines, echoing them as the C pactester does.
if u.startswith("#"):
sys.stdout.write(u)
continue
fields = u.split(None, 1)
if not fields:
continue
u = fields[0]
host = get_host_from_url(u)
if not host:
continue
try:
proxy = pacparser.find_proxy(u, host)
except Exception:
sys.stderr.write("pactester: Problem in finding proxy for %s.\n" % u)
pacparser.cleanup()
return 1
if proxy:
print("%s : %s" % (u, proxy))
fp.close()
pacparser.cleanup()
return 0

pacparser.cleanup()
return 0

if __name__ == '__main__':
sys.exit(main())
7 changes: 6 additions & 1 deletion src/pymod/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,12 @@ def main(patched_func):
license="LGPL",
ext_package="pacparser",
ext_modules=[pacparser_module],
py_modules=["pacparser.__init__"],
py_modules=["pacparser.__init__", "pacparser.pactester"],
entry_points={
"console_scripts": [
"pactester = pacparser.pactester:main",
],
},
)


Expand Down
35 changes: 35 additions & 0 deletions tests/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import getopt
import glob
import os
import subprocess
import sys
import tempfile

Expand All @@ -35,6 +36,38 @@ def module_path(tests_dir):

return glob.glob(os.path.join(builddir, 'lib*%s' % py_ver))[0]

def run_cli_tests(pacfile, testdata, pacparser_module_path):
# Run the same testdata lines through the python command line tool
# (python -m pacparser.pactester), the way runtests.sh does for the C
# pactester.
env = dict(os.environ)
env['PYTHONPATH'] = (pacparser_module_path + os.pathsep +
env.get('PYTHONPATH', ''))
f = open(testdata)
for line in f:
comment = ''
if '#' in line:
comment = line.split('#', 1)[1]
line = line.split('#', 1)[0].strip()
if not line:
continue
if ('NO_INTERNET' in os.environ and os.environ['NO_INTERNET'] and
'INTERNET_REQUIRED' in comment):
continue
(params, expected_result) = line.strip().split('|')
cmd = [sys.executable, '-m', 'pacparser.pactester', '-p', pacfile]
cmd += params.split()
p = subprocess.run(cmd, env=env, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True)
if p.returncode != 0:
raise Exception('CLI test failed: %s returned %d\nstdout: %s\n'
'stderr: %s'
% (' '.join(cmd), p.returncode, p.stdout, p.stderr))
if p.stdout.strip() != expected_result:
raise Exception('CLI test failed. Got "%s", expected "%s"'
% (p.stdout.strip(), expected_result))
f.close()

def runtests(pacfile, testdata, tests_dir):
try:
pacparser_module_path = module_path(tests_dir)
Expand Down Expand Up @@ -104,6 +137,8 @@ def runtests(pacfile, testdata, tests_dir):
raise Exception('Logging test failed: stderr mismatch\nExpected:\n%s\nGot:\n%s' %
(expected_stderr, actual_stderr))

run_cli_tests(pacfile, testdata, pacparser_module_path)

print('All tests were successful.')


Expand Down
Loading