Coverage Report

Created: 2018-09-25 14:53

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