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