Skip to content

Commit 30b490f

Browse files
committed
WIP: Implement a PAC script parser and resolver for dojo#335
1 parent bf3b947 commit 30b490f

File tree

2 files changed

+251
-0
lines changed

2 files changed

+251
-0
lines changed

package.json

+6
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,23 @@
2929
"@dojo/loader": "beta1",
3030
"@types/benchmark": "~1.0.0",
3131
"@types/chai": "~3.4.0",
32+
"@types/fibers": "0.0.29",
3233
"@types/formidable": "~1.0.0",
3334
"@types/glob": "~5.0.0",
3435
"@types/grunt": "~0.4.0",
36+
"@types/ip": "0.0.29",
37+
"@types/netmask": "^1.0.29",
3538
"@types/sinon": "~1.16.0",
3639
"benchmark": "^1.0.0",
40+
"fibers": "^1.0.15",
3741
"formidable": "1.0.17",
3842
"grunt": "^1.0.1",
3943
"grunt-dojo2": "beta1",
4044
"http-proxy": "0.10.3",
4145
"intern": "^3.4.1",
46+
"ip": "^1.1.5",
4247
"istanbul": "0.4.3",
48+
"netmask": "^1.0.6",
4349
"sinon": "~1.17.6",
4450
"typescript": "~2.3.2"
4551
}

src/request/providers/node/pac.ts

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import { runInNewContext } from 'vm';
2+
import { lookup as lookupAsync } from 'dns';
3+
import Promise from '@dojo/shim/Promise';
4+
import DateObject, { DateProperties } from '../../../DateObject';
5+
import Fiber = require('fibers');
6+
import { Netmask } from 'netmask';
7+
import { address } from 'ip';
8+
9+
/**
10+
* Wraps `dns.lookup` for use in a Fiber so it works synchronously
11+
* inside a PAC script.
12+
*/
13+
function lookup(hostname: string, family: number = 4): void {
14+
const fiber = Fiber.current;
15+
lookupAsync(hostname, family, (err, address) => {
16+
if (err) {
17+
// Notify of the error
18+
(fiber as any).throwInto(err);
19+
} else {
20+
// Resume execution
21+
fiber.run(address as any);
22+
}
23+
});
24+
25+
// Tell the Fiber to pause execution
26+
(Fiber as any).yield();
27+
}
28+
29+
const dotRegExp = /\./g;
30+
31+
function isPlainHostName(host: string): boolean {
32+
return !(dotRegExp.test(host));
33+
}
34+
35+
function dnsDomainIs(host: string, domain: string): boolean {
36+
domain = String(domain);
37+
return String(host).substr(domain.length * -1) === domain;
38+
}
39+
40+
function localHostOrDomainIs(host: string, hostdom: string): boolean {
41+
if (!isPlainHostName(host)) {
42+
return host === hostdom;
43+
}
44+
return host === String(hostdom).split('.')[0];
45+
}
46+
47+
function isResolvable(host: string): boolean {
48+
try {
49+
lookup(host);
50+
}
51+
catch (_) {
52+
return false;
53+
}
54+
return true;
55+
}
56+
57+
function isInNet(host: string, pattern: string, mask: string): boolean {
58+
const ip = lookup(host) || '127.0.0.1';
59+
const netmask = new Netmask(pattern, mask);
60+
return Boolean(netmask.contains(ip));
61+
}
62+
63+
function dnsResolve(host: string): string {
64+
return lookup(host) || '127.0.0.1';
65+
}
66+
67+
function myIpAddress(): string {
68+
return address();
69+
}
70+
71+
function dnsDomainLevels(host: string): number {
72+
const match = String(host).match(dotRegExp);
73+
if (!match) {
74+
return 0;
75+
}
76+
return match.length;
77+
}
78+
79+
function shExpMatch(str: string, shexp: string): boolean {
80+
shexp = String(shexp)
81+
.replace(/\?/g, '.')
82+
.replace(/\*/g, '(:?.*)');
83+
return new RegExp(`^${shexp}$`).test(str);
84+
}
85+
86+
type Weekday = 'SUN' | 'MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT';
87+
88+
const dayMap: { [key: string]: number } = {
89+
SUN: 0,
90+
MON: 1,
91+
TUE: 2,
92+
WED: 3,
93+
THU: 4,
94+
FRI: 5,
95+
SAT: 6
96+
};
97+
98+
function weekdayRange(weekDay1: Weekday, gmt?: 'GMT'): boolean;
99+
function weekdayRange(weekDay1: Weekday, weekDay2: Weekday, gmt?: 'GMT'): boolean;
100+
function weekdayRange(weekDay1: Weekday, weekDay2: string = '', gmt?: 'GMT'): boolean {
101+
let wd1 = dayMap[weekDay1] || -1;
102+
let wd2 = dayMap[weekDay2] || -1;
103+
let useUTC = weekDay2 === 'GMT' || gmt === 'GMT';
104+
let now = DateObject.now();
105+
let today: number = (useUTC ? now.utc : now).dayOfWeek;
106+
107+
if (wd2 === -1) {
108+
return wd1 === today;
109+
}
110+
else {
111+
if (wd1 <= wd2) {
112+
return weekdayInRange(today, wd1, wd2);
113+
}
114+
else {
115+
return weekdayInRange(today, wd1, 6) || weekdayInRange(today, 0, wd2);
116+
}
117+
}
118+
}
119+
120+
function weekdayInRange(today: number, start: number, end: number): boolean {
121+
return today >= start && today <= end;
122+
}
123+
124+
type Month = 'JAN' | 'FEB' | 'MAR' | 'APR' | 'MAY' | 'JUN' | 'JUL' | 'AUG' | 'SEP' | 'OCT' | 'NOV' | 'DEC';
125+
126+
/*const monthMap: { [key: string]: number; } = {
127+
JAN: 1,
128+
FEB: 2,
129+
MAR: 3,
130+
APR: 4,
131+
MAY: 5,
132+
JUN: 6,
133+
JUL: 7,
134+
AUG: 8,
135+
SEP: 9,
136+
OCT: 10,
137+
NOV: 11,
138+
DEC: 12
139+
};*/
140+
141+
function dateRange(dayOrYear: number, gmt?: 'GMT'): boolean;
142+
function dateRange(dayOrYear1: number, dayOrYear2: number, gmt?: 'GMT'): boolean;
143+
function dateRange(mon: Month, gmt?: 'GMT'): boolean;
144+
function dateRange(mon1: Month, mon2: Month, gmt?: 'GMT'): boolean;
145+
function dateRange(day1: number, mon1: Month, day2: number, mon2: Month, gmt?: 'GMT'): boolean;
146+
function dateRange(mon1: Month, year1: number, mon2: Month, year2: number, gmt?: 'GMT'): boolean;
147+
function dateRange(day1: number, mon1: Month, year1: number, day2: number, mon2: Month, year2: number, gmt?: 'GMT'): boolean;
148+
function dateRange(...args: any[]): boolean {
149+
let useUTC = false;
150+
if (args[args.length - 1] === 'GMT') {
151+
args.pop();
152+
useUTC = true;
153+
}
154+
155+
return false;
156+
}
157+
158+
function timeRange(hour: number, gmt?: 'GMT'): boolean;
159+
function timeRange(hour1: number, hour2: number, gmt?: 'GMT'): boolean;
160+
function timeRange(hour1: number, min1: number, hour2: number, min2: number, gmt?: 'GMT'): boolean;
161+
function timeRange(hour1: number, min1: number, sec1: number, hour2: number, min2: number, sec2: number, gmt?: 'GMT'): boolean;
162+
function timeRange(...args: any[]): boolean {
163+
let useUTC = false;
164+
if (args[args.length - 1] === 'GMT') {
165+
args.pop();
166+
useUTC = true;
167+
}
168+
169+
let now: DateProperties = useUTC ? DateObject.now() : DateObject.now().utc;
170+
let integers = args.map(arg => parseInt(arg, 10));
171+
172+
if (args.length === 1) {
173+
return now.hours === integers[0];
174+
}
175+
else if (args.length === 2) {
176+
return integers[0] <= now.hours && now.hours < integers[1];
177+
}
178+
else if (args.length === 4) {
179+
let seconds = getSeconds(now.hours, now.minutes, 0);
180+
181+
return getSeconds(integers[0], integers[1], 0) <= seconds &&
182+
seconds <= getSeconds(integers[2], integers[3], 59);
183+
}
184+
else if (args.length === 6) {
185+
let seconds = getSeconds(now.hours, now.minutes, now.seconds);
186+
187+
return getSeconds(integers[0], integers[1], integers[2]) <= seconds &&
188+
seconds <= getSeconds(integers[3], integers[4], integers[5]);
189+
}
190+
191+
return false;
192+
}
193+
194+
function getSeconds(hours: number, minutes: number, seconds: number): number {
195+
return (hours * 3600) + (minutes * 60) + seconds;
196+
}
197+
198+
export interface FindProxyForURL {
199+
(url: string, host: string): Promise<string>;
200+
}
201+
202+
export default function pac(content: string, fileName?: string): FindProxyForURL {
203+
const pacFind = new Promise<FindProxyForURL>((resolve, reject) => {
204+
Fiber(() => {
205+
try {
206+
const pacFind = runInNewContext(`${content};FindProxyForURL`, {
207+
isPlainHostName,
208+
dnsDomainIs,
209+
localHostOrDomainIs,
210+
isResolvable,
211+
isInNet,
212+
dnsResolve,
213+
myIpAddress,
214+
dnsDomainLevels,
215+
shExpMatch,
216+
weekdayRange,
217+
dateRange,
218+
timeRange
219+
}, fileName);
220+
221+
if (typeof pacFind !== 'function') {
222+
throw new TypeError('A PAC file must declare a function named "FindProxyForURL"');
223+
}
224+
}
225+
catch (error) {
226+
reject(error);
227+
}
228+
}).run();
229+
});
230+
231+
return function FindProxyForURL(url: string, host: string): Promise<string> {
232+
return pacFind.then(pacFind => {
233+
return new Promise<string>((resolve, reject) => {
234+
Fiber(() => {
235+
try {
236+
resolve(pacFind(url, host));
237+
}
238+
catch (error) {
239+
reject(error);
240+
}
241+
}).run();
242+
});
243+
});
244+
};
245+
}

0 commit comments

Comments
 (0)