diff options
Diffstat (limited to 'netwerk/base/ProxyAutoConfig.cpp')
-rw-r--r-- | netwerk/base/ProxyAutoConfig.cpp | 1015 |
1 files changed, 1015 insertions, 0 deletions
diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp new file mode 100644 index 000000000..4d7a6c1fd --- /dev/null +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -0,0 +1,1015 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProxyAutoConfig.h" +#include "nsICancelable.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsThreadUtils.h" +#include "nsIConsoleService.h" +#include "nsIURLParser.h" +#include "nsJSUtils.h" +#include "jsfriendapi.h" +#include "prnetdb.h" +#include "nsITimer.h" +#include "mozilla/net/DNS.h" +#include "nsServiceManagerUtils.h" +#include "nsNetCID.h" + +namespace mozilla { +namespace net { + +// These are some global helper symbols the PAC format requires that we provide that +// are initialized as part of the global javascript context used for PAC evaluations. +// Additionally dnsResolve(host) and myIpAddress() are supplied in the same context +// but are implemented as c++ helpers. alert(msg) is similarly defined. + +static const char *sPacUtils = + "function dnsDomainIs(host, domain) {\n" + " return (host.length >= domain.length &&\n" + " host.substring(host.length - domain.length) == domain);\n" + "}\n" + "" + "function dnsDomainLevels(host) {\n" + " return host.split('.').length - 1;\n" + "}\n" + "" + "function convert_addr(ipchars) {\n" + " var bytes = ipchars.split('.');\n" + " var result = ((bytes[0] & 0xff) << 24) |\n" + " ((bytes[1] & 0xff) << 16) |\n" + " ((bytes[2] & 0xff) << 8) |\n" + " (bytes[3] & 0xff);\n" + " return result;\n" + "}\n" + "" + "function isInNet(ipaddr, pattern, maskstr) {\n" + " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" + " if (test == null) {\n" + " ipaddr = dnsResolve(ipaddr);\n" + " if (ipaddr == null)\n" + " return false;\n" + " } else if (test[1] > 255 || test[2] > 255 || \n" + " test[3] > 255 || test[4] > 255) {\n" + " return false; // not an IP address\n" + " }\n" + " var host = convert_addr(ipaddr);\n" + " var pat = convert_addr(pattern);\n" + " var mask = convert_addr(maskstr);\n" + " return ((host & mask) == (pat & mask));\n" + " \n" + "}\n" + "" + "function isPlainHostName(host) {\n" + " return (host.search('\\\\.') == -1);\n" + "}\n" + "" + "function isResolvable(host) {\n" + " var ip = dnsResolve(host);\n" + " return (ip != null);\n" + "}\n" + "" + "function localHostOrDomainIs(host, hostdom) {\n" + " return (host == hostdom) ||\n" + " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" + "}\n" + "" + "function shExpMatch(url, pattern) {\n" + " pattern = pattern.replace(/\\./g, '\\\\.');\n" + " pattern = pattern.replace(/\\*/g, '.*');\n" + " pattern = pattern.replace(/\\?/g, '.');\n" + " var newRe = new RegExp('^'+pattern+'$');\n" + " return newRe.test(url);\n" + "}\n" + "" + "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" + "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" + "" + "function weekdayRange() {\n" + " function getDay(weekday) {\n" + " if (weekday in wdays) {\n" + " return wdays[weekday];\n" + " }\n" + " return -1;\n" + " }\n" + " var date = new Date();\n" + " var argc = arguments.length;\n" + " var wday;\n" + " if (argc < 1)\n" + " return false;\n" + " if (arguments[argc - 1] == 'GMT') {\n" + " argc--;\n" + " wday = date.getUTCDay();\n" + " } else {\n" + " wday = date.getDay();\n" + " }\n" + " var wd1 = getDay(arguments[0]);\n" + " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" + " return (wd1 == -1 || wd2 == -1) ? false\n" + " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n" + " : (wd2 >= wday || wday >= wd1);\n" + "}\n" + "" + "function dateRange() {\n" + " function getMonth(name) {\n" + " if (name in months) {\n" + " return months[name];\n" + " }\n" + " return -1;\n" + " }\n" + " var date = new Date();\n" + " var argc = arguments.length;\n" + " if (argc < 1) {\n" + " return false;\n" + " }\n" + " var isGMT = (arguments[argc - 1] == 'GMT');\n" + "\n" + " if (isGMT) {\n" + " argc--;\n" + " }\n" + " // function will work even without explict handling of this case\n" + " if (argc == 1) {\n" + " var tmp = parseInt(arguments[0]);\n" + " if (isNaN(tmp)) {\n" + " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" + " getMonth(arguments[0]));\n" + " } else if (tmp < 32) {\n" + " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" + " } else { \n" + " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n" + " tmp);\n" + " }\n" + " }\n" + " var year = date.getFullYear();\n" + " var date1, date2;\n" + " date1 = new Date(year, 0, 1, 0, 0, 0);\n" + " date2 = new Date(year, 11, 31, 23, 59, 59);\n" + " var adjustMonth = false;\n" + " for (var i = 0; i < (argc >> 1); i++) {\n" + " var tmp = parseInt(arguments[i]);\n" + " if (isNaN(tmp)) {\n" + " var mon = getMonth(arguments[i]);\n" + " date1.setMonth(mon);\n" + " } else if (tmp < 32) {\n" + " adjustMonth = (argc <= 2);\n" + " date1.setDate(tmp);\n" + " } else {\n" + " date1.setFullYear(tmp);\n" + " }\n" + " }\n" + " for (var i = (argc >> 1); i < argc; i++) {\n" + " var tmp = parseInt(arguments[i]);\n" + " if (isNaN(tmp)) {\n" + " var mon = getMonth(arguments[i]);\n" + " date2.setMonth(mon);\n" + " } else if (tmp < 32) {\n" + " date2.setDate(tmp);\n" + " } else {\n" + " date2.setFullYear(tmp);\n" + " }\n" + " }\n" + " if (adjustMonth) {\n" + " date1.setMonth(date.getMonth());\n" + " date2.setMonth(date.getMonth());\n" + " }\n" + " if (isGMT) {\n" + " var tmp = date;\n" + " tmp.setFullYear(date.getUTCFullYear());\n" + " tmp.setMonth(date.getUTCMonth());\n" + " tmp.setDate(date.getUTCDate());\n" + " tmp.setHours(date.getUTCHours());\n" + " tmp.setMinutes(date.getUTCMinutes());\n" + " tmp.setSeconds(date.getUTCSeconds());\n" + " date = tmp;\n" + " }\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "}\n" + "" + "function timeRange() {\n" + " var argc = arguments.length;\n" + " var date = new Date();\n" + " var isGMT= false;\n" + "" + " if (argc < 1) {\n" + " return false;\n" + " }\n" + " if (arguments[argc - 1] == 'GMT') {\n" + " isGMT = true;\n" + " argc--;\n" + " }\n" + "\n" + " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" + " var date1, date2;\n" + " date1 = new Date();\n" + " date2 = new Date();\n" + "\n" + " if (argc == 1) {\n" + " return (hour == arguments[0]);\n" + " } else if (argc == 2) {\n" + " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" + " } else {\n" + " switch (argc) {\n" + " case 6:\n" + " date1.setSeconds(arguments[2]);\n" + " date2.setSeconds(arguments[5]);\n" + " case 4:\n" + " var middle = argc >> 1;\n" + " date1.setHours(arguments[0]);\n" + " date1.setMinutes(arguments[1]);\n" + " date2.setHours(arguments[middle]);\n" + " date2.setMinutes(arguments[middle + 1]);\n" + " if (middle == 2) {\n" + " date2.setSeconds(59);\n" + " }\n" + " break;\n" + " default:\n" + " throw 'timeRange: bad number of arguments'\n" + " }\n" + " }\n" + "\n" + " if (isGMT) {\n" + " date.setFullYear(date.getUTCFullYear());\n" + " date.setMonth(date.getUTCMonth());\n" + " date.setDate(date.getUTCDate());\n" + " date.setHours(date.getUTCHours());\n" + " date.setMinutes(date.getUTCMinutes());\n" + " date.setSeconds(date.getUTCSeconds());\n" + " }\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "\n" + "}\n" + ""; + +// sRunning is defined for the helper functions only while the +// Javascript engine is running and the PAC object cannot be deleted +// or reset. +static uint32_t sRunningIndex = 0xdeadbeef; +static ProxyAutoConfig *GetRunning() +{ + MOZ_ASSERT(sRunningIndex != 0xdeadbeef); + return static_cast<ProxyAutoConfig *>(PR_GetThreadPrivate(sRunningIndex)); +} + +static void SetRunning(ProxyAutoConfig *arg) +{ + MOZ_ASSERT(sRunningIndex != 0xdeadbeef); + PR_SetThreadPrivate(sRunningIndex, arg); +} + +// The PACResolver is used for dnsResolve() +class PACResolver final : public nsIDNSListener + , public nsITimerCallback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + PACResolver() + : mStatus(NS_ERROR_FAILURE) + { + } + + // nsIDNSListener + NS_IMETHOD OnLookupComplete(nsICancelable *request, + nsIDNSRecord *record, + nsresult status) override + { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + mRequest = nullptr; + mStatus = status; + mResponse = record; + return NS_OK; + } + + // nsITimerCallback + NS_IMETHOD Notify(nsITimer *timer) override + { + if (mRequest) + mRequest->Cancel(NS_ERROR_NET_TIMEOUT); + mTimer = nullptr; + return NS_OK; + } + + nsresult mStatus; + nsCOMPtr<nsICancelable> mRequest; + nsCOMPtr<nsIDNSRecord> mResponse; + nsCOMPtr<nsITimer> mTimer; + +private: + ~PACResolver() {} +}; +NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback) + +static +void PACLogToConsole(nsString &aMessage) +{ + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!consoleService) + return; + + consoleService->LogStringMessage(aMessage.get()); +} + +// Javascript errors and warnings are logged to the main error console +static void +PACLogErrorOrWarning(const nsAString& aKind, JSErrorReport* aReport) +{ + nsString formattedMessage(NS_LITERAL_STRING("PAC Execution ")); + formattedMessage += aKind; + formattedMessage += NS_LITERAL_STRING(": "); + if (aReport->message()) + formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); + formattedMessage += NS_LITERAL_STRING(" ["); + formattedMessage.Append(aReport->linebuf(), aReport->linebufLength()); + formattedMessage += NS_LITERAL_STRING("]"); + PACLogToConsole(formattedMessage); +} + +static void +PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) +{ + MOZ_ASSERT(aReport); + MOZ_ASSERT(JSREPORT_IS_WARNING(aReport->flags)); + + PACLogErrorOrWarning(NS_LITERAL_STRING("Warning"), aReport); +} + +class MOZ_STACK_CLASS AutoPACErrorReporter +{ + JSContext* mCx; + +public: + explicit AutoPACErrorReporter(JSContext* aCx) + : mCx(aCx) + {} + ~AutoPACErrorReporter() { + if (!JS_IsExceptionPending(mCx)) { + return; + } + JS::RootedValue exn(mCx); + if (!JS_GetPendingException(mCx, &exn)) { + return; + } + JS_ClearPendingException(mCx); + + js::ErrorReport report(mCx); + if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) { + JS_ClearPendingException(mCx); + return; + } + + PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), report.report()); + } +}; + +// timeout of 0 means the normal necko timeout strategy, otherwise the dns request +// will be canceled after aTimeout milliseconds +static +bool PACResolve(const nsCString &aHostName, NetAddr *aNetAddr, + unsigned int aTimeout) +{ + if (!GetRunning()) { + NS_WARNING("PACResolve without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout); +} + +ProxyAutoConfig::ProxyAutoConfig() + : mJSContext(nullptr) + , mJSNeedsSetup(false) + , mShutdown(false) + , mIncludePath(false) +{ + MOZ_COUNT_CTOR(ProxyAutoConfig); +} + +bool +ProxyAutoConfig::ResolveAddress(const nsCString &aHostName, + NetAddr *aNetAddr, + unsigned int aTimeout) +{ + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + if (!dns) + return false; + + RefPtr<PACResolver> helper = new PACResolver(); + + if (NS_FAILED(dns->AsyncResolve(aHostName, + nsIDNSService::RESOLVE_PRIORITY_MEDIUM, + helper, + NS_GetCurrentThread(), + getter_AddRefs(helper->mRequest)))) + return false; + + if (aTimeout && helper->mRequest) { + if (!mTimer) + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (mTimer) { + mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT); + helper->mTimer = mTimer; + } + } + + // Spin the event loop of the pac thread until lookup is complete. + // nsPACman is responsible for keeping a queue and only allowing + // one PAC execution at a time even when it is called re-entrantly. + while (helper->mRequest) + NS_ProcessNextEvent(NS_GetCurrentThread()); + + if (NS_FAILED(helper->mStatus) || + NS_FAILED(helper->mResponse->GetNextAddr(0, aNetAddr))) + return false; + return true; +} + +static +bool PACResolveToString(const nsCString &aHostName, + nsCString &aDottedDecimal, + unsigned int aTimeout) +{ + NetAddr netAddr; + if (!PACResolve(aHostName, &netAddr, aTimeout)) + return false; + + char dottedDecimal[128]; + if (!NetAddrToString(&netAddr, dottedDecimal, sizeof(dottedDecimal))) + return false; + + aDottedDecimal.Assign(dottedDecimal); + return true; +} + +// dnsResolve(host) javascript implementation +static +bool PACDnsResolve(JSContext *cx, unsigned int argc, JS::Value *vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!args.requireAtLeast(cx, "dnsResolve", 1)) + return false; + + JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) + return false; + + nsAutoJSString hostName; + nsAutoCString dottedDecimal; + + if (!hostName.init(cx, arg1)) + return false; + if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) { + JSString *dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + args.rval().setString(dottedDecimalString); + } + else { + args.rval().setNull(); + } + + return true; +} + +// myIpAddress() javascript implementation +static +bool PACMyIpAddress(JSContext *cx, unsigned int argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (NS_IsMainThread()) { + NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); + return false; + } + + if (!GetRunning()) { + NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object"); + return false; + } + + return GetRunning()->MyIPAddress(args); +} + +// proxyAlert(msg) javascript implementation +static +bool PACProxyAlert(JSContext *cx, unsigned int argc, JS::Value *vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "alert", 1)) + return false; + + JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0])); + if (!arg1) + return false; + + nsAutoJSString message; + if (!message.init(cx, arg1)) + return false; + + nsAutoString alertMessage; + alertMessage.SetCapacity(32 + message.Length()); + alertMessage += NS_LITERAL_STRING("PAC-alert: "); + alertMessage += message; + PACLogToConsole(alertMessage); + + args.rval().setUndefined(); /* return undefined */ + return true; +} + +static const JSFunctionSpec PACGlobalFunctions[] = { + JS_FS("dnsResolve", PACDnsResolve, 1, 0), + + // a global "var pacUseMultihomedDNS = true;" will change behavior + // of myIpAddress to actively use DNS + JS_FS("myIpAddress", PACMyIpAddress, 0, 0), + JS_FS("alert", PACProxyAlert, 1, 0), + JS_FS_END +}; + +// JSContextWrapper is a c++ object that manages the context for the JS engine +// used on the PAC thread. It is initialized and destroyed on the PAC thread. +class JSContextWrapper +{ + public: + static JSContextWrapper *Create() + { + JSContext* cx = JS_NewContext(sContextHeapSize); + if (NS_WARN_IF(!cx)) + return nullptr; + + JSContextWrapper *entry = new JSContextWrapper(cx); + if (NS_FAILED(entry->Init())) { + delete entry; + return nullptr; + } + + return entry; + } + + JSContext *Context() const + { + return mContext; + } + + JSObject *Global() const + { + return mGlobal; + } + + ~JSContextWrapper() + { + mGlobal = nullptr; + + MOZ_COUNT_DTOR(JSContextWrapper); + + if (mContext) { + JS_DestroyContext(mContext); + } + } + + void SetOK() + { + mOK = true; + } + + bool IsOK() + { + return mOK; + } + +private: + static const unsigned sContextHeapSize = 4 << 20; // 4 MB + + JSContext *mContext; + JS::PersistentRooted<JSObject*> mGlobal; + bool mOK; + + static const JSClass sGlobalClass; + + explicit JSContextWrapper(JSContext* cx) + : mContext(cx), mGlobal(cx, nullptr), mOK(false) + { + MOZ_COUNT_CTOR(JSContextWrapper); + } + + nsresult Init() + { + /* + * Not setting this will cause JS_CHECK_RECURSION to report false + * positives + */ + JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024); + + JS::SetWarningReporter(mContext, PACWarningReporter); + + if (!JS::InitSelfHostedCode(mContext)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + JSAutoRequest ar(mContext); + + JS::CompartmentOptions options; + options.creationOptions().setZone(JS::SystemZone); + options.behaviors().setVersion(JSVERSION_LATEST); + mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr, + JS::DontFireOnNewGlobalHook, options); + if (!mGlobal) { + JS_ClearPendingException(mContext); + return NS_ERROR_OUT_OF_MEMORY; + } + JS::Rooted<JSObject*> global(mContext, mGlobal); + + JSAutoCompartment ac(mContext, global); + AutoPACErrorReporter aper(mContext); + if (!JS_InitStandardClasses(mContext, global)) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) { + return NS_ERROR_FAILURE; + } + + JS_FireOnNewGlobalObject(mContext, global); + + return NS_OK; + } +}; + +static const JSClassOps sJSContextWrapperGlobalClassOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + JS_GlobalObjectTraceHook +}; + +const JSClass JSContextWrapper::sGlobalClass = { + "PACResolutionThreadGlobal", + JSCLASS_GLOBAL_FLAGS, + &sJSContextWrapperGlobalClassOps +}; + +void +ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) +{ + sRunningIndex = index; +} + +nsresult +ProxyAutoConfig::Init(const nsCString &aPACURI, + const nsCString &aPACScript, + bool aIncludePath) +{ + mPACURI = aPACURI; + mPACScript = sPacUtils; + mPACScript.Append(aPACScript); + mIncludePath = aIncludePath; + + if (!GetRunning()) + return SetupJS(); + + mJSNeedsSetup = true; + return NS_OK; +} + +nsresult +ProxyAutoConfig::SetupJS() +{ + mJSNeedsSetup = false; + MOZ_ASSERT(!GetRunning(), "JIT is running"); + + delete mJSContext; + mJSContext = nullptr; + + if (mPACScript.IsEmpty()) + return NS_ERROR_FAILURE; + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + mJSContext = JSContextWrapper::Create(); + if (!mJSContext) + return NS_ERROR_FAILURE; + + JSContext* cx = mJSContext->Context(); + JSAutoRequest ar(cx); + JSAutoCompartment ac(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // check if this is a data: uri so that we don't spam the js console with + // huge meaningless strings. this is not on the main thread, so it can't + // use nsIURI scheme methods + bool isDataURI = nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5); + + SetRunning(this); + JS::Rooted<JSObject*> global(cx, mJSContext->Global()); + JS::CompileOptions options(cx); + options.setFileAndLine(mPACURI.get(), 1); + JS::Rooted<JSScript*> script(cx); + if (!JS_CompileScript(cx, mPACScript.get(), mPACScript.Length(), options, + &script) || + !JS_ExecuteScript(cx, script)) + { + nsString alertMessage(NS_LITERAL_STRING("PAC file failed to install from ")); + if (isDataURI) { + alertMessage += NS_LITERAL_STRING("data: URI"); + } + else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + SetRunning(nullptr); + return NS_ERROR_FAILURE; + } + SetRunning(nullptr); + + mJSContext->SetOK(); + nsString alertMessage(NS_LITERAL_STRING("PAC file installed from ")); + if (isDataURI) { + alertMessage += NS_LITERAL_STRING("data: URI"); + } + else { + alertMessage += NS_ConvertUTF8toUTF16(mPACURI); + } + PACLogToConsole(alertMessage); + + // we don't need these now + mPACScript.Truncate(); + mPACURI.Truncate(); + + return NS_OK; +} + +nsresult +ProxyAutoConfig::GetProxyForURI(const nsCString &aTestURI, + const nsCString &aTestHost, + nsACString &result) +{ + if (mJSNeedsSetup) + SetupJS(); + + if (!mJSContext || !mJSContext->IsOK()) + return NS_ERROR_NOT_AVAILABLE; + + JSContext *cx = mJSContext->Context(); + JSAutoRequest ar(cx); + JSAutoCompartment ac(cx, mJSContext->Global()); + AutoPACErrorReporter aper(cx); + + // the sRunning flag keeps a new PAC file from being installed + // while the event loop is spinning on a DNS function. Don't early return. + SetRunning(this); + mRunningHost = aTestHost; + + nsresult rv = NS_ERROR_FAILURE; + nsCString clensedURI = aTestURI; + + if (!mIncludePath) { + nsCOMPtr<nsIURLParser> urlParser = + do_GetService(NS_STDURLPARSER_CONTRACTID); + int32_t pathLen = 0; + if (urlParser) { + uint32_t schemePos; + int32_t schemeLen; + uint32_t authorityPos; + int32_t authorityLen; + uint32_t pathPos; + rv = urlParser->ParseURL(aTestURI.get(), aTestURI.Length(), + &schemePos, &schemeLen, + &authorityPos, &authorityLen, + &pathPos, &pathLen); + } + if (NS_SUCCEEDED(rv)) { + if (pathLen) { + // cut off the path but leave the initial slash + pathLen--; + } + aTestURI.Left(clensedURI, aTestURI.Length() - pathLen); + } + } + + JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get())); + JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get())); + + if (uriString && hostString) { + JS::AutoValueArray<2> args(cx); + args[0].setString(uriString); + args[1].setString(hostString); + + JS::Rooted<JS::Value> rval(cx); + JS::Rooted<JSObject*> global(cx, mJSContext->Global()); + bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval); + + if (ok && rval.isString()) { + nsAutoJSString pacString; + if (pacString.init(cx, rval.toString())) { + CopyUTF16toUTF8(pacString, result); + rv = NS_OK; + } + } + } + + mRunningHost.Truncate(); + SetRunning(nullptr); + return rv; +} + +void +ProxyAutoConfig::GC() +{ + if (!mJSContext || !mJSContext->IsOK()) + return; + + JSAutoCompartment ac(mJSContext->Context(), mJSContext->Global()); + JS_MaybeGC(mJSContext->Context()); +} + +ProxyAutoConfig::~ProxyAutoConfig() +{ + MOZ_COUNT_DTOR(ProxyAutoConfig); + NS_ASSERTION(!mJSContext, + "~ProxyAutoConfig leaking JS context that " + "should have been deleted on pac thread"); +} + +void +ProxyAutoConfig::Shutdown() +{ + MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown"); + + if (GetRunning() || mShutdown) + return; + + mShutdown = true; + delete mJSContext; + mJSContext = nullptr; +} + +bool +ProxyAutoConfig::SrcAddress(const NetAddr *remoteAddress, nsCString &localAddress) +{ + PRFileDesc *fd; + fd = PR_OpenUDPSocket(remoteAddress->raw.family); + if (!fd) + return false; + + PRNetAddr prRemoteAddress; + NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress); + if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PRNetAddr localName; + if (PR_GetSockName(fd, &localName) != PR_SUCCESS) { + PR_Close(fd); + return false; + } + + PR_Close(fd); + + char dottedDecimal[128]; + if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != PR_SUCCESS) + return false; + + localAddress.Assign(dottedDecimal); + + return true; +} + +// hostName is run through a dns lookup and then a udp socket is connected +// to the result. If that all works, the local IP address of the socket is +// returned to the javascript caller and |*aResult| is set to true. Otherwise +// |*aResult| is set to false. +bool +ProxyAutoConfig::MyIPAddressTryHost(const nsCString &hostName, + unsigned int timeout, + const JS::CallArgs &aArgs, + bool* aResult) +{ + *aResult = false; + + NetAddr remoteAddress; + nsAutoCString localDottedDecimal; + JSContext *cx = mJSContext->Context(); + + if (PACResolve(hostName, &remoteAddress, timeout) && + SrcAddress(&remoteAddress, localDottedDecimal)) { + JSString *dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + *aResult = true; + aArgs.rval().setString(dottedDecimalString); + } + return true; +} + +bool +ProxyAutoConfig::MyIPAddress(const JS::CallArgs &aArgs) +{ + nsAutoCString remoteDottedDecimal; + nsAutoCString localDottedDecimal; + JSContext *cx = mJSContext->Context(); + JS::RootedValue v(cx); + JS::Rooted<JSObject*> global(cx, mJSContext->Global()); + + bool useMultihomedDNS = + JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) && + !v.isUndefined() && ToBoolean(v); + + // first, lookup the local address of a socket connected + // to the host of uri being resolved by the pac file. This is + // v6 safe.. but is the last step like that + bool rvalAssigned = false; + if (useMultihomedDNS) { + if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + } else { + // we can still do the fancy multi homing thing if the host is a literal + PRNetAddr tempAddr; + memset(&tempAddr, 0, sizeof(PRNetAddr)); + if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) && + (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || + rvalAssigned)) { + return rvalAssigned; + } + } + + // next, look for a route to a public internet address that doesn't need DNS. + // This is the google anycast dns address, but it doesn't matter if it + // remains operable (as we don't contact it) as long as the address stays + // in commonly routed IP address space. + remoteDottedDecimal.AssignLiteral("8.8.8.8"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // finally, use the old algorithm based on the local hostname + nsAutoCString hostName; + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + // without multihomedDNS use such a short timeout that we are basically + // just looking at the cache for raw dotted decimals + uint32_t timeout = useMultihomedDNS ? kTimeout : 1; + if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) && + PACResolveToString(hostName, localDottedDecimal, timeout)) { + JSString *dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; + } + + // next try a couple RFC 1918 variants.. maybe there is a + // local route + remoteDottedDecimal.AssignLiteral("192.168.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // more RFC 1918 + remoteDottedDecimal.AssignLiteral("10.0.0.1"); + if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || + rvalAssigned) { + return rvalAssigned; + } + + // who knows? let's fallback to localhost + localDottedDecimal.AssignLiteral("127.0.0.1"); + JSString *dottedDecimalString = + JS_NewStringCopyZ(cx, localDottedDecimal.get()); + if (!dottedDecimalString) { + return false; + } + + aArgs.rval().setString(dottedDecimalString); + return true; +} + +} // namespace net +} // namespace mozilla |