Skip to content

Commit febcc37

Browse files
authored
[webapp] Add web-based PAC file tester (#234)
* Add web-based PAC file tester Adds a client-side web app (web/) for testing PAC files in the browser without installing any software. Key features: - Paste or upload a PAC file and test it against a URL - Custom host→IP DNS mappings to mock dnsResolve() / myIpAddress() - Full PAC helper function support (all functions from pac_utils.h) - Debug panel showing DNS lookups made and whether myIpAddress() was called - 100% client-side — no data leaves the browser To keep pac_utils.h and the web app in sync, adds: - src/pac_utils_dump.c: tiny C program that dumps pac_utils.h JS as a JS source string (const PAC_UTILS_JS) into web/pac_utils.js - Makefile targets pac_utils_dump and pac_utils_js - web/pac_utils.js: generated file, checked in so the app works without a build step Also adds .github/workflows/deploy-pages.yml to publish the app to GitHub Pages on every push to main that touches web/ or pac_utils.h. Enable Pages (source: GitHub Actions) in repo settings to activate. * Add more information about pacparser
1 parent 74eb8a4 commit febcc37

11 files changed

Lines changed: 1429 additions & 1 deletion

File tree

.github/workflows/deploy-pages.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Deploy PAC Tester to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "web/**"
8+
- "src/pac_utils.h"
9+
- "src/pac_utils_dump.c"
10+
- ".github/workflows/deploy-pages.yml"
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: read
15+
pages: write
16+
id-token: write
17+
18+
# Only one Pages deployment at a time; skip queued runs but don't cancel
19+
# an in-progress deploy.
20+
concurrency:
21+
group: pages
22+
cancel-in-progress: false
23+
24+
jobs:
25+
deploy:
26+
environment:
27+
name: github-pages
28+
url: ${{ steps.deployment.outputs.page_url }}
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
32+
33+
# Regenerate web/pac_utils.js from src/pac_utils.h so the deployed
34+
# site is always in sync even if someone forgot to run
35+
# `make -C src pac_utils_js` locally before committing.
36+
- name: Regenerate pac_utils.js
37+
run: make -C src pac_utils_js
38+
39+
- uses: actions/configure-pages@v5
40+
41+
- uses: actions/upload-pages-artifact@v3
42+
with:
43+
path: web/
44+
45+
- name: Deploy to GitHub Pages
46+
id: deployment
47+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ tools/packages
2626
*buildstamp
2727
src/spidermonkey/js
2828
src/pactester
29+
src/pac_utils_dump
2930

3031
# OS specific files
3132
.DS_Store

src/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ testpactester: pactester $(LIBRARY_LINK)
108108
echo "Running tests for pactester."
109109
NO_INTERNET=$(NO_INTERNET) ../tests/runtests.sh
110110

111+
pac_utils_dump: pac_utils_dump.c pac_utils.h
112+
$(CC) -o pac_utils_dump pac_utils_dump.c
113+
114+
pac_utils_js: pac_utils_dump
115+
./pac_utils_dump > ../web/pac_utils.js
116+
111117
docs:
112118
../tools/generatedocs.sh
113119

@@ -150,7 +156,7 @@ install-pymod: pymod
150156
cd pymod && ARCHFLAGS="" $(PYTHON) setup.py install --root="$(DESTDIR)/" $(EXTRA_ARGS)
151157

152158
clean:
153-
rm -f $(LIBRARY_LINK) $(LIBRARY) pacparser.o pactester pymod/pacparser_o_buildstamp libpacparser.a
159+
rm -f $(LIBRARY_LINK) $(LIBRARY) pacparser.o pactester pymod/pacparser_o_buildstamp libpacparser.a pac_utils_dump
154160
rm -rf dist
155161
cd pymod && $(PYTHON) setup.py clean --all
156162
cd quickjs && $(MAKE) clean

src/pac_utils_dump.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// pac_utils_dump.c - Dumps pac_utils.h JavaScript as a pac_utils.js file
2+
// for use by the web-based PAC file tester.
3+
//
4+
// Build: cc -o pac_utils_dump pac_utils_dump.c
5+
// Usage: ./pac_utils_dump > ../web/pac_utils.js
6+
// (or via: make -C src pac_utils_js)
7+
8+
#include <stdio.h>
9+
#include "pac_utils.h"
10+
11+
int main() {
12+
printf("// Auto-generated from src/pac_utils.h — do not edit manually.\n");
13+
printf("// Regenerate with: make -C src pac_utils_js\n");
14+
printf("//\n");
15+
printf("// Exported as a source string (PAC_UTILS_JS) rather than executed\n");
16+
printf("// directly, so that the eval'd PAC sandbox can define its own\n");
17+
printf("// dnsResolve() / myIpAddress() in the same scope as the utility\n");
18+
printf("// functions — keeping DNS mocking correct via lexical scoping.\n");
19+
printf("const PAC_UTILS_JS = `\n");
20+
printf("%s", pacUtils);
21+
printf("`;\n");
22+
return 0;
23+
}

web/DEMO.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# PAC File Tester - Demo Guide
2+
3+
## Quick Start Demo
4+
5+
### Step 1: Open the Application
6+
7+
The web server is running! Open your browser and navigate to:
8+
```
9+
http://localhost:8000
10+
```
11+
12+
### Step 2: Try These Test Scenarios
13+
14+
#### Test Scenario 1: Plain Hostname
15+
1. **PAC File**: Use the content from `sample.pac` or paste:
16+
```javascript
17+
function FindProxyForURL(url, host) {
18+
if (isPlainHostName(host)) {
19+
return "DIRECT";
20+
}
21+
return "PROXY proxy.example.com:8080";
22+
}
23+
```
24+
25+
2. **Test URL**: `http://localhost`
26+
27+
3. **Expected Result**: `DIRECT`
28+
29+
---
30+
31+
#### Test Scenario 2: Internal Network Check
32+
1. **PAC File**: Use `sample.pac` (already created)
33+
34+
2. **Configuration**:
35+
- **Test URL**: `https://internal.company.com`
36+
- **Client IP**: `192.168.1.100`
37+
- **DNS Mappings**:
38+
- `internal.company.com``192.168.1.50`
39+
40+
3. **Expected Result**: `DIRECT` (since both client and destination are internal)
41+
42+
---
43+
44+
#### Test Scenario 3: External Site via Proxy
45+
1. **PAC File**: Use `sample.pac`
46+
47+
2. **Configuration**:
48+
- **Test URL**: `https://www.google.com`
49+
- **Client IP**: `192.168.1.100`
50+
- **DNS Mappings**:
51+
- `www.google.com``142.250.185.46`
52+
53+
3. **Expected Result**: `PROXY proxy.corporate.com:3128; DIRECT`
54+
55+
---
56+
57+
#### Test Scenario 4: Domain-Based Routing
58+
1. **PAC File**: Paste:
59+
```javascript
60+
function FindProxyForURL(url, host) {
61+
// Go direct for plain hostnames
62+
if (isPlainHostName(host) || dnsDomainIs(host, '.local')) {
63+
return "DIRECT";
64+
}
65+
66+
// Check if host is resolvable (requires DNS mapping)
67+
if (dnsDomainIs(host, '.google.com') && isResolvable(host)) {
68+
return "PROXY google-proxy:8080";
69+
}
70+
71+
// Default proxy
72+
return "PROXY proxy.company.com:3128; DIRECT";
73+
}
74+
```
75+
76+
2. **Configuration**:
77+
- **Test URL**: `https://www.google.com`
78+
- **DNS Mappings**:
79+
- `www.google.com``142.250.185.46`
80+
81+
3. **Expected Result**: `PROXY google-proxy:8080`
82+
83+
---
84+
85+
#### Test Scenario 5: IP Range Matching
86+
1. **PAC File**: Paste:
87+
```javascript
88+
function FindProxyForURL(url, host) {
89+
// Check if client is in corporate network
90+
if (isInNet(myIpAddress(), "10.10.0.0", "255.255.0.0")) {
91+
return "PROXY internal-proxy:3128";
92+
}
93+
94+
// Check if destination is in local network
95+
var resolved = dnsResolve(host);
96+
if (resolved && isInNet(resolved, "192.168.0.0", "255.255.0.0")) {
97+
return "DIRECT";
98+
}
99+
100+
return "PROXY external-proxy:8080";
101+
}
102+
```
103+
104+
2. **Configuration**:
105+
- **Test URL**: `http://intranet.local`
106+
- **Client IP**: `10.10.100.50`
107+
- **DNS Mappings**:
108+
- `intranet.local``192.168.10.5`
109+
110+
3. **Expected Result**: `PROXY internal-proxy:3128`
111+
112+
---
113+
114+
#### Test Scenario 6: Time-Based Routing
115+
1. **PAC File**: Paste:
116+
```javascript
117+
function FindProxyForURL(url, host) {
118+
// Use fast proxy during business hours (9 AM - 5 PM)
119+
if (timeRange(9, 17)) {
120+
return "PROXY fast-proxy:8080";
121+
}
122+
123+
// Use slower proxy outside business hours
124+
return "PROXY slow-proxy:8080";
125+
}
126+
```
127+
128+
2. **Test URL**: `https://www.example.com`
129+
130+
3. **Expected Result**: Depends on current time
131+
- During 9 AM - 5 PM: `PROXY fast-proxy:8080`
132+
- Outside those hours: `PROXY slow-proxy:8080`
133+
134+
---
135+
136+
## Testing with Real PAC Files
137+
138+
### Example: Google PAC File Pattern
139+
```javascript
140+
function FindProxyForURL(url, host) {
141+
// Bypass proxy for local addresses
142+
if (shExpMatch(host, "*.local") ||
143+
shExpMatch(host, "127.*") ||
144+
shExpMatch(host, "localhost")) {
145+
return "DIRECT";
146+
}
147+
148+
// Use specific proxy for certain domains
149+
if (shExpMatch(host, "*.example.com")) {
150+
return "PROXY proxy1.example.com:8080; PROXY proxy2.example.com:8080; DIRECT";
151+
}
152+
153+
// Default
154+
return "PROXY default-proxy.example.com:8080; DIRECT";
155+
}
156+
```
157+
158+
**Test with**:
159+
- URL: `http://internal.example.com`
160+
- Expected: `PROXY proxy1.example.com:8080; PROXY proxy2.example.com:8080; DIRECT`
161+
162+
---
163+
164+
## Debugging Tips
165+
166+
1. **Check the Debug Information**: After testing, scroll down to see:
167+
- Which DNS lookups were performed
168+
- Whether `myIpAddress()` was called
169+
- The extracted hostname from the URL
170+
171+
2. **Add DNS Mappings Progressively**: Start with no DNS mappings and add them as needed based on errors
172+
173+
3. **Test Multiple URLs**: Use the same PAC file with different URLs to verify routing logic
174+
175+
4. **Common Issues**:
176+
- If `dnsResolve(host)` returns null, add a DNS mapping for that host
177+
- If `myIpAddress()` returns unexpected value, set the Client IP field
178+
- Case sensitivity matters for domain names
179+
180+
## Next Steps
181+
182+
- Try uploading your own PAC file
183+
- Test against your actual proxy configuration
184+
- Share the web app with your team for testing PAC files
185+
- Deploy to a static hosting service (GitHub Pages, Netlify, etc.)
186+
187+
## Feedback
188+
189+
If you find any issues or have suggestions, please report them on the [Pacparser GitHub Issues](https://github.com/manugarg/pacparser/issues).

web/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# PAC File Tester - Web Application
2+
3+
A client-side web application for testing Proxy Auto-Config (PAC) files without installing any software.
4+
5+
## Features
6+
7+
- **100% Client-Side**: All processing happens in your browser - no data is sent to any server
8+
- **Privacy First**: Your PAC files and configuration never leave your machine
9+
- **Full PAC Support**: Implements all standard PAC helper functions
10+
- **Custom DNS Mappings**: Mock DNS resolution for testing different scenarios
11+
- **Debug Information**: See which DNS lookups were performed and what functions were called
12+
- **Simple UI**: Clean, intuitive interface for quick testing
13+
14+
## Usage
15+
16+
1. **Open `index.html`** in your web browser
17+
- You can open it directly from your file system
18+
- Or serve it with any web server (e.g., `python -m http.server 8000`)
19+
20+
2. **Provide Your PAC File**
21+
- Paste the PAC file content directly into the text area, OR
22+
- Upload a `.pac` file using the file picker
23+
24+
3. **Configure Test Parameters**
25+
- **Test URL**: The URL you want to test (e.g., `https://example.com`)
26+
- **Client IP** (optional): The IP address returned by `myIpAddress()` function
27+
- **DNS Mappings** (optional): Hostname to IP mappings for `dnsResolve()` function
28+
29+
4. **Click "Test Proxy"** to see the result
30+
31+
## Example
32+
33+
### Sample PAC File
34+
A sample PAC file is provided in `sample.pac` for testing.
35+
36+
### Sample Test Configuration
37+
- **URL**: `https://www.google.com`
38+
- **Client IP**: `192.168.1.100`
39+
- **DNS Mappings**:
40+
- `www.google.com``142.250.185.46`
41+
- `proxy.corporate.com``10.0.0.1`
42+
43+
## Supported PAC Functions
44+
45+
The tester implements all standard PAC helper functions:
46+
47+
### Network Functions
48+
- `dnsResolve(host)` - Resolves hostname to IP (uses custom mappings)
49+
- `myIpAddress()` - Returns client IP (uses custom IP or 127.0.0.1)
50+
- `isResolvable(host)` - Checks if hostname can be resolved
51+
52+
### Matching Functions
53+
- `isPlainHostName(host)` - True if hostname has no dots
54+
- `dnsDomainIs(host, domain)` - True if host is in domain
55+
- `localHostOrDomainIs(host, hostdom)` - True if host matches
56+
- `shExpMatch(str, pattern)` - Shell expression matching
57+
- `isInNet(host, pattern, mask)` - IP address range checking
58+
- `dnsDomainLevels(host)` - Number of DNS domain levels
59+
60+
### Time Functions
61+
- `weekdayRange([wd1, wd2, "GMT"])` - Weekday matching
62+
- `dateRange([day1, month1, year1, day2, month2, year2, "GMT"])` - Date range matching
63+
- `timeRange([hour1, min1, sec1, hour2, min2, sec2, "GMT"])` - Time range matching
64+
65+
## How It Works
66+
67+
1. The tester loads the PAC utility functions (ported from Mozilla's implementation)
68+
2. It creates mock implementations of `dnsResolve()` and `myIpAddress()` using your custom mappings
69+
3. Your PAC file is evaluated in a sandboxed context
70+
4. The `FindProxyForURL()` function is called with your test URL
71+
5. Results and debug information are displayed
72+
73+
## Privacy & Security
74+
75+
- **No Server Processing**: Everything runs in your browser using JavaScript
76+
- **No Analytics**: No tracking or data collection
77+
- **No External Requests**: Doesn't make any network requests
78+
- **Safe Evaluation**: PAC files are evaluated in isolated JavaScript contexts
79+
80+
## License
81+
82+
This web application is part of the Pacparser project and is licensed under LGPL.
83+
84+
## Contributing
85+
86+
Found a bug or have a feature request? Please open an issue on the [Pacparser GitHub repository](https://github.com/manugarg/pacparser).

0 commit comments

Comments
 (0)