Skip to content
Merged
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
47 changes: 47 additions & 0 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Deploy PAC Tester to GitHub Pages

on:
push:
branches: [main]
paths:
- "web/**"
- "src/pac_utils.h"
- "src/pac_utils_dump.c"
- ".github/workflows/deploy-pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Only one Pages deployment at a time; skip queued runs but don't cancel
# an in-progress deploy.
concurrency:
group: pages
cancel-in-progress: false

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

# Regenerate web/pac_utils.js from src/pac_utils.h so the deployed
# site is always in sync even if someone forgot to run
# `make -C src pac_utils_js` locally before committing.
- name: Regenerate pac_utils.js
run: make -C src pac_utils_js

- uses: actions/configure-pages@v5

- uses: actions/upload-pages-artifact@v3
with:
path: web/

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ tools/packages
*buildstamp
src/spidermonkey/js
src/pactester
src/pac_utils_dump

# OS specific files
.DS_Store
Expand Down
8 changes: 7 additions & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ testpactester: pactester $(LIBRARY_LINK)
echo "Running tests for pactester."
NO_INTERNET=$(NO_INTERNET) ../tests/runtests.sh

pac_utils_dump: pac_utils_dump.c pac_utils.h
$(CC) -o pac_utils_dump pac_utils_dump.c

pac_utils_js: pac_utils_dump
./pac_utils_dump > ../web/pac_utils.js

docs:
../tools/generatedocs.sh

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

clean:
rm -f $(LIBRARY_LINK) $(LIBRARY) pacparser.o pactester pymod/pacparser_o_buildstamp libpacparser.a
rm -f $(LIBRARY_LINK) $(LIBRARY) pacparser.o pactester pymod/pacparser_o_buildstamp libpacparser.a pac_utils_dump
rm -rf dist
cd pymod && $(PYTHON) setup.py clean --all
cd quickjs && $(MAKE) clean
23 changes: 23 additions & 0 deletions src/pac_utils_dump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// pac_utils_dump.c - Dumps pac_utils.h JavaScript as a pac_utils.js file
// for use by the web-based PAC file tester.
//
// Build: cc -o pac_utils_dump pac_utils_dump.c
// Usage: ./pac_utils_dump > ../web/pac_utils.js
// (or via: make -C src pac_utils_js)

#include <stdio.h>
#include "pac_utils.h"

int main() {
printf("// Auto-generated from src/pac_utils.h — do not edit manually.\n");
printf("// Regenerate with: make -C src pac_utils_js\n");
printf("//\n");
printf("// Exported as a source string (PAC_UTILS_JS) rather than executed\n");
printf("// directly, so that the eval'd PAC sandbox can define its own\n");
printf("// dnsResolve() / myIpAddress() in the same scope as the utility\n");
printf("// functions — keeping DNS mocking correct via lexical scoping.\n");
printf("const PAC_UTILS_JS = `\n");
printf("%s", pacUtils);
printf("`;\n");
return 0;
}
189 changes: 189 additions & 0 deletions web/DEMO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# PAC File Tester - Demo Guide

## Quick Start Demo

### Step 1: Open the Application

The web server is running! Open your browser and navigate to:
```
http://localhost:8000
```

### Step 2: Try These Test Scenarios

#### Test Scenario 1: Plain Hostname
1. **PAC File**: Use the content from `sample.pac` or paste:
```javascript
function FindProxyForURL(url, host) {
if (isPlainHostName(host)) {
return "DIRECT";
}
return "PROXY proxy.example.com:8080";
}
```

2. **Test URL**: `http://localhost`

3. **Expected Result**: `DIRECT`

---

#### Test Scenario 2: Internal Network Check
1. **PAC File**: Use `sample.pac` (already created)

2. **Configuration**:
- **Test URL**: `https://internal.company.com`
- **Client IP**: `192.168.1.100`
- **DNS Mappings**:
- `internal.company.com` → `192.168.1.50`

3. **Expected Result**: `DIRECT` (since both client and destination are internal)

---

#### Test Scenario 3: External Site via Proxy
1. **PAC File**: Use `sample.pac`

2. **Configuration**:
- **Test URL**: `https://www.google.com`
- **Client IP**: `192.168.1.100`
- **DNS Mappings**:
- `www.google.com` → `142.250.185.46`

3. **Expected Result**: `PROXY proxy.corporate.com:3128; DIRECT`

---

#### Test Scenario 4: Domain-Based Routing
1. **PAC File**: Paste:
```javascript
function FindProxyForURL(url, host) {
// Go direct for plain hostnames
if (isPlainHostName(host) || dnsDomainIs(host, '.local')) {
return "DIRECT";
}

// Check if host is resolvable (requires DNS mapping)
if (dnsDomainIs(host, '.google.com') && isResolvable(host)) {
return "PROXY google-proxy:8080";
}

// Default proxy
return "PROXY proxy.company.com:3128; DIRECT";
}
```

2. **Configuration**:
- **Test URL**: `https://www.google.com`
- **DNS Mappings**:
- `www.google.com` → `142.250.185.46`

3. **Expected Result**: `PROXY google-proxy:8080`

---

#### Test Scenario 5: IP Range Matching
1. **PAC File**: Paste:
```javascript
function FindProxyForURL(url, host) {
// Check if client is in corporate network
if (isInNet(myIpAddress(), "10.10.0.0", "255.255.0.0")) {
return "PROXY internal-proxy:3128";
}

// Check if destination is in local network
var resolved = dnsResolve(host);
if (resolved && isInNet(resolved, "192.168.0.0", "255.255.0.0")) {
return "DIRECT";
}

return "PROXY external-proxy:8080";
}
```

2. **Configuration**:
- **Test URL**: `http://intranet.local`
- **Client IP**: `10.10.100.50`
- **DNS Mappings**:
- `intranet.local` → `192.168.10.5`

3. **Expected Result**: `PROXY internal-proxy:3128`

---

#### Test Scenario 6: Time-Based Routing
1. **PAC File**: Paste:
```javascript
function FindProxyForURL(url, host) {
// Use fast proxy during business hours (9 AM - 5 PM)
if (timeRange(9, 17)) {
return "PROXY fast-proxy:8080";
}

// Use slower proxy outside business hours
return "PROXY slow-proxy:8080";
}
```

2. **Test URL**: `https://www.example.com`

3. **Expected Result**: Depends on current time
- During 9 AM - 5 PM: `PROXY fast-proxy:8080`
- Outside those hours: `PROXY slow-proxy:8080`

---

## Testing with Real PAC Files

### Example: Google PAC File Pattern
```javascript
function FindProxyForURL(url, host) {
// Bypass proxy for local addresses
if (shExpMatch(host, "*.local") ||
shExpMatch(host, "127.*") ||
shExpMatch(host, "localhost")) {
return "DIRECT";
}

// Use specific proxy for certain domains
if (shExpMatch(host, "*.example.com")) {
return "PROXY proxy1.example.com:8080; PROXY proxy2.example.com:8080; DIRECT";
}

// Default
return "PROXY default-proxy.example.com:8080; DIRECT";
}
```

**Test with**:
- URL: `http://internal.example.com`
- Expected: `PROXY proxy1.example.com:8080; PROXY proxy2.example.com:8080; DIRECT`

---

## Debugging Tips

1. **Check the Debug Information**: After testing, scroll down to see:
- Which DNS lookups were performed
- Whether `myIpAddress()` was called
- The extracted hostname from the URL

2. **Add DNS Mappings Progressively**: Start with no DNS mappings and add them as needed based on errors

3. **Test Multiple URLs**: Use the same PAC file with different URLs to verify routing logic

4. **Common Issues**:
- If `dnsResolve(host)` returns null, add a DNS mapping for that host
- If `myIpAddress()` returns unexpected value, set the Client IP field
- Case sensitivity matters for domain names

## Next Steps

- Try uploading your own PAC file
- Test against your actual proxy configuration
- Share the web app with your team for testing PAC files
- Deploy to a static hosting service (GitHub Pages, Netlify, etc.)

## Feedback

If you find any issues or have suggestions, please report them on the [Pacparser GitHub Issues](https://github.com/manugarg/pacparser/issues).
86 changes: 86 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# PAC File Tester - Web Application

A client-side web application for testing Proxy Auto-Config (PAC) files without installing any software.

## Features

- **100% Client-Side**: All processing happens in your browser - no data is sent to any server
- **Privacy First**: Your PAC files and configuration never leave your machine
- **Full PAC Support**: Implements all standard PAC helper functions
- **Custom DNS Mappings**: Mock DNS resolution for testing different scenarios
- **Debug Information**: See which DNS lookups were performed and what functions were called
- **Simple UI**: Clean, intuitive interface for quick testing

## Usage

1. **Open `index.html`** in your web browser
- You can open it directly from your file system
- Or serve it with any web server (e.g., `python -m http.server 8000`)

2. **Provide Your PAC File**
- Paste the PAC file content directly into the text area, OR
- Upload a `.pac` file using the file picker

3. **Configure Test Parameters**
- **Test URL**: The URL you want to test (e.g., `https://example.com`)
- **Client IP** (optional): The IP address returned by `myIpAddress()` function
- **DNS Mappings** (optional): Hostname to IP mappings for `dnsResolve()` function

4. **Click "Test Proxy"** to see the result

## Example

### Sample PAC File
A sample PAC file is provided in `sample.pac` for testing.

### Sample Test Configuration
- **URL**: `https://www.google.com`
- **Client IP**: `192.168.1.100`
- **DNS Mappings**:
- `www.google.com` → `142.250.185.46`
- `proxy.corporate.com` → `10.0.0.1`

## Supported PAC Functions

The tester implements all standard PAC helper functions:

### Network Functions
- `dnsResolve(host)` - Resolves hostname to IP (uses custom mappings)
- `myIpAddress()` - Returns client IP (uses custom IP or 127.0.0.1)
- `isResolvable(host)` - Checks if hostname can be resolved

### Matching Functions
- `isPlainHostName(host)` - True if hostname has no dots
- `dnsDomainIs(host, domain)` - True if host is in domain
- `localHostOrDomainIs(host, hostdom)` - True if host matches
- `shExpMatch(str, pattern)` - Shell expression matching
- `isInNet(host, pattern, mask)` - IP address range checking
- `dnsDomainLevels(host)` - Number of DNS domain levels

### Time Functions
- `weekdayRange([wd1, wd2, "GMT"])` - Weekday matching
- `dateRange([day1, month1, year1, day2, month2, year2, "GMT"])` - Date range matching
- `timeRange([hour1, min1, sec1, hour2, min2, sec2, "GMT"])` - Time range matching

## How It Works

1. The tester loads the PAC utility functions (ported from Mozilla's implementation)
2. It creates mock implementations of `dnsResolve()` and `myIpAddress()` using your custom mappings
3. Your PAC file is evaluated in a sandboxed context
4. The `FindProxyForURL()` function is called with your test URL
5. Results and debug information are displayed

## Privacy & Security

- **No Server Processing**: Everything runs in your browser using JavaScript
- **No Analytics**: No tracking or data collection
- **No External Requests**: Doesn't make any network requests
- **Safe Evaluation**: PAC files are evaluated in isolated JavaScript contexts

## License

This web application is part of the Pacparser project and is licensed under LGPL.

## Contributing

Found a bug or have a feature request? Please open an issue on the [Pacparser GitHub repository](https://github.com/manugarg/pacparser).
Loading
Loading