1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "ProxyAutoConfig.h"
8 #include "nsICancelable.h"
9 #include "nsIDNSListener.h"
10 #include "nsIDNSRecord.h"
11 #include "nsIDNSService.h"
12 #include "nsINamed.h"
13 #include "nsThreadUtils.h"
14 #include "nsIConsoleService.h"
15 #include "nsIURLParser.h"
16 #include "nsJSUtils.h"
17 #include "jsfriendapi.h"
18 #include "prnetdb.h"
19 #include "nsITimer.h"
20 #include "mozilla/net/DNS.h"
21 #include "nsServiceManagerUtils.h"
22 #include "nsNetCID.h"
23
24 #if defined(XP_MACOSX)
25 # include "nsMacUtilsImpl.h"
26 #endif
27
28 namespace mozilla {
29 namespace net {
30
31 // These are some global helper symbols the PAC format requires that we provide
32 // that are initialized as part of the global javascript context used for PAC
33 // evaluations. Additionally dnsResolve(host) and myIpAddress() are supplied in
34 // the same context but are implemented as c++ helpers. alert(msg) is similarly
35 // defined.
36
37 static const char *sPacUtils =
38 "function dnsDomainIs(host, domain) {\n"
39 " return (host.length >= domain.length &&\n"
40 " host.substring(host.length - domain.length) == domain);\n"
41 "}\n"
42 ""
43 "function dnsDomainLevels(host) {\n"
44 " return host.split('.').length - 1;\n"
45 "}\n"
46 ""
47 "function isValidIpAddress(ipchars) {\n"
48 " var matches = "
49 "/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipchars);\n"
50 " if (matches == null) {\n"
51 " return false;\n"
52 " } else if (matches[1] > 255 || matches[2] > 255 || \n"
53 " matches[3] > 255 || matches[4] > 255) {\n"
54 " return false;\n"
55 " }\n"
56 " return true;\n"
57 "}\n"
58 ""
59 "function convert_addr(ipchars) {\n"
60 " var bytes = ipchars.split('.');\n"
61 " var result = ((bytes[0] & 0xff) << 24) |\n"
62 " ((bytes[1] & 0xff) << 16) |\n"
63 " ((bytes[2] & 0xff) << 8) |\n"
64 " (bytes[3] & 0xff);\n"
65 " return result;\n"
66 "}\n"
67 ""
68 "function isInNet(ipaddr, pattern, maskstr) {\n"
69 " if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) {\n"
70 " return false;\n"
71 " }\n"
72 " if (!isValidIpAddress(ipaddr)) {\n"
73 " ipaddr = dnsResolve(ipaddr);\n"
74 " if (ipaddr == null) {\n"
75 " return false;\n"
76 " }\n"
77 " }\n"
78 " var host = convert_addr(ipaddr);\n"
79 " var pat = convert_addr(pattern);\n"
80 " var mask = convert_addr(maskstr);\n"
81 " return ((host & mask) == (pat & mask));\n"
82 " \n"
83 "}\n"
84 ""
85 "function isPlainHostName(host) {\n"
86 " return (host.search('\\\\.') == -1);\n"
87 "}\n"
88 ""
89 "function isResolvable(host) {\n"
90 " var ip = dnsResolve(host);\n"
91 " return (ip != null);\n"
92 "}\n"
93 ""
94 "function localHostOrDomainIs(host, hostdom) {\n"
95 " return (host == hostdom) ||\n"
96 " (hostdom.lastIndexOf(host + '.', 0) == 0);\n"
97 "}\n"
98 ""
99 "function shExpMatch(url, pattern) {\n"
100 " pattern = pattern.replace(/\\./g, '\\\\.');\n"
101 " pattern = pattern.replace(/\\*/g, '.*');\n"
102 " pattern = pattern.replace(/\\?/g, '.');\n"
103 " var newRe = new RegExp('^'+pattern+'$');\n"
104 " return newRe.test(url);\n"
105 "}\n"
106 ""
107 "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n"
108 "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, "
109 "AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n"
110 ""
111 "function weekdayRange() {\n"
112 " function getDay(weekday) {\n"
113 " if (weekday in wdays) {\n"
114 " return wdays[weekday];\n"
115 " }\n"
116 " return -1;\n"
117 " }\n"
118 " var date = new Date();\n"
119 " var argc = arguments.length;\n"
120 " var wday;\n"
121 " if (argc < 1)\n"
122 " return false;\n"
123 " if (arguments[argc - 1] == 'GMT') {\n"
124 " argc--;\n"
125 " wday = date.getUTCDay();\n"
126 " } else {\n"
127 " wday = date.getDay();\n"
128 " }\n"
129 " var wd1 = getDay(arguments[0]);\n"
130 " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n"
131 " return (wd1 == -1 || wd2 == -1) ? false\n"
132 " : (wd1 <= wd2) ? (wd1 <= wday && wday "
133 "<= wd2)\n"
134 " : (wd2 >= wday || wday "
135 ">= wd1);\n"
136 "}\n"
137 ""
138 "function dateRange() {\n"
139 " function getMonth(name) {\n"
140 " if (name in months) {\n"
141 " return months[name];\n"
142 " }\n"
143 " return -1;\n"
144 " }\n"
145 " var date = new Date();\n"
146 " var argc = arguments.length;\n"
147 " if (argc < 1) {\n"
148 " return false;\n"
149 " }\n"
150 " var isGMT = (arguments[argc - 1] == 'GMT');\n"
151 "\n"
152 " if (isGMT) {\n"
153 " argc--;\n"
154 " }\n"
155 " // function will work even without explict handling of this case\n"
156 " if (argc == 1) {\n"
157 " var tmp = parseInt(arguments[0]);\n"
158 " if (isNaN(tmp)) {\n"
159 " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n"
160 " getMonth(arguments[0]));\n"
161 " } else if (tmp < 32) {\n"
162 " return ((isGMT ? date.getUTCDate() : date.getDate()) == "
163 "tmp);\n"
164 " } else { \n"
165 " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) "
166 "==\n"
167 " tmp);\n"
168 " }\n"
169 " }\n"
170 " var year = date.getFullYear();\n"
171 " var date1, date2;\n"
172 " date1 = new Date(year, 0, 1, 0, 0, 0);\n"
173 " date2 = new Date(year, 11, 31, 23, 59, 59);\n"
174 " var adjustMonth = false;\n"
175 " for (var i = 0; i < (argc >> 1); i++) {\n"
176 " var tmp = parseInt(arguments[i]);\n"
177 " if (isNaN(tmp)) {\n"
178 " var mon = getMonth(arguments[i]);\n"
179 " date1.setMonth(mon);\n"
180 " } else if (tmp < 32) {\n"
181 " adjustMonth = (argc <= 2);\n"
182 " date1.setDate(tmp);\n"
183 " } else {\n"
184 " date1.setFullYear(tmp);\n"
185 " }\n"
186 " }\n"
187 " for (var i = (argc >> 1); i < argc; i++) {\n"
188 " var tmp = parseInt(arguments[i]);\n"
189 " if (isNaN(tmp)) {\n"
190 " var mon = getMonth(arguments[i]);\n"
191 " date2.setMonth(mon);\n"
192 " } else if (tmp < 32) {\n"
193 " date2.setDate(tmp);\n"
194 " } else {\n"
195 " date2.setFullYear(tmp);\n"
196 " }\n"
197 " }\n"
198 " if (adjustMonth) {\n"
199 " date1.setMonth(date.getMonth());\n"
200 " date2.setMonth(date.getMonth());\n"
201 " }\n"
202 " if (isGMT) {\n"
203 " var tmp = date;\n"
204 " tmp.setFullYear(date.getUTCFullYear());\n"
205 " tmp.setMonth(date.getUTCMonth());\n"
206 " tmp.setDate(date.getUTCDate());\n"
207 " tmp.setHours(date.getUTCHours());\n"
208 " tmp.setMinutes(date.getUTCMinutes());\n"
209 " tmp.setSeconds(date.getUTCSeconds());\n"
210 " date = tmp;\n"
211 " }\n"
212 " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
213 " : (date2 >= date) || (date >= date1);\n"
214 "}\n"
215 ""
216 "function timeRange() {\n"
217 " var argc = arguments.length;\n"
218 " var date = new Date();\n"
219 " var isGMT= false;\n"
220 ""
221 " if (argc < 1) {\n"
222 " return false;\n"
223 " }\n"
224 " if (arguments[argc - 1] == 'GMT') {\n"
225 " isGMT = true;\n"
226 " argc--;\n"
227 " }\n"
228 "\n"
229 " var hour = isGMT ? date.getUTCHours() : date.getHours();\n"
230 " var date1, date2;\n"
231 " date1 = new Date();\n"
232 " date2 = new Date();\n"
233 "\n"
234 " if (argc == 1) {\n"
235 " return (hour == arguments[0]);\n"
236 " } else if (argc == 2) {\n"
237 " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n"
238 " } else {\n"
239 " switch (argc) {\n"
240 " case 6:\n"
241 " date1.setSeconds(arguments[2]);\n"
242 " date2.setSeconds(arguments[5]);\n"
243 " case 4:\n"
244 " var middle = argc >> 1;\n"
245 " date1.setHours(arguments[0]);\n"
246 " date1.setMinutes(arguments[1]);\n"
247 " date2.setHours(arguments[middle]);\n"
248 " date2.setMinutes(arguments[middle + 1]);\n"
249 " if (middle == 2) {\n"
250 " date2.setSeconds(59);\n"
251 " }\n"
252 " break;\n"
253 " default:\n"
254 " throw 'timeRange: bad number of arguments'\n"
255 " }\n"
256 " }\n"
257 "\n"
258 " if (isGMT) {\n"
259 " date.setFullYear(date.getUTCFullYear());\n"
260 " date.setMonth(date.getUTCMonth());\n"
261 " date.setDate(date.getUTCDate());\n"
262 " date.setHours(date.getUTCHours());\n"
263 " date.setMinutes(date.getUTCMinutes());\n"
264 " date.setSeconds(date.getUTCSeconds());\n"
265 " }\n"
266 " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
267 " : (date2 >= date) || (date >= date1);\n"
268 "\n"
269 "}\n"
270 "";
271
272 // sRunning is defined for the helper functions only while the
273 // Javascript engine is running and the PAC object cannot be deleted
274 // or reset.
275 static uint32_t sRunningIndex = 0xdeadbeef;
GetRunning()276 static ProxyAutoConfig *GetRunning() {
277 MOZ_ASSERT(sRunningIndex != 0xdeadbeef);
278 return static_cast<ProxyAutoConfig *>(PR_GetThreadPrivate(sRunningIndex));
279 }
280
SetRunning(ProxyAutoConfig * arg)281 static void SetRunning(ProxyAutoConfig *arg) {
282 MOZ_ASSERT(sRunningIndex != 0xdeadbeef);
283 PR_SetThreadPrivate(sRunningIndex, arg);
284 }
285
286 // The PACResolver is used for dnsResolve()
287 class PACResolver final : public nsIDNSListener,
288 public nsITimerCallback,
289 public nsINamed {
290 public:
291 NS_DECL_THREADSAFE_ISUPPORTS
292
PACResolver(nsIEventTarget * aTarget)293 explicit PACResolver(nsIEventTarget *aTarget)
294 : mStatus(NS_ERROR_FAILURE), mMainThreadEventTarget(aTarget) {}
295
296 // nsIDNSListener
OnLookupComplete(nsICancelable * request,nsIDNSRecord * record,nsresult status)297 NS_IMETHOD OnLookupComplete(nsICancelable *request, nsIDNSRecord *record,
298 nsresult status) override {
299 if (mTimer) {
300 mTimer->Cancel();
301 mTimer = nullptr;
302 }
303
304 mRequest = nullptr;
305 mStatus = status;
306 mResponse = record;
307 return NS_OK;
308 }
309
310 // nsITimerCallback
Notify(nsITimer * timer)311 NS_IMETHOD Notify(nsITimer *timer) override {
312 nsCOMPtr<nsICancelable> request(mRequest);
313 if (request) request->Cancel(NS_ERROR_NET_TIMEOUT);
314 mTimer = nullptr;
315 return NS_OK;
316 }
317
318 // nsINamed
GetName(nsACString & aName)319 NS_IMETHOD GetName(nsACString &aName) override {
320 aName.AssignLiteral("PACResolver");
321 return NS_OK;
322 }
323
324 nsresult mStatus;
325 nsCOMPtr<nsICancelable> mRequest;
326 nsCOMPtr<nsIDNSRecord> mResponse;
327 nsCOMPtr<nsITimer> mTimer;
328 nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
329
330 private:
~PACResolver()331 ~PACResolver() {}
332 };
NS_IMPL_ISUPPORTS(PACResolver,nsIDNSListener,nsITimerCallback,nsINamed)333 NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed)
334
335 static void PACLogToConsole(nsString &aMessage) {
336 nsCOMPtr<nsIConsoleService> consoleService =
337 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
338 if (!consoleService) return;
339
340 consoleService->LogStringMessage(aMessage.get());
341 }
342
343 // Javascript errors and warnings are logged to the main error console
PACLogErrorOrWarning(const nsAString & aKind,JSErrorReport * aReport)344 static void PACLogErrorOrWarning(const nsAString &aKind,
345 JSErrorReport *aReport) {
346 nsString formattedMessage(NS_LITERAL_STRING("PAC Execution "));
347 formattedMessage += aKind;
348 formattedMessage += NS_LITERAL_STRING(": ");
349 if (aReport->message())
350 formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
351 formattedMessage += NS_LITERAL_STRING(" [");
352 formattedMessage.Append(aReport->linebuf(), aReport->linebufLength());
353 formattedMessage += NS_LITERAL_STRING("]");
354 PACLogToConsole(formattedMessage);
355 }
356
PACWarningReporter(JSContext * aCx,JSErrorReport * aReport)357 static void PACWarningReporter(JSContext *aCx, JSErrorReport *aReport) {
358 MOZ_ASSERT(aReport);
359 MOZ_ASSERT(JSREPORT_IS_WARNING(aReport->flags));
360
361 PACLogErrorOrWarning(NS_LITERAL_STRING("Warning"), aReport);
362 }
363
364 class MOZ_STACK_CLASS AutoPACErrorReporter {
365 JSContext *mCx;
366
367 public:
AutoPACErrorReporter(JSContext * aCx)368 explicit AutoPACErrorReporter(JSContext *aCx) : mCx(aCx) {}
~AutoPACErrorReporter()369 ~AutoPACErrorReporter() {
370 if (!JS_IsExceptionPending(mCx)) {
371 return;
372 }
373 JS::RootedValue exn(mCx);
374 if (!JS_GetPendingException(mCx, &exn)) {
375 return;
376 }
377 JS_ClearPendingException(mCx);
378
379 js::ErrorReport report(mCx);
380 if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) {
381 JS_ClearPendingException(mCx);
382 return;
383 }
384
385 PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), report.report());
386 }
387 };
388
389 // timeout of 0 means the normal necko timeout strategy, otherwise the dns
390 // request will be canceled after aTimeout milliseconds
PACResolve(const nsCString & aHostName,NetAddr * aNetAddr,unsigned int aTimeout)391 static bool PACResolve(const nsCString &aHostName, NetAddr *aNetAddr,
392 unsigned int aTimeout) {
393 if (!GetRunning()) {
394 NS_WARNING("PACResolve without a running ProxyAutoConfig object");
395 return false;
396 }
397
398 return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout);
399 }
400
ProxyAutoConfig()401 ProxyAutoConfig::ProxyAutoConfig()
402 : mJSContext(nullptr),
403 mJSNeedsSetup(false),
404 mShutdown(false),
405 mIncludePath(false),
406 mExtraHeapSize(0) {
407 MOZ_COUNT_CTOR(ProxyAutoConfig);
408 }
409
ResolveAddress(const nsCString & aHostName,NetAddr * aNetAddr,unsigned int aTimeout)410 bool ProxyAutoConfig::ResolveAddress(const nsCString &aHostName,
411 NetAddr *aNetAddr, unsigned int aTimeout) {
412 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
413 if (!dns) return false;
414
415 RefPtr<PACResolver> helper = new PACResolver(mMainThreadEventTarget);
416 OriginAttributes attrs;
417
418 if (NS_FAILED(dns->AsyncResolveNative(
419 aHostName, nsIDNSService::RESOLVE_PRIORITY_MEDIUM, helper,
420 GetCurrentThreadEventTarget(), attrs,
421 getter_AddRefs(helper->mRequest))))
422 return false;
423
424 if (aTimeout && helper->mRequest) {
425 if (!mTimer) mTimer = NS_NewTimer();
426 if (mTimer) {
427 mTimer->SetTarget(mMainThreadEventTarget);
428 mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
429 helper->mTimer = mTimer;
430 }
431 }
432
433 // Spin the event loop of the pac thread until lookup is complete.
434 // nsPACman is responsible for keeping a queue and only allowing
435 // one PAC execution at a time even when it is called re-entrantly.
436 SpinEventLoopUntil([&, helper]() { return !helper->mRequest; });
437
438 if (NS_FAILED(helper->mStatus) ||
439 NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr)))
440 return false;
441 return true;
442 }
443
PACResolveToString(const nsCString & aHostName,nsCString & aDottedDecimal,unsigned int aTimeout)444 static bool PACResolveToString(const nsCString &aHostName,
445 nsCString &aDottedDecimal,
446 unsigned int aTimeout) {
447 NetAddr netAddr;
448 if (!PACResolve(aHostName, &netAddr, aTimeout)) return false;
449
450 char dottedDecimal[128];
451 if (!NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal)))
452 return false;
453
454 aDottedDecimal.Assign(dottedDecimal);
455 return true;
456 }
457
458 // dnsResolve(host) javascript implementation
PACDnsResolve(JSContext * cx,unsigned int argc,JS::Value * vp)459 static bool PACDnsResolve(JSContext *cx, unsigned int argc, JS::Value *vp) {
460 JS::CallArgs args = CallArgsFromVp(argc, vp);
461
462 if (NS_IsMainThread()) {
463 NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
464 return false;
465 }
466
467 if (!args.requireAtLeast(cx, "dnsResolve", 1)) return false;
468
469 JS::Rooted<JSString *> arg1(cx, JS::ToString(cx, args[0]));
470 if (!arg1) return false;
471
472 nsAutoJSString hostName;
473 nsAutoCString dottedDecimal;
474
475 if (!hostName.init(cx, arg1)) return false;
476 if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
477 JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
478 if (!dottedDecimalString) {
479 return false;
480 }
481
482 args.rval().setString(dottedDecimalString);
483 } else {
484 args.rval().setNull();
485 }
486
487 return true;
488 }
489
490 // myIpAddress() javascript implementation
PACMyIpAddress(JSContext * cx,unsigned int argc,JS::Value * vp)491 static bool PACMyIpAddress(JSContext *cx, unsigned int argc, JS::Value *vp) {
492 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
493
494 if (NS_IsMainThread()) {
495 NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
496 return false;
497 }
498
499 if (!GetRunning()) {
500 NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
501 return false;
502 }
503
504 return GetRunning()->MyIPAddress(args);
505 }
506
507 // proxyAlert(msg) javascript implementation
PACProxyAlert(JSContext * cx,unsigned int argc,JS::Value * vp)508 static bool PACProxyAlert(JSContext *cx, unsigned int argc, JS::Value *vp) {
509 JS::CallArgs args = CallArgsFromVp(argc, vp);
510
511 if (!args.requireAtLeast(cx, "alert", 1)) return false;
512
513 JS::Rooted<JSString *> arg1(cx, JS::ToString(cx, args[0]));
514 if (!arg1) return false;
515
516 nsAutoJSString message;
517 if (!message.init(cx, arg1)) return false;
518
519 nsAutoString alertMessage;
520 alertMessage.SetCapacity(32 + message.Length());
521 alertMessage += NS_LITERAL_STRING("PAC-alert: ");
522 alertMessage += message;
523 PACLogToConsole(alertMessage);
524
525 args.rval().setUndefined(); /* return undefined */
526 return true;
527 }
528
529 static const JSFunctionSpec PACGlobalFunctions[] = {
530 JS_FN("dnsResolve", PACDnsResolve, 1, 0),
531
532 // a global "var pacUseMultihomedDNS = true;" will change behavior
533 // of myIpAddress to actively use DNS
534 JS_FN("myIpAddress", PACMyIpAddress, 0, 0),
535 JS_FN("alert", PACProxyAlert, 1, 0), JS_FS_END};
536
537 // JSContextWrapper is a c++ object that manages the context for the JS engine
538 // used on the PAC thread. It is initialized and destroyed on the PAC thread.
539 class JSContextWrapper {
540 public:
Create(uint32_t aExtraHeapSize)541 static JSContextWrapper *Create(uint32_t aExtraHeapSize) {
542 JSContext *cx = JS_NewContext(sContextHeapSize + aExtraHeapSize);
543 if (NS_WARN_IF(!cx)) return nullptr;
544
545 JSContextWrapper *entry = new JSContextWrapper(cx);
546 if (NS_FAILED(entry->Init())) {
547 delete entry;
548 return nullptr;
549 }
550
551 return entry;
552 }
553
Context() const554 JSContext *Context() const { return mContext; }
555
Global() const556 JSObject *Global() const { return mGlobal; }
557
~JSContextWrapper()558 ~JSContextWrapper() {
559 mGlobal = nullptr;
560
561 MOZ_COUNT_DTOR(JSContextWrapper);
562
563 if (mContext) {
564 JS_DestroyContext(mContext);
565 }
566 }
567
SetOK()568 void SetOK() { mOK = true; }
569
IsOK()570 bool IsOK() { return mOK; }
571
572 private:
573 static const uint32_t sContextHeapSize = 4 << 20; // 4 MB
574
575 JSContext *mContext;
576 JS::PersistentRooted<JSObject *> mGlobal;
577 bool mOK;
578
579 static const JSClass sGlobalClass;
580
JSContextWrapper(JSContext * cx)581 explicit JSContextWrapper(JSContext *cx)
582 : mContext(cx), mGlobal(cx, nullptr), mOK(false) {
583 MOZ_COUNT_CTOR(JSContextWrapper);
584 }
585
Init()586 nsresult Init() {
587 /*
588 * Not setting this will cause JS_CHECK_RECURSION to report false
589 * positives
590 */
591 JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
592
593 JS::SetWarningReporter(mContext, PACWarningReporter);
594
595 if (!JS::InitSelfHostedCode(mContext)) {
596 return NS_ERROR_OUT_OF_MEMORY;
597 }
598
599 JSAutoRequest ar(mContext);
600
601 JS::CompartmentOptions options;
602 options.creationOptions().setSystemZone();
603 mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
604 JS::DontFireOnNewGlobalHook, options);
605 if (!mGlobal) {
606 JS_ClearPendingException(mContext);
607 return NS_ERROR_OUT_OF_MEMORY;
608 }
609 JS::Rooted<JSObject *> global(mContext, mGlobal);
610
611 JSAutoCompartment ac(mContext, global);
612 AutoPACErrorReporter aper(mContext);
613 if (!JS_InitStandardClasses(mContext, global)) {
614 return NS_ERROR_FAILURE;
615 }
616 if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) {
617 return NS_ERROR_FAILURE;
618 }
619
620 JS_FireOnNewGlobalObject(mContext, global);
621
622 return NS_OK;
623 }
624 };
625
626 static const JSClassOps sJSContextWrapperGlobalClassOps = {
627 nullptr,
628 nullptr,
629 nullptr,
630 nullptr,
631 nullptr,
632 nullptr,
633 nullptr,
634 nullptr,
635 nullptr,
636 nullptr,
637 JS_GlobalObjectTraceHook};
638
639 const JSClass JSContextWrapper::sGlobalClass = {
640 "PACResolutionThreadGlobal", JSCLASS_GLOBAL_FLAGS,
641 &sJSContextWrapperGlobalClassOps};
642
SetThreadLocalIndex(uint32_t index)643 void ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) {
644 sRunningIndex = index;
645 }
646
Init(const nsCString & aPACURI,const nsCString & aPACScript,bool aIncludePath,uint32_t aExtraHeapSize,nsIEventTarget * aEventTarget)647 nsresult ProxyAutoConfig::Init(const nsCString &aPACURI,
648 const nsCString &aPACScript, bool aIncludePath,
649 uint32_t aExtraHeapSize,
650 nsIEventTarget *aEventTarget) {
651 mPACURI = aPACURI;
652 mPACScript = sPacUtils;
653 mPACScript.Append(aPACScript);
654 mIncludePath = aIncludePath;
655 mExtraHeapSize = aExtraHeapSize;
656 mMainThreadEventTarget = aEventTarget;
657
658 if (!GetRunning()) return SetupJS();
659
660 mJSNeedsSetup = true;
661 return NS_OK;
662 }
663
SetupJS()664 nsresult ProxyAutoConfig::SetupJS() {
665 mJSNeedsSetup = false;
666 MOZ_ASSERT(!GetRunning(), "JIT is running");
667
668 #if defined(XP_MACOSX)
669 nsMacUtilsImpl::EnableTCSMIfAvailable();
670 #endif
671
672 delete mJSContext;
673 mJSContext = nullptr;
674
675 if (mPACScript.IsEmpty()) return NS_ERROR_FAILURE;
676
677 NS_GetCurrentThread()->SetCanInvokeJS(true);
678
679 mJSContext = JSContextWrapper::Create(mExtraHeapSize);
680 if (!mJSContext) return NS_ERROR_FAILURE;
681
682 JSContext *cx = mJSContext->Context();
683 JSAutoRequest ar(cx);
684 JSAutoCompartment ac(cx, mJSContext->Global());
685 AutoPACErrorReporter aper(cx);
686
687 // check if this is a data: uri so that we don't spam the js console with
688 // huge meaningless strings. this is not on the main thread, so it can't
689 // use nsIURI scheme methods
690 bool isDataURI =
691 nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5);
692
693 SetRunning(this);
694 JS::Rooted<JSObject *> global(cx, mJSContext->Global());
695 JS::CompileOptions options(cx);
696 options.setFileAndLine(mPACURI.get(), 1);
697 JS::Rooted<JSScript *> script(cx);
698 if (!JS_CompileScript(cx, mPACScript.get(), mPACScript.Length(), options,
699 &script) ||
700 !JS_ExecuteScript(cx, script)) {
701 nsString alertMessage(
702 NS_LITERAL_STRING("PAC file failed to install from "));
703 if (isDataURI) {
704 alertMessage += NS_LITERAL_STRING("data: URI");
705 } else {
706 alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
707 }
708 PACLogToConsole(alertMessage);
709 SetRunning(nullptr);
710 return NS_ERROR_FAILURE;
711 }
712 SetRunning(nullptr);
713
714 mJSContext->SetOK();
715 nsString alertMessage(NS_LITERAL_STRING("PAC file installed from "));
716 if (isDataURI) {
717 alertMessage += NS_LITERAL_STRING("data: URI");
718 } else {
719 alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
720 }
721 PACLogToConsole(alertMessage);
722
723 // we don't need these now
724 mPACScript.Truncate();
725 mPACURI.Truncate();
726
727 return NS_OK;
728 }
729
GetProxyForURI(const nsCString & aTestURI,const nsCString & aTestHost,nsACString & result)730 nsresult ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI,
731 const nsCString &aTestHost,
732 nsACString &result) {
733 if (mJSNeedsSetup) SetupJS();
734
735 if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE;
736
737 JSContext *cx = mJSContext->Context();
738 JSAutoRequest ar(cx);
739 JSAutoCompartment ac(cx, mJSContext->Global());
740 AutoPACErrorReporter aper(cx);
741
742 // the sRunning flag keeps a new PAC file from being installed
743 // while the event loop is spinning on a DNS function. Don't early return.
744 SetRunning(this);
745 mRunningHost = aTestHost;
746
747 nsresult rv = NS_ERROR_FAILURE;
748 nsCString clensedURI = aTestURI;
749
750 if (!mIncludePath) {
751 nsCOMPtr<nsIURLParser> urlParser =
752 do_GetService(NS_STDURLPARSER_CONTRACTID);
753 int32_t pathLen = 0;
754 if (urlParser) {
755 uint32_t schemePos;
756 int32_t schemeLen;
757 uint32_t authorityPos;
758 int32_t authorityLen;
759 uint32_t pathPos;
760 rv = urlParser->ParseURL(aTestURI.get(), aTestURI.Length(), &schemePos,
761 &schemeLen, &authorityPos, &authorityLen,
762 &pathPos, &pathLen);
763 }
764 if (NS_SUCCEEDED(rv)) {
765 if (pathLen) {
766 // cut off the path but leave the initial slash
767 pathLen--;
768 }
769 aTestURI.Left(clensedURI, aTestURI.Length() - pathLen);
770 }
771 }
772
773 JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get()));
774 JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get()));
775
776 if (uriString && hostString) {
777 JS::AutoValueArray<2> args(cx);
778 args[0].setString(uriString);
779 args[1].setString(hostString);
780
781 JS::Rooted<JS::Value> rval(cx);
782 JS::Rooted<JSObject *> global(cx, mJSContext->Global());
783 bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval);
784
785 if (ok && rval.isString()) {
786 nsAutoJSString pacString;
787 if (pacString.init(cx, rval.toString())) {
788 CopyUTF16toUTF8(pacString, result);
789 rv = NS_OK;
790 }
791 }
792 }
793
794 mRunningHost.Truncate();
795 SetRunning(nullptr);
796 return rv;
797 }
798
GC()799 void ProxyAutoConfig::GC() {
800 if (!mJSContext || !mJSContext->IsOK()) return;
801
802 JSAutoCompartment ac(mJSContext->Context(), mJSContext->Global());
803 JS_MaybeGC(mJSContext->Context());
804 }
805
~ProxyAutoConfig()806 ProxyAutoConfig::~ProxyAutoConfig() {
807 MOZ_COUNT_DTOR(ProxyAutoConfig);
808 MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor.");
809 NS_ASSERTION(!mJSContext,
810 "~ProxyAutoConfig leaking JS context that "
811 "should have been deleted on pac thread");
812 }
813
Shutdown()814 void ProxyAutoConfig::Shutdown() {
815 MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown");
816
817 if (NS_WARN_IF(GetRunning()) || mShutdown) {
818 return;
819 }
820
821 mShutdown = true;
822 delete mJSContext;
823 mJSContext = nullptr;
824 }
825
SrcAddress(const NetAddr * remoteAddress,nsCString & localAddress)826 bool ProxyAutoConfig::SrcAddress(const NetAddr *remoteAddress,
827 nsCString &localAddress) {
828 PRFileDesc *fd;
829 fd = PR_OpenUDPSocket(remoteAddress->raw.family);
830 if (!fd) return false;
831
832 PRNetAddr prRemoteAddress;
833 NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress);
834 if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) {
835 PR_Close(fd);
836 return false;
837 }
838
839 PRNetAddr localName;
840 if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
841 PR_Close(fd);
842 return false;
843 }
844
845 PR_Close(fd);
846
847 char dottedDecimal[128];
848 if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) !=
849 PR_SUCCESS)
850 return false;
851
852 localAddress.Assign(dottedDecimal);
853
854 return true;
855 }
856
857 // hostName is run through a dns lookup and then a udp socket is connected
858 // to the result. If that all works, the local IP address of the socket is
859 // returned to the javascript caller and |*aResult| is set to true. Otherwise
860 // |*aResult| is set to false.
MyIPAddressTryHost(const nsCString & hostName,unsigned int timeout,const JS::CallArgs & aArgs,bool * aResult)861 bool ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName,
862 unsigned int timeout,
863 const JS::CallArgs &aArgs,
864 bool *aResult) {
865 *aResult = false;
866
867 NetAddr remoteAddress;
868 nsAutoCString localDottedDecimal;
869 JSContext *cx = mJSContext->Context();
870
871 if (PACResolve(hostName, &remoteAddress, timeout) &&
872 SrcAddress(&remoteAddress, localDottedDecimal)) {
873 JSString *dottedDecimalString =
874 JS_NewStringCopyZ(cx, localDottedDecimal.get());
875 if (!dottedDecimalString) {
876 return false;
877 }
878
879 *aResult = true;
880 aArgs.rval().setString(dottedDecimalString);
881 }
882 return true;
883 }
884
MyIPAddress(const JS::CallArgs & aArgs)885 bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs) {
886 nsAutoCString remoteDottedDecimal;
887 nsAutoCString localDottedDecimal;
888 JSContext *cx = mJSContext->Context();
889 JS::RootedValue v(cx);
890 JS::Rooted<JSObject *> global(cx, mJSContext->Global());
891
892 bool useMultihomedDNS =
893 JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) &&
894 !v.isUndefined() && ToBoolean(v);
895
896 // first, lookup the local address of a socket connected
897 // to the host of uri being resolved by the pac file. This is
898 // v6 safe.. but is the last step like that
899 bool rvalAssigned = false;
900 if (useMultihomedDNS) {
901 if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
902 rvalAssigned) {
903 return rvalAssigned;
904 }
905 } else {
906 // we can still do the fancy multi homing thing if the host is a literal
907 PRNetAddr tempAddr;
908 memset(&tempAddr, 0, sizeof(PRNetAddr));
909 if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) &&
910 (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
911 rvalAssigned)) {
912 return rvalAssigned;
913 }
914 }
915
916 // next, look for a route to a public internet address that doesn't need DNS.
917 // This is the google anycast dns address, but it doesn't matter if it
918 // remains operable (as we don't contact it) as long as the address stays
919 // in commonly routed IP address space.
920 remoteDottedDecimal.AssignLiteral("8.8.8.8");
921 if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
922 rvalAssigned) {
923 return rvalAssigned;
924 }
925
926 // finally, use the old algorithm based on the local hostname
927 nsAutoCString hostName;
928 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
929 // without multihomedDNS use such a short timeout that we are basically
930 // just looking at the cache for raw dotted decimals
931 uint32_t timeout = useMultihomedDNS ? kTimeout : 1;
932 if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
933 PACResolveToString(hostName, localDottedDecimal, timeout)) {
934 JSString *dottedDecimalString =
935 JS_NewStringCopyZ(cx, localDottedDecimal.get());
936 if (!dottedDecimalString) {
937 return false;
938 }
939
940 aArgs.rval().setString(dottedDecimalString);
941 return true;
942 }
943
944 // next try a couple RFC 1918 variants.. maybe there is a
945 // local route
946 remoteDottedDecimal.AssignLiteral("192.168.0.1");
947 if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
948 rvalAssigned) {
949 return rvalAssigned;
950 }
951
952 // more RFC 1918
953 remoteDottedDecimal.AssignLiteral("10.0.0.1");
954 if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
955 rvalAssigned) {
956 return rvalAssigned;
957 }
958
959 // who knows? let's fallback to localhost
960 localDottedDecimal.AssignLiteral("127.0.0.1");
961 JSString *dottedDecimalString =
962 JS_NewStringCopyZ(cx, localDottedDecimal.get());
963 if (!dottedDecimalString) {
964 return false;
965 }
966
967 aArgs.rval().setString(dottedDecimalString);
968 return true;
969 }
970
971 } // namespace net
972 } // namespace mozilla
973