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