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