netwerk/base/src/ProxyAutoConfig.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/base/src/ProxyAutoConfig.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,879 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "ProxyAutoConfig.h"
    1.11 +#include "nsICancelable.h"
    1.12 +#include "nsIDNSListener.h"
    1.13 +#include "nsIDNSRecord.h"
    1.14 +#include "nsIDNSService.h"
    1.15 +#include "nsThreadUtils.h"
    1.16 +#include "nsIConsoleService.h"
    1.17 +#include "nsJSUtils.h"
    1.18 +#include "jsfriendapi.h"
    1.19 +#include "prnetdb.h"
    1.20 +#include "nsITimer.h"
    1.21 +#include "mozilla/net/DNS.h"
    1.22 +#include "nsServiceManagerUtils.h"
    1.23 +#include "nsNetCID.h"
    1.24 +
    1.25 +namespace mozilla {
    1.26 +namespace net {
    1.27 +
    1.28 +// These are some global helper symbols the PAC format requires that we provide that
    1.29 +// are initialized as part of the global javascript context used for PAC evaluations.
    1.30 +// Additionally dnsResolve(host) and myIpAddress() are supplied in the same context
    1.31 +// but are implemented as c++ helpers. alert(msg) is similarly defined.
    1.32 +
    1.33 +static const char *sPacUtils =
    1.34 +  "function dnsDomainIs(host, domain) {\n"
    1.35 +  "    return (host.length >= domain.length &&\n"
    1.36 +  "            host.substring(host.length - domain.length) == domain);\n"
    1.37 +  "}\n"
    1.38 +  ""
    1.39 +  "function dnsDomainLevels(host) {\n"
    1.40 +  "    return host.split('.').length - 1;\n"
    1.41 +  "}\n"
    1.42 +  ""
    1.43 +  "function convert_addr(ipchars) {\n"
    1.44 +  "    var bytes = ipchars.split('.');\n"
    1.45 +  "    var result = ((bytes[0] & 0xff) << 24) |\n"
    1.46 +  "                 ((bytes[1] & 0xff) << 16) |\n"
    1.47 +  "                 ((bytes[2] & 0xff) <<  8) |\n"
    1.48 +  "                  (bytes[3] & 0xff);\n"
    1.49 +  "    return result;\n"
    1.50 +  "}\n"
    1.51 +  ""
    1.52 +  "function isInNet(ipaddr, pattern, maskstr) {\n"
    1.53 +  "    var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n"
    1.54 +  "    if (test == null) {\n"
    1.55 +  "        ipaddr = dnsResolve(ipaddr);\n"
    1.56 +  "        if (ipaddr == null)\n"
    1.57 +  "            return false;\n"
    1.58 +  "    } else if (test[1] > 255 || test[2] > 255 || \n"
    1.59 +  "               test[3] > 255 || test[4] > 255) {\n"
    1.60 +  "        return false;    // not an IP address\n"
    1.61 +  "    }\n"
    1.62 +  "    var host = convert_addr(ipaddr);\n"
    1.63 +  "    var pat  = convert_addr(pattern);\n"
    1.64 +  "    var mask = convert_addr(maskstr);\n"
    1.65 +  "    return ((host & mask) == (pat & mask));\n"
    1.66 +  "    \n"
    1.67 +  "}\n"
    1.68 +  ""
    1.69 +  "function isPlainHostName(host) {\n"
    1.70 +  "    return (host.search('\\\\.') == -1);\n"
    1.71 +  "}\n"
    1.72 +  ""
    1.73 +  "function isResolvable(host) {\n"
    1.74 +  "    var ip = dnsResolve(host);\n"
    1.75 +  "    return (ip != null);\n"
    1.76 +  "}\n"
    1.77 +  ""
    1.78 +  "function localHostOrDomainIs(host, hostdom) {\n"
    1.79 +  "    return (host == hostdom) ||\n"
    1.80 +  "           (hostdom.lastIndexOf(host + '.', 0) == 0);\n"
    1.81 +  "}\n"
    1.82 +  ""
    1.83 +  "function shExpMatch(url, pattern) {\n"
    1.84 +  "   pattern = pattern.replace(/\\./g, '\\\\.');\n"
    1.85 +  "   pattern = pattern.replace(/\\*/g, '.*');\n"
    1.86 +  "   pattern = pattern.replace(/\\?/g, '.');\n"
    1.87 +  "   var newRe = new RegExp('^'+pattern+'$');\n"
    1.88 +  "   return newRe.test(url);\n"
    1.89 +  "}\n"
    1.90 +  ""
    1.91 +  "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n"
    1.92 +  "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"
    1.93 +  ""
    1.94 +  "function weekdayRange() {\n"
    1.95 +  "    function getDay(weekday) {\n"
    1.96 +  "        if (weekday in wdays) {\n"
    1.97 +  "            return wdays[weekday];\n"
    1.98 +  "        }\n"
    1.99 +  "        return -1;\n"
   1.100 +  "    }\n"
   1.101 +  "    var date = new Date();\n"
   1.102 +  "    var argc = arguments.length;\n"
   1.103 +  "    var wday;\n"
   1.104 +  "    if (argc < 1)\n"
   1.105 +  "        return false;\n"
   1.106 +  "    if (arguments[argc - 1] == 'GMT') {\n"
   1.107 +  "        argc--;\n"
   1.108 +  "        wday = date.getUTCDay();\n"
   1.109 +  "    } else {\n"
   1.110 +  "        wday = date.getDay();\n"
   1.111 +  "    }\n"
   1.112 +  "    var wd1 = getDay(arguments[0]);\n"
   1.113 +  "    var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n"
   1.114 +  "    return (wd1 == -1 || wd2 == -1) ? false\n"
   1.115 +  "                                    : (wd1 <= wday && wday <= wd2);\n"
   1.116 +  "}\n"
   1.117 +  ""
   1.118 +  "function dateRange() {\n"
   1.119 +  "    function getMonth(name) {\n"
   1.120 +  "        if (name in months) {\n"
   1.121 +  "            return months[name];\n"
   1.122 +  "        }\n"
   1.123 +  "        return -1;\n"
   1.124 +  "    }\n"
   1.125 +  "    var date = new Date();\n"
   1.126 +  "    var argc = arguments.length;\n"
   1.127 +  "    if (argc < 1) {\n"
   1.128 +  "        return false;\n"
   1.129 +  "    }\n"
   1.130 +  "    var isGMT = (arguments[argc - 1] == 'GMT');\n"
   1.131 +  "\n"
   1.132 +  "    if (isGMT) {\n"
   1.133 +  "        argc--;\n"
   1.134 +  "    }\n"
   1.135 +  "    // function will work even without explict handling of this case\n"
   1.136 +  "    if (argc == 1) {\n"
   1.137 +  "        var tmp = parseInt(arguments[0]);\n"
   1.138 +  "        if (isNaN(tmp)) {\n"
   1.139 +  "            return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n"
   1.140 +  "                     getMonth(arguments[0]));\n"
   1.141 +  "        } else if (tmp < 32) {\n"
   1.142 +  "            return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n"
   1.143 +  "        } else { \n"
   1.144 +  "            return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n"
   1.145 +  "                     tmp);\n"
   1.146 +  "        }\n"
   1.147 +  "    }\n"
   1.148 +  "    var year = date.getFullYear();\n"
   1.149 +  "    var date1, date2;\n"
   1.150 +  "    date1 = new Date(year,  0,  1,  0,  0,  0);\n"
   1.151 +  "    date2 = new Date(year, 11, 31, 23, 59, 59);\n"
   1.152 +  "    var adjustMonth = false;\n"
   1.153 +  "    for (var i = 0; i < (argc >> 1); i++) {\n"
   1.154 +  "        var tmp = parseInt(arguments[i]);\n"
   1.155 +  "        if (isNaN(tmp)) {\n"
   1.156 +  "            var mon = getMonth(arguments[i]);\n"
   1.157 +  "            date1.setMonth(mon);\n"
   1.158 +  "        } else if (tmp < 32) {\n"
   1.159 +  "            adjustMonth = (argc <= 2);\n"
   1.160 +  "            date1.setDate(tmp);\n"
   1.161 +  "        } else {\n"
   1.162 +  "            date1.setFullYear(tmp);\n"
   1.163 +  "        }\n"
   1.164 +  "    }\n"
   1.165 +  "    for (var i = (argc >> 1); i < argc; i++) {\n"
   1.166 +  "        var tmp = parseInt(arguments[i]);\n"
   1.167 +  "        if (isNaN(tmp)) {\n"
   1.168 +  "            var mon = getMonth(arguments[i]);\n"
   1.169 +  "            date2.setMonth(mon);\n"
   1.170 +  "        } else if (tmp < 32) {\n"
   1.171 +  "            date2.setDate(tmp);\n"
   1.172 +  "        } else {\n"
   1.173 +  "            date2.setFullYear(tmp);\n"
   1.174 +  "        }\n"
   1.175 +  "    }\n"
   1.176 +  "    if (adjustMonth) {\n"
   1.177 +  "        date1.setMonth(date.getMonth());\n"
   1.178 +  "        date2.setMonth(date.getMonth());\n"
   1.179 +  "    }\n"
   1.180 +  "    if (isGMT) {\n"
   1.181 +  "    var tmp = date;\n"
   1.182 +  "        tmp.setFullYear(date.getUTCFullYear());\n"
   1.183 +  "        tmp.setMonth(date.getUTCMonth());\n"
   1.184 +  "        tmp.setDate(date.getUTCDate());\n"
   1.185 +  "        tmp.setHours(date.getUTCHours());\n"
   1.186 +  "        tmp.setMinutes(date.getUTCMinutes());\n"
   1.187 +  "        tmp.setSeconds(date.getUTCSeconds());\n"
   1.188 +  "        date = tmp;\n"
   1.189 +  "    }\n"
   1.190 +  "    return ((date1 <= date) && (date <= date2));\n"
   1.191 +  "}\n"
   1.192 +  ""
   1.193 +  "function timeRange() {\n"
   1.194 +  "    var argc = arguments.length;\n"
   1.195 +  "    var date = new Date();\n"
   1.196 +  "    var isGMT= false;\n"
   1.197 +  ""
   1.198 +  "    if (argc < 1) {\n"
   1.199 +  "        return false;\n"
   1.200 +  "    }\n"
   1.201 +  "    if (arguments[argc - 1] == 'GMT') {\n"
   1.202 +  "        isGMT = true;\n"
   1.203 +  "        argc--;\n"
   1.204 +  "    }\n"
   1.205 +  "\n"
   1.206 +  "    var hour = isGMT ? date.getUTCHours() : date.getHours();\n"
   1.207 +  "    var date1, date2;\n"
   1.208 +  "    date1 = new Date();\n"
   1.209 +  "    date2 = new Date();\n"
   1.210 +  "\n"
   1.211 +  "    if (argc == 1) {\n"
   1.212 +  "        return (hour == arguments[0]);\n"
   1.213 +  "    } else if (argc == 2) {\n"
   1.214 +  "        return ((arguments[0] <= hour) && (hour <= arguments[1]));\n"
   1.215 +  "    } else {\n"
   1.216 +  "        switch (argc) {\n"
   1.217 +  "        case 6:\n"
   1.218 +  "            date1.setSeconds(arguments[2]);\n"
   1.219 +  "            date2.setSeconds(arguments[5]);\n"
   1.220 +  "        case 4:\n"
   1.221 +  "            var middle = argc >> 1;\n"
   1.222 +  "            date1.setHours(arguments[0]);\n"
   1.223 +  "            date1.setMinutes(arguments[1]);\n"
   1.224 +  "            date2.setHours(arguments[middle]);\n"
   1.225 +  "            date2.setMinutes(arguments[middle + 1]);\n"
   1.226 +  "            if (middle == 2) {\n"
   1.227 +  "                date2.setSeconds(59);\n"
   1.228 +  "            }\n"
   1.229 +  "            break;\n"
   1.230 +  "        default:\n"
   1.231 +  "          throw 'timeRange: bad number of arguments'\n"
   1.232 +  "        }\n"
   1.233 +  "    }\n"
   1.234 +  "\n"
   1.235 +  "    if (isGMT) {\n"
   1.236 +  "        date.setFullYear(date.getUTCFullYear());\n"
   1.237 +  "        date.setMonth(date.getUTCMonth());\n"
   1.238 +  "        date.setDate(date.getUTCDate());\n"
   1.239 +  "        date.setHours(date.getUTCHours());\n"
   1.240 +  "        date.setMinutes(date.getUTCMinutes());\n"
   1.241 +  "        date.setSeconds(date.getUTCSeconds());\n"
   1.242 +  "    }\n"
   1.243 +  "    return ((date1 <= date) && (date <= date2));\n"
   1.244 +  "}\n"
   1.245 +  "";
   1.246 +
   1.247 +// sRunning is defined for the helper functions only while the
   1.248 +// Javascript engine is running and the PAC object cannot be deleted
   1.249 +// or reset.
   1.250 +static ProxyAutoConfig *sRunning = nullptr;
   1.251 +
   1.252 +// The PACResolver is used for dnsResolve()
   1.253 +class PACResolver MOZ_FINAL : public nsIDNSListener
   1.254 +                            , public nsITimerCallback
   1.255 +{
   1.256 +public:
   1.257 +  NS_DECL_THREADSAFE_ISUPPORTS
   1.258 +
   1.259 +  PACResolver()
   1.260 +    : mStatus(NS_ERROR_FAILURE)
   1.261 +  {
   1.262 +  }
   1.263 +
   1.264 +  // nsIDNSListener
   1.265 +  NS_IMETHODIMP OnLookupComplete(nsICancelable *request,
   1.266 +                                 nsIDNSRecord *record,
   1.267 +                                 nsresult status)
   1.268 +  {
   1.269 +    if (mTimer) {
   1.270 +      mTimer->Cancel();
   1.271 +      mTimer = nullptr;
   1.272 +    }
   1.273 +
   1.274 +    mRequest = nullptr;
   1.275 +    mStatus = status;
   1.276 +    mResponse = record;
   1.277 +    return NS_OK;
   1.278 +  }
   1.279 +
   1.280 +  // nsITimerCallback
   1.281 +  NS_IMETHODIMP Notify(nsITimer *timer) 
   1.282 +  {
   1.283 +    if (mRequest)
   1.284 +      mRequest->Cancel(NS_ERROR_NET_TIMEOUT);
   1.285 +    mTimer = nullptr;
   1.286 +    return NS_OK;
   1.287 +  }
   1.288 +
   1.289 +  nsresult                mStatus;
   1.290 +  nsCOMPtr<nsICancelable> mRequest;
   1.291 +  nsCOMPtr<nsIDNSRecord>  mResponse;
   1.292 +  nsCOMPtr<nsITimer>      mTimer;
   1.293 +};
   1.294 +NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback)
   1.295 +
   1.296 +static
   1.297 +void PACLogToConsole(nsString &aMessage)
   1.298 +{
   1.299 +  nsCOMPtr<nsIConsoleService> consoleService =
   1.300 +    do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   1.301 +  if (!consoleService)
   1.302 +    return;
   1.303 +
   1.304 +  consoleService->LogStringMessage(aMessage.get());
   1.305 +}
   1.306 +
   1.307 +// Javascript errors are logged to the main error console
   1.308 +static void
   1.309 +PACErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
   1.310 +{
   1.311 +  nsString formattedMessage(NS_LITERAL_STRING("PAC Execution Error: "));
   1.312 +  formattedMessage += report->ucmessage;
   1.313 +  formattedMessage += NS_LITERAL_STRING(" [");
   1.314 +  formattedMessage += report->uclinebuf;
   1.315 +  formattedMessage += NS_LITERAL_STRING("]");
   1.316 +  PACLogToConsole(formattedMessage);
   1.317 +}
   1.318 +
   1.319 +// timeout of 0 means the normal necko timeout strategy, otherwise the dns request
   1.320 +// will be canceled after aTimeout milliseconds
   1.321 +static
   1.322 +bool PACResolve(const nsCString &aHostName, NetAddr *aNetAddr,
   1.323 +                unsigned int aTimeout)
   1.324 +{
   1.325 +  if (!sRunning) {
   1.326 +    NS_WARNING("PACResolve without a running ProxyAutoConfig object");
   1.327 +    return false;
   1.328 +  }
   1.329 +
   1.330 +  return sRunning->ResolveAddress(aHostName, aNetAddr, aTimeout);
   1.331 +}
   1.332 +
   1.333 +ProxyAutoConfig::ProxyAutoConfig()
   1.334 +  : mJSRuntime(nullptr)
   1.335 +  , mJSNeedsSetup(false)
   1.336 +  , mShutdown(false)
   1.337 +{
   1.338 +  MOZ_COUNT_CTOR(ProxyAutoConfig);
   1.339 +}
   1.340 +
   1.341 +bool
   1.342 +ProxyAutoConfig::ResolveAddress(const nsCString &aHostName,
   1.343 +                                NetAddr *aNetAddr,
   1.344 +                                unsigned int aTimeout)
   1.345 +{
   1.346 +  nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
   1.347 +  if (!dns)
   1.348 +    return false;
   1.349 +
   1.350 +  nsRefPtr<PACResolver> helper = new PACResolver();
   1.351 +
   1.352 +  if (NS_FAILED(dns->AsyncResolve(aHostName,
   1.353 +                                  nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
   1.354 +                                  helper,
   1.355 +                                  NS_GetCurrentThread(),
   1.356 +                                  getter_AddRefs(helper->mRequest))))
   1.357 +    return false;
   1.358 +
   1.359 +  if (aTimeout && helper->mRequest) {
   1.360 +    if (!mTimer)
   1.361 +      mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   1.362 +    if (mTimer) {
   1.363 +      mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT);
   1.364 +      helper->mTimer = mTimer;
   1.365 +    }
   1.366 +  }
   1.367 +
   1.368 +  // Spin the event loop of the pac thread until lookup is complete.
   1.369 +  // nsPACman is responsible for keeping a queue and only allowing
   1.370 +  // one PAC execution at a time even when it is called re-entrantly.
   1.371 +  while (helper->mRequest)
   1.372 +    NS_ProcessNextEvent(NS_GetCurrentThread());
   1.373 +
   1.374 +  if (NS_FAILED(helper->mStatus) ||
   1.375 +      NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr)))
   1.376 +    return false;
   1.377 +  return true;
   1.378 +}
   1.379 +
   1.380 +static
   1.381 +bool PACResolveToString(const nsCString &aHostName,
   1.382 +                        nsCString &aDottedDecimal,
   1.383 +                        unsigned int aTimeout)
   1.384 +{
   1.385 +  NetAddr netAddr;
   1.386 +  if (!PACResolve(aHostName, &netAddr, aTimeout))
   1.387 +    return false;
   1.388 +
   1.389 +  char dottedDecimal[128];
   1.390 +  if (!NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal)))
   1.391 +    return false;
   1.392 +
   1.393 +  aDottedDecimal.Assign(dottedDecimal);
   1.394 +  return true;
   1.395 +}
   1.396 +
   1.397 +// dnsResolve(host) javascript implementation
   1.398 +static
   1.399 +bool PACDnsResolve(JSContext *cx, unsigned int argc, JS::Value *vp)
   1.400 +{
   1.401 +  JS::CallArgs args = CallArgsFromVp(argc, vp);
   1.402 +
   1.403 +  if (NS_IsMainThread()) {
   1.404 +    NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
   1.405 +    return false;
   1.406 +  }
   1.407 +
   1.408 +  JS::Rooted<JSString*> arg1(cx);
   1.409 +  if (!JS_ConvertArguments(cx, args, "S", arg1.address()))
   1.410 +    return false;
   1.411 +
   1.412 +  nsDependentJSString hostName;
   1.413 +  nsAutoCString dottedDecimal;
   1.414 +
   1.415 +  if (!hostName.init(cx, arg1))
   1.416 +    return false;
   1.417 +  if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
   1.418 +    JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
   1.419 +    if (!dottedDecimalString) {
   1.420 +      return false;
   1.421 +    }
   1.422 +
   1.423 +    args.rval().setString(dottedDecimalString);
   1.424 +  }
   1.425 +  else {
   1.426 +    args.rval().setNull();
   1.427 +  }
   1.428 +
   1.429 +  return true;
   1.430 +}
   1.431 +
   1.432 +// myIpAddress() javascript implementation
   1.433 +static
   1.434 +bool PACMyIpAddress(JSContext *cx, unsigned int argc, JS::Value *vp)
   1.435 +{
   1.436 +  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   1.437 +
   1.438 +  if (NS_IsMainThread()) {
   1.439 +    NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
   1.440 +    return false;
   1.441 +  }
   1.442 +
   1.443 +  if (!sRunning) {
   1.444 +    NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
   1.445 +    return false;
   1.446 +  }
   1.447 +
   1.448 +  return sRunning->MyIPAddress(args);
   1.449 +}
   1.450 +
   1.451 +// proxyAlert(msg) javascript implementation
   1.452 +static
   1.453 +bool PACProxyAlert(JSContext *cx, unsigned int argc, JS::Value *vp)
   1.454 +{
   1.455 +  JS::CallArgs args = CallArgsFromVp(argc, vp);
   1.456 +
   1.457 +  JS::Rooted<JSString*> arg1(cx);
   1.458 +  if (!JS_ConvertArguments(cx, args, "S", arg1.address()))
   1.459 +    return false;
   1.460 +
   1.461 +  nsDependentJSString message;
   1.462 +  if (!message.init(cx, arg1))
   1.463 +    return false;
   1.464 +
   1.465 +  nsString alertMessage;
   1.466 +  alertMessage.SetCapacity(32 + message.Length());
   1.467 +  alertMessage += NS_LITERAL_STRING("PAC-alert: ");
   1.468 +  alertMessage += message;
   1.469 +  PACLogToConsole(alertMessage);
   1.470 +
   1.471 +  args.rval().setUndefined();  /* return undefined */
   1.472 +  return true;
   1.473 +}
   1.474 +
   1.475 +static const JSFunctionSpec PACGlobalFunctions[] = {
   1.476 +  JS_FS("dnsResolve", PACDnsResolve, 1, 0),
   1.477 +  JS_FS("myIpAddress", PACMyIpAddress, 0, 0),
   1.478 +  JS_FS("alert", PACProxyAlert, 1, 0),
   1.479 +  JS_FS_END
   1.480 +};
   1.481 +
   1.482 +// JSRuntimeWrapper is a c++ object that manages the runtime and context
   1.483 +// for the JS engine used on the PAC thread. It is initialized and destroyed
   1.484 +// on the PAC thread.
   1.485 +class JSRuntimeWrapper
   1.486 +{
   1.487 + public:
   1.488 +  static JSRuntimeWrapper *Create()
   1.489 +  {
   1.490 +    JSRuntimeWrapper *entry = new JSRuntimeWrapper();
   1.491 +
   1.492 +    if (NS_FAILED(entry->Init())) {
   1.493 +      delete entry;
   1.494 +      return nullptr;
   1.495 +    }
   1.496 +
   1.497 +    return entry;
   1.498 +  }
   1.499 +
   1.500 +  JSContext *Context() const
   1.501 +  {
   1.502 +    return mContext;
   1.503 +  }
   1.504 +
   1.505 +  JSObject *Global() const
   1.506 +  {
   1.507 +    return mGlobal;
   1.508 +  }
   1.509 +
   1.510 +  ~JSRuntimeWrapper()
   1.511 +  {
   1.512 +    MOZ_COUNT_DTOR(JSRuntimeWrapper);
   1.513 +    if (mContext) {
   1.514 +      JS_DestroyContext(mContext);
   1.515 +    }
   1.516 +
   1.517 +    if (mRuntime) {
   1.518 +      JS_DestroyRuntime(mRuntime);
   1.519 +    }
   1.520 +  }
   1.521 +
   1.522 +  void SetOK()
   1.523 +  {
   1.524 +    mOK = true;
   1.525 +  }
   1.526 +
   1.527 +  bool IsOK()
   1.528 +  {
   1.529 +    return mOK;
   1.530 +  }
   1.531 +
   1.532 +private:
   1.533 +  static const unsigned sRuntimeHeapSize = 2 << 20;
   1.534 +
   1.535 +  JSRuntime *mRuntime;
   1.536 +  JSContext *mContext;
   1.537 +  JSObject  *mGlobal;
   1.538 +  bool      mOK;
   1.539 +
   1.540 +  static const JSClass sGlobalClass;
   1.541 +
   1.542 +  JSRuntimeWrapper()
   1.543 +    : mRuntime(nullptr), mContext(nullptr), mGlobal(nullptr), mOK(false)
   1.544 +  {
   1.545 +      MOZ_COUNT_CTOR(JSRuntimeWrapper);
   1.546 +  }
   1.547 +
   1.548 +  nsresult Init()
   1.549 +  {
   1.550 +    mRuntime = JS_NewRuntime(sRuntimeHeapSize, JS_NO_HELPER_THREADS);
   1.551 +    NS_ENSURE_TRUE(mRuntime, NS_ERROR_OUT_OF_MEMORY);
   1.552 +
   1.553 +    /*
   1.554 +     * Not setting this will cause JS_CHECK_RECURSION to report false
   1.555 +     * positives
   1.556 +     */
   1.557 +    JS_SetNativeStackQuota(mRuntime, 128 * sizeof(size_t) * 1024); 
   1.558 +
   1.559 +    mContext = JS_NewContext(mRuntime, 0);
   1.560 +    NS_ENSURE_TRUE(mContext, NS_ERROR_OUT_OF_MEMORY);
   1.561 +
   1.562 +    JSAutoRequest ar(mContext);
   1.563 +
   1.564 +    JS::CompartmentOptions options;
   1.565 +    options.setZone(JS::SystemZone)
   1.566 +           .setVersion(JSVERSION_LATEST);
   1.567 +    mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
   1.568 +                                 JS::DontFireOnNewGlobalHook, options);
   1.569 +    NS_ENSURE_TRUE(mGlobal, NS_ERROR_OUT_OF_MEMORY);
   1.570 +    JS::Rooted<JSObject*> global(mContext, mGlobal);
   1.571 +
   1.572 +    JSAutoCompartment ac(mContext, global);
   1.573 +    js::SetDefaultObjectForContext(mContext, global);
   1.574 +    JS_InitStandardClasses(mContext, global);
   1.575 +
   1.576 +    JS_SetErrorReporter(mContext, PACErrorReporter);
   1.577 +
   1.578 +    if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions))
   1.579 +      return NS_ERROR_FAILURE;
   1.580 +
   1.581 +    JS_FireOnNewGlobalObject(mContext, global);
   1.582 +
   1.583 +    return NS_OK;
   1.584 +  }
   1.585 +};
   1.586 +
   1.587 +const JSClass JSRuntimeWrapper::sGlobalClass = {
   1.588 +  "PACResolutionThreadGlobal",
   1.589 +  JSCLASS_GLOBAL_FLAGS,
   1.590 +  JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
   1.591 +  JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
   1.592 +  nullptr, nullptr, nullptr, nullptr,
   1.593 +  JS_GlobalObjectTraceHook
   1.594 +};
   1.595 +
   1.596 +nsresult
   1.597 +ProxyAutoConfig::Init(const nsCString &aPACURI,
   1.598 +                      const nsCString &aPACScript)
   1.599 +{
   1.600 +  mPACURI = aPACURI;
   1.601 +  mPACScript = sPacUtils;
   1.602 +  mPACScript.Append(aPACScript);
   1.603 +
   1.604 +  if (!sRunning)
   1.605 +    return SetupJS();
   1.606 +
   1.607 +  mJSNeedsSetup = true;
   1.608 +  return NS_OK;
   1.609 +}
   1.610 +
   1.611 +nsresult
   1.612 +ProxyAutoConfig::SetupJS()
   1.613 +{
   1.614 +  mJSNeedsSetup = false;
   1.615 +  NS_ABORT_IF_FALSE(!sRunning, "JIT is running");
   1.616 +
   1.617 +  delete mJSRuntime;
   1.618 +  mJSRuntime = nullptr;
   1.619 +
   1.620 +  if (mPACScript.IsEmpty())
   1.621 +    return NS_ERROR_FAILURE;
   1.622 +
   1.623 +  mJSRuntime = JSRuntimeWrapper::Create();
   1.624 +  if (!mJSRuntime)
   1.625 +    return NS_ERROR_FAILURE;
   1.626 +
   1.627 +  JSContext* cx = mJSRuntime->Context();
   1.628 +  JSAutoRequest ar(cx);
   1.629 +  JSAutoCompartment ac(cx, mJSRuntime->Global());
   1.630 +
   1.631 +  // check if this is a data: uri so that we don't spam the js console with
   1.632 +  // huge meaningless strings. this is not on the main thread, so it can't
   1.633 +  // use nsIRUI scheme methods
   1.634 +  bool isDataURI = nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5);
   1.635 +
   1.636 +  sRunning = this;
   1.637 +  JS::Rooted<JSObject*> global(cx, mJSRuntime->Global());
   1.638 +  JS::CompileOptions options(cx);
   1.639 +  options.setFileAndLine(mPACURI.get(), 1);
   1.640 +  JS::Rooted<JSScript*> script(cx, JS_CompileScript(cx, global, mPACScript.get(),
   1.641 +                                                    mPACScript.Length(), options));
   1.642 +  if (!script || !JS_ExecuteScript(cx, global, script)) {
   1.643 +    nsString alertMessage(NS_LITERAL_STRING("PAC file failed to install from "));
   1.644 +    if (isDataURI) {
   1.645 +      alertMessage += NS_LITERAL_STRING("data: URI");
   1.646 +    }
   1.647 +    else {
   1.648 +      alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
   1.649 +    }
   1.650 +    PACLogToConsole(alertMessage);
   1.651 +    sRunning = nullptr;
   1.652 +    return NS_ERROR_FAILURE;
   1.653 +  }
   1.654 +  sRunning = nullptr;
   1.655 +
   1.656 +  mJSRuntime->SetOK();
   1.657 +  nsString alertMessage(NS_LITERAL_STRING("PAC file installed from "));
   1.658 +  if (isDataURI) {
   1.659 +    alertMessage += NS_LITERAL_STRING("data: URI");
   1.660 +  }
   1.661 +  else {
   1.662 +    alertMessage += NS_ConvertUTF8toUTF16(mPACURI);
   1.663 +  }
   1.664 +  PACLogToConsole(alertMessage);
   1.665 +
   1.666 +  // we don't need these now
   1.667 +  mPACScript.Truncate();
   1.668 +  mPACURI.Truncate();
   1.669 +
   1.670 +  return NS_OK;
   1.671 +}
   1.672 +
   1.673 +nsresult
   1.674 +ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI,
   1.675 +                                const nsCString &aTestHost,
   1.676 +                                nsACString &result)
   1.677 +{
   1.678 +  if (mJSNeedsSetup)
   1.679 +    SetupJS();
   1.680 +
   1.681 +  if (!mJSRuntime || !mJSRuntime->IsOK())
   1.682 +    return NS_ERROR_NOT_AVAILABLE;
   1.683 +
   1.684 +  JSContext *cx = mJSRuntime->Context();
   1.685 +  JSAutoRequest ar(cx);
   1.686 +  JSAutoCompartment ac(cx, mJSRuntime->Global());
   1.687 +
   1.688 +  // the sRunning flag keeps a new PAC file from being installed
   1.689 +  // while the event loop is spinning on a DNS function. Don't early return.
   1.690 +  sRunning = this;
   1.691 +  mRunningHost = aTestHost;
   1.692 +
   1.693 +  nsresult rv = NS_ERROR_FAILURE;
   1.694 +  JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, aTestURI.get()));
   1.695 +  JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get()));
   1.696 +
   1.697 +  if (uriString && hostString) {
   1.698 +    JS::AutoValueArray<2> args(cx);
   1.699 +    args[0].setString(uriString);
   1.700 +    args[1].setString(hostString);
   1.701 +
   1.702 +    JS::Rooted<JS::Value> rval(cx);
   1.703 +    JS::Rooted<JSObject*> global(cx, mJSRuntime->Global());
   1.704 +    bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval);
   1.705 +
   1.706 +    if (ok && rval.isString()) {
   1.707 +      nsDependentJSString pacString;
   1.708 +      if (pacString.init(cx, rval.toString())) {
   1.709 +        CopyUTF16toUTF8(pacString, result);
   1.710 +        rv = NS_OK;
   1.711 +      }
   1.712 +    }
   1.713 +  }
   1.714 +
   1.715 +  mRunningHost.Truncate();
   1.716 +  sRunning = nullptr;
   1.717 +  return rv;
   1.718 +}
   1.719 +
   1.720 +void
   1.721 +ProxyAutoConfig::GC()
   1.722 +{
   1.723 +  if (!mJSRuntime || !mJSRuntime->IsOK())
   1.724 +    return;
   1.725 +
   1.726 +  JSAutoCompartment ac(mJSRuntime->Context(), mJSRuntime->Global());
   1.727 +  JS_MaybeGC(mJSRuntime->Context());
   1.728 +}
   1.729 +
   1.730 +ProxyAutoConfig::~ProxyAutoConfig()
   1.731 +{
   1.732 +  MOZ_COUNT_DTOR(ProxyAutoConfig);
   1.733 +  NS_ASSERTION(!mJSRuntime,
   1.734 +               "~ProxyAutoConfig leaking JS runtime that "
   1.735 +               "should have been deleted on pac thread");
   1.736 +}
   1.737 +
   1.738 +void
   1.739 +ProxyAutoConfig::Shutdown()
   1.740 +{
   1.741 +  NS_ABORT_IF_FALSE(!NS_IsMainThread(), "wrong thread for shutdown");
   1.742 +
   1.743 +  if (sRunning || mShutdown)
   1.744 +    return;
   1.745 +
   1.746 +  mShutdown = true;
   1.747 +  delete mJSRuntime;
   1.748 +  mJSRuntime = nullptr;
   1.749 +}
   1.750 +
   1.751 +bool
   1.752 +ProxyAutoConfig::SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress)
   1.753 +{
   1.754 +  PRFileDesc *fd;
   1.755 +  fd = PR_OpenUDPSocket(remoteAddress->raw.family);
   1.756 +  if (!fd)
   1.757 +    return false;
   1.758 +
   1.759 +  PRNetAddr prRemoteAddress;
   1.760 +  NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress);
   1.761 +  if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) {
   1.762 +    PR_Close(fd);
   1.763 +    return false;
   1.764 +  }
   1.765 +
   1.766 +  PRNetAddr localName;
   1.767 +  if (PR_GetSockName(fd, &localName) != PR_SUCCESS) {
   1.768 +    PR_Close(fd);
   1.769 +    return false;
   1.770 +  }
   1.771 +
   1.772 +  PR_Close(fd);
   1.773 +  
   1.774 +  char dottedDecimal[128];
   1.775 +  if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS)
   1.776 +    return false;
   1.777 +  
   1.778 +  localAddress.Assign(dottedDecimal);
   1.779 +
   1.780 +  return true;
   1.781 +}
   1.782 +
   1.783 +// hostName is run through a dns lookup and then a udp socket is connected
   1.784 +// to the result. If that all works, the local IP address of the socket is
   1.785 +// returned to the javascript caller and |*aResult| is set to true. Otherwise
   1.786 +// |*aResult| is set to false.
   1.787 +bool
   1.788 +ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName,
   1.789 +                                    unsigned int timeout,
   1.790 +                                    const JS::CallArgs &aArgs,
   1.791 +                                    bool* aResult)
   1.792 +{
   1.793 +  *aResult = false;
   1.794 +
   1.795 +  NetAddr remoteAddress;
   1.796 +  nsAutoCString localDottedDecimal;
   1.797 +  JSContext *cx = mJSRuntime->Context();
   1.798 +
   1.799 +  if (PACResolve(hostName, &remoteAddress, timeout) &&
   1.800 +      SrcAddress(&remoteAddress, localDottedDecimal)) {
   1.801 +    JSString *dottedDecimalString =
   1.802 +      JS_NewStringCopyZ(cx, localDottedDecimal.get());
   1.803 +    if (!dottedDecimalString) {
   1.804 +      return false;
   1.805 +    }
   1.806 +
   1.807 +    *aResult = true;
   1.808 +    aArgs.rval().setString(dottedDecimalString);
   1.809 +  }
   1.810 +  return true;
   1.811 +}
   1.812 +
   1.813 +bool
   1.814 +ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs)
   1.815 +{
   1.816 +  nsAutoCString remoteDottedDecimal;
   1.817 +  nsAutoCString localDottedDecimal;
   1.818 +  JSContext *cx = mJSRuntime->Context();
   1.819 +
   1.820 +  // first, lookup the local address of a socket connected
   1.821 +  // to the host of uri being resolved by the pac file. This is
   1.822 +  // v6 safe.. but is the last step like that
   1.823 +  bool rvalAssigned = false;
   1.824 +  if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
   1.825 +      rvalAssigned) {
   1.826 +    return rvalAssigned;
   1.827 +  }
   1.828 +
   1.829 +  // next, look for a route to a public internet address that doesn't need DNS.
   1.830 +  // This is the google anycast dns address, but it doesn't matter if it
   1.831 +  // remains operable (as we don't contact it) as long as the address stays
   1.832 +  // in commonly routed IP address space.
   1.833 +  remoteDottedDecimal.AssignLiteral("8.8.8.8");
   1.834 +  if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
   1.835 +      rvalAssigned) {
   1.836 +    return rvalAssigned;
   1.837 +  }
   1.838 +  
   1.839 +  // next, use the old algorithm based on the local hostname
   1.840 +  nsAutoCString hostName;
   1.841 +  nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
   1.842 +  if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
   1.843 +      PACResolveToString(hostName, localDottedDecimal, kTimeout)) {
   1.844 +    JSString *dottedDecimalString =
   1.845 +      JS_NewStringCopyZ(cx, localDottedDecimal.get());
   1.846 +    if (!dottedDecimalString) {
   1.847 +      return false;
   1.848 +    }
   1.849 +
   1.850 +    aArgs.rval().setString(dottedDecimalString);
   1.851 +    return true;
   1.852 +  }
   1.853 +
   1.854 +  // next try a couple RFC 1918 variants.. maybe there is a
   1.855 +  // local route
   1.856 +  remoteDottedDecimal.AssignLiteral("192.168.0.1");
   1.857 +  if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
   1.858 +      rvalAssigned) {
   1.859 +    return rvalAssigned;
   1.860 +  }
   1.861 +
   1.862 +  // more RFC 1918
   1.863 +  remoteDottedDecimal.AssignLiteral("10.0.0.1");
   1.864 +  if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
   1.865 +      rvalAssigned) {
   1.866 +    return rvalAssigned;
   1.867 +  }
   1.868 +
   1.869 +  // who knows? let's fallback to localhost
   1.870 +  localDottedDecimal.AssignLiteral("127.0.0.1");
   1.871 +  JSString *dottedDecimalString =
   1.872 +    JS_NewStringCopyZ(cx, localDottedDecimal.get());
   1.873 +  if (!dottedDecimalString) {
   1.874 +    return false;
   1.875 +  }
   1.876 +
   1.877 +  aArgs.rval().setString(dottedDecimalString);
   1.878 +  return true;
   1.879 +}
   1.880 +
   1.881 +} // namespace mozilla
   1.882 +} // namespace mozilla::net

mercurial