Library / SDK
manugarg/pacparser avatar
manugarg/pacparser

Pacparser runs a PAC file in QuickJS and hands you a proxy string

Library and tools to parse and test proxy auto-config (PAC) files

534 stars125 forksCLGPL-3.0

At a glance

What is it?
Pacparser is a C library and a Python module that evaluate proxy auto-config files and return the proxy decision for a URL, built on the QuickJS engine with Mozilla's PAC functions. It suits anything that has to honour a PAC file, and its global parser state means one instance per process.
Who is it for?
Pacparser is worth adopting if your program has to honour a PAC file that a browser would honour, since it uses the same QuickJS engine and the same Mozilla PAC functions rather than an approximation, and since the Python module makes it a two-line change in a script and a link against a shared library in a C program.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A PAC file is a program, so evaluating it needs a JavaScript engine

The problem pacparser solves starts with what a PAC file actually is. Proxy auto-config files are a widely used proxy configuration method, and web browsers use a PAC file to determine which proxy server to use or whether to go direct for a given URL. PAC files are written in JavaScript, and they can be programmed to return different proxy methods, for example `"PROXY proxy1:port; DIRECT"`, depending on the URL, the source IP address, the protocol, the time of the day and other conditions. That is the point: a PAC file is not a list of rules, it is a program, and reproducing its behaviour means running JavaScript with the right set of host functions. pacparser is a library to parse proxy auto-config files, and the idea behind it is stated plainly: to make it easy to add PAC-file parsing capability to any program. C and Python are supported right now, and the licence is LGPL-3.0.

QuickJS provides the engine, Mozilla provides the PAC functions

The implementation choice is what makes the results match what a browser would decide. Pacparser makes use of the QuickJS JavaScript engine to parse PAC files, which are nothing but JavaScript. The second half is subtler. The proxy auto-config standard assumes availability of functions that are not part of standard JavaScript, and pacparser uses Mozilla's PAC implementation to define all of them, except for a couple of DNS functions which are defined by pacparser itself. The documentation's own summary of the result is that pacparser is as close to standard as it gets, and that is a meaningful claim because Mozilla's implementation is the reference implementation the browsers themselves are built against. The consequence for an adopter is that a PAC file that behaves one way in Chrome or Firefox behaves the same way here, which removes the class of bug where a proxy decision differs between the browser and the tool that is supposed to be proxy aware.

Four functions are the whole C API

The C interface is small enough to paste into a program. The library exposes an init function, a parse function that takes the PAC file path, a find function that takes a URL and a host and returns the proxy string, and a cleanup function:

C
int pacparser_init();
int pacparser_parse_pac(char* pacfile);
char *pacparser_find_proxy(char *url, char *host);
void pacparser_cleanup();

The example program shows the intended sequence, initialising, parsing the file given as the first argument, looking up the proxy for the second and third arguments, printing the result and cleaning up. The compiled binary is then linked against the shared library with `gcc -o pactest pactest.c -lpacparser`, and the shell transcript shows the result for a URL, which is a semicolon separated list of proxies and a DIRECT fallback. The shared library is named per platform, `libpacparser.so` on Linux, `libpacparser.dylib` on MacOS and `pacparser.dll` on Windows, and the documentation notes that using it is as easy as compiling your C programs against it.

The Python module is the same library with a nicer surface

The Python side is the same engine behind a module, and the example session is worth reading because it shows the one behaviour that surprises people. The sequence is to import the module, initialise, parse a PAC file, and then call find_proxy:

python
>>> import pacparser
>>> pacparser.init()
>>> pacparser.parse_pac('examples/wpad.dat')
>>> pacparser.find_proxy('http://www.google.com', 'www.google.com')
'DIRECT'
>>> pacparser.setmyip("192.168.1.134")
>>> pacparser.find_proxy('http://www.google.com', 'www.google.com')
'PROXY proxy1.manugarg.com:3128; PROXY proxy2.manugarg.com:3128; DIRECT'

The interesting part is the change between the two calls. The first lookup returns DIRECT, and the second, after `setmyip` has been called with an address, returns a chain of two proxies and a direct fallback. That is the PAC file's own logic working: the decision depends on the source IP address the client appears to have, and pacparser exposes that as a settable value because a library evaluating a PAC file has no socket of its own to derive it from. A caller therefore has to tell the library what its own address is, or the PAC file will make a decision based on the wrong premise. The session ends with `pacparser.cleanup()`.

The parser is process-global, so every call needs your own lock

This is the most important thing in the documentation for any server-side use, and it is stated in unusually blunt terms. Pacparser keeps all of its JavaScript engine state, meaning the QuickJS runtime and context, the parsed PAC script and the last result, in process-global variables, and the library creates no threads of its own. The API is therefore not thread-safe, because there is a single shared parser instance per process. If you use pacparser from a multi-threaded program, you must serialize all calls into the library, with the named functions being init, the parse functions, find_proxy, setmyip and cleanup, using your own lock. The specific hazard called out is the worst one: never call `pacparser_cleanup()` while another thread is inside `pacparser_find_proxy()` or a parse function, because cleanup frees the QuickJS runtime and context that the other thread is still using, which crashes the process, and which is a use-after-free in builds compiled with `-DNDEBUG`.

Installation is a pip install for Python or a release binary for everyone else

For Python, the pre-built module is available for 64-bit Linux, Windows, MacOS on Intel and MacOS on ARM, covering Python 3.8 through 3.14, and the install is a pip command:

code
python -m pip install pacparser
python -m pip install pacparser==1.5.0 (specific version)

For anything else, the documentation points at the project's releases page for pre-built binaries, and it also notes that the latest binaries can be downloaded from the GitHub Actions artifacts. The third route is compiling from source, and the INSTALL file in the repository covers that, with a separate README.win32.md for the Windows build. The repository also carries a `web/` directory and a `docs/` directory, and the project homepage at pacparser.manugarg.com is described as the homepage and online PAC file tester, so you can check a PAC file's behaviour in a browser before wiring it into anything. The example directory holds `pactest.c`, `pactest.py`, `fetchurl.py` and the `wpad.dat` PAC file used in the documentation examples.

The Docker image builds a pactester binary, not the library

The container in this repository is worth understanding before you reach for it, because it is not a packaged version of the library. The Dockerfile starts from ubuntu:latest as a build stage, installs gcc, g++, git, bash and make, copies the `src` directory and runs `make pactester`, then copies the resulting `pactester` binary into a final ubuntu:latest image whose entrypoint is that binary. The image carries build metadata as labels, including the build date, the name Pactester, the vcs URL and ref, the version, and a licence label. The Makefile builds it with buildx for multiple architectures, linux/amd64, linux/arm64 and linux/arm/v7, and derives the image tags from the git tag, publishing master and main tags when there is no exact tag and the tag plus latest when there is one. So the container route gives you a command line tool that evaluates PAC files, which is useful in a CI job or a containerised service, but the library itself is what you consume from C or Python.

Where PAC evaluation is the wrong requirement

Two limits are worth being explicit about. The first is the one the documentation itself points at, that a PAC file is a program and running it means running JavaScript, which means a QuickJS runtime inside your process with a global state. For a single command line tool or a script, that is a perfectly reasonable price. For a service that evaluates PAC decisions concurrently, the global state turns every call into a critical section, and the failure mode of getting that wrong is a crash rather than a wrong answer. The second limit is scope. Pacparser evaluates PAC files, and a PAC file only appears in an environment where proxy configuration is delegated to a script, which is a shrinking category as more deployments move to fixed proxy lists or to configuration managed by the operating system. The alternative approach to weigh against is to avoid PAC evaluation entirely, either by asking the browser what it would do through its own proxy settings, or by translating the PAC file's logic into whatever configuration your proxy server actually consumes. The difference in approach is that pacparser keeps the PAC file as the source of truth and reproduces its behaviour faithfully, while the alternatives change the source of truth and accept drift. For an environment where the PAC file is the thing the organisation actually writes and ships, keeping it authoritative is worth a shared lock. For a deployment with one static proxy list, it is pure overhead.

Editorial conclusion

Pacparser is worth adopting if your program has to honour a PAC file that a browser would honour, since it uses the same QuickJS engine and the same Mozilla PAC functions rather than an approximation, and since the Python module makes it a two-line change in a script and a link against a shared library in a C program. It is a poor fit for a multi-threaded server that wants concurrent evaluation, because the parser state is process-global and every call has to be serialised with your own lock, and a poor fit if you only need to read a static configuration file, since a PAC file is a program and a static file needs no JavaScript engine at all. Before integrating it, read the thread safety section carefully, because a `pacparser_cleanup()` call racing with a `pacparser_find_proxy()` call crashes the process, and note that the last push to the repository was on 2026-10-03 with release v1.5.3 published the same day.

Frequently asked questions

What is Pacparser and what problem does it solve?

It is a library to parse proxy auto-config files, and the idea behind it is to make it easy to add PAC-file parsing capability to any program, in C and Python right now. A PAC file is a JavaScript program that returns a proxy decision for a URL, and pacparser runs it and hands back the proxy string.

How do I install the pacparser Python module?

Run python -m pip install pacparser, or pin a version with python -m pip install pacparser==1.5.0. Pre-built modules exist for 64-bit Linux, Windows, MacOS on Intel and MacOS on ARM, for Python 3.8 through 3.14, and other pre-built binaries are on the project's releases page, with the INSTALL file covering compilation from source.

Is pacparser safe to use from multiple threads?

No. The API is not thread-safe because the QuickJS runtime and context, the parsed PAC script and the last result are kept in process-global variables, so there is a single shared parser instance per process. You must serialize all calls into the library with your own lock, and never call pacparser_cleanup() while another thread is inside pacparser_find_proxy() or a parse function, because that frees the runtime the other thread is using and crashes the process.

Why does the proxy decision change after calling setmyip?

A PAC file can decide based on the source IP address, and a library evaluating a file has no socket of its own to derive that address from, so pacparser exposes it as a settable value. In the documented example, the same lookup returns DIRECT before setmyip is called and a chain of two proxies with a DIRECT fallback after it is called with an address.

Official sources

  1. License: LGPL-3.0
  2. manugarg/pacparser on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/manugarg-pacparser.svg)](https://hysenlabs.com/projects/manugarg-pacparser)

Was this useful?