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