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