dom/base/nsJSTimeoutHandler.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 sw=2 et tw=78: */
     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/. */
     7 #include "nsCOMPtr.h"
     8 #include "nsIDocument.h"
     9 #include "nsIScriptTimeoutHandler.h"
    10 #include "nsIXPConnect.h"
    11 #include "nsJSUtils.h"
    12 #include "nsContentUtils.h"
    13 #include "nsError.h"
    14 #include "nsGlobalWindow.h"
    15 #include "nsIContentSecurityPolicy.h"
    16 #include "mozilla/Attributes.h"
    17 #include "mozilla/Likely.h"
    18 #include <algorithm>
    19 #include "mozilla/dom/FunctionBinding.h"
    20 #include "nsAXPCNativeCallContext.h"
    22 static const char kSetIntervalStr[] = "setInterval";
    23 static const char kSetTimeoutStr[] = "setTimeout";
    25 using namespace mozilla;
    26 using namespace mozilla::dom;
    28 // Our JS nsIScriptTimeoutHandler implementation.
    29 class nsJSScriptTimeoutHandler MOZ_FINAL : public nsIScriptTimeoutHandler
    30 {
    31 public:
    32   // nsISupports
    33   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    34   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler)
    36   nsJSScriptTimeoutHandler();
    37   // This will call SwapElements on aArguments with an empty array.
    38   nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction,
    39                            FallibleTArray<JS::Heap<JS::Value> >& aArguments,
    40                            ErrorResult& aError);
    41   nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
    42                            const nsAString& aExpression, bool* aAllowEval,
    43                            ErrorResult& aError);
    44   ~nsJSScriptTimeoutHandler();
    46   virtual const char16_t *GetHandlerText();
    47   virtual Function* GetCallback()
    48   {
    49     return mFunction;
    50   }
    51   virtual void GetLocation(const char **aFileName, uint32_t *aLineNo)
    52   {
    53     *aFileName = mFileName.get();
    54     *aLineNo = mLineNo;
    55   }
    57   virtual const nsTArray<JS::Value>& GetArgs()
    58   {
    59     return mArgs;
    60   }
    62   nsresult Init(nsGlobalWindow *aWindow, bool *aIsInterval,
    63                 int32_t *aInterval, bool* aAllowEval);
    65   void ReleaseJSObjects();
    67 private:
    68   // filename, line number and JS language version string of the
    69   // caller of setTimeout()
    70   nsCString mFileName;
    71   uint32_t mLineNo;
    72   nsTArray<JS::Heap<JS::Value> > mArgs;
    74   // The expression to evaluate or function to call. If mFunction is non-null
    75   // it should be used, else use mExpr.
    76   nsString mExpr;
    77   nsRefPtr<Function> mFunction;
    78 };
    81 // nsJSScriptTimeoutHandler
    82 // QueryInterface implementation for nsJSScriptTimeoutHandler
    83 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler)
    85 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler)
    86   tmp->ReleaseJSObjects();
    87 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler)
    89   if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
    90     nsAutoCString name("nsJSScriptTimeoutHandler");
    91     if (tmp->mFunction) {
    92       JSFunction* fun =
    93         JS_GetObjectFunction(js::UncheckedUnwrap(tmp->mFunction->Callable()));
    94       if (fun && JS_GetFunctionId(fun)) {
    95         JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun));
    96         size_t size = 1 + JS_PutEscapedFlatString(nullptr, 0, funId, 0);
    97         char *funIdName = new char[size];
    98         if (funIdName) {
    99           JS_PutEscapedFlatString(funIdName, size, funId, 0);
   100           name.AppendLiteral(" [");
   101           name.Append(funIdName);
   102           delete[] funIdName;
   103           name.AppendLiteral("]");
   104         }
   105       }
   106     } else {
   107       name.AppendLiteral(" [");
   108       name.Append(tmp->mFileName);
   109       name.AppendLiteral(":");
   110       name.AppendInt(tmp->mLineNo);
   111       name.AppendLiteral("]");
   112     }
   113     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
   114   }
   115   else {
   116     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler,
   117                                       tmp->mRefCnt.get())
   118   }
   120   if (tmp->mFunction) {
   121     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
   122     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   123   }
   124 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   126 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
   127   for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) {
   128     NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mArgs[i])
   129   }
   130 NS_IMPL_CYCLE_COLLECTION_TRACE_END
   132 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler)
   133   NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler)
   134   NS_INTERFACE_MAP_ENTRY(nsISupports)
   135 NS_INTERFACE_MAP_END
   137 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
   138 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
   140 static bool
   141 CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError)
   142 {
   143   // if CSP is enabled, and setTimeout/setInterval was called with a string,
   144   // disable the registration and log an error
   145   nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
   146   if (!doc) {
   147     // if there's no document, we don't have to do anything.
   148     return true;
   149   }
   151   nsCOMPtr<nsIContentSecurityPolicy> csp;
   152   aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
   153   if (aError.Failed()) {
   154     return false;
   155   }
   157   if (!csp) {
   158     return true;
   159   }
   161   bool allowsEval = true;
   162   bool reportViolation = false;
   163   aError = csp->GetAllowsEval(&reportViolation, &allowsEval);
   164   if (aError.Failed()) {
   165     return false;
   166   }
   168   if (reportViolation) {
   169     // TODO : need actual script sample in violation report.
   170     NS_NAMED_LITERAL_STRING(scriptSample,
   171                             "call to eval() or related function blocked by CSP");
   173     // Get the calling location.
   174     uint32_t lineNum = 0;
   175     const char *fileName;
   176     nsAutoString fileNameString;
   177     if (nsJSUtils::GetCallingLocation(aCx, &fileName, &lineNum)) {
   178       AppendUTF8toUTF16(fileName, fileNameString);
   179     } else {
   180       fileNameString.AssignLiteral("unknown");
   181     }
   183     csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
   184                              fileNameString, scriptSample, lineNum,
   185                              EmptyString(), EmptyString());
   186   }
   188   return allowsEval;
   189 }
   191 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() :
   192   mLineNo(0)
   193 {
   194 }
   196 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(nsGlobalWindow *aWindow,
   197                                                    Function& aFunction,
   198                                                    FallibleTArray<JS::Heap<JS::Value> >& aArguments,
   199                                                    ErrorResult& aError) :
   200   mLineNo(0),
   201   mFunction(&aFunction)
   202 {
   203   if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
   204     // This window was already closed, or never properly initialized,
   205     // don't let a timer be scheduled on such a window.
   206     aError.Throw(NS_ERROR_NOT_INITIALIZED);
   207     return;
   208   }
   210   mozilla::HoldJSObjects(this);
   211   mArgs.SwapElements(aArguments);
   212 }
   214 nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
   215                                                    nsGlobalWindow *aWindow,
   216                                                    const nsAString& aExpression,
   217                                                    bool* aAllowEval,
   218                                                    ErrorResult& aError) :
   219   mLineNo(0),
   220   mExpr(aExpression)
   221 {
   222   if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
   223     // This window was already closed, or never properly initialized,
   224     // don't let a timer be scheduled on such a window.
   225     aError.Throw(NS_ERROR_NOT_INITIALIZED);
   226     return;
   227   }
   229   *aAllowEval = CheckCSPForEval(aCx, aWindow, aError);
   230   if (aError.Failed() || !*aAllowEval) {
   231     return;
   232   }
   234   // Get the calling location.
   235   const char *filename;
   236   if (nsJSUtils::GetCallingLocation(aCx, &filename, &mLineNo)) {
   237     mFileName.Assign(filename);
   238   }
   239 }
   241 nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
   242 {
   243   ReleaseJSObjects();
   244 }
   246 void
   247 nsJSScriptTimeoutHandler::ReleaseJSObjects()
   248 {
   249   if (mFunction) {
   250     mFunction = nullptr;
   251     mArgs.Clear();
   252     mozilla::DropJSObjects(this);
   253   }
   254 }
   256 nsresult
   257 nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval,
   258                                int32_t *aInterval, bool *aAllowEval)
   259 {
   260   if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
   261     // This window was already closed, or never properly initialized,
   262     // don't let a timer be scheduled on such a window.
   264     return NS_ERROR_NOT_INITIALIZED;
   265   }
   267   nsAXPCNativeCallContext *ncc = nullptr;
   268   nsresult rv = nsContentUtils::XPConnect()->
   269     GetCurrentNativeCallContext(&ncc);
   270   NS_ENSURE_SUCCESS(rv, rv);
   272   if (!ncc)
   273     return NS_ERROR_NOT_AVAILABLE;
   275   JSContext *cx = nullptr;
   277   rv = ncc->GetJSContext(&cx);
   278   NS_ENSURE_SUCCESS(rv, rv);
   280   uint32_t argc;
   281   JS::Value *argv = nullptr;
   283   ncc->GetArgc(&argc);
   284   ncc->GetArgvPtr(&argv);
   286   JS::Rooted<JSFlatString*> expr(cx);
   287   JS::Rooted<JSObject*> funobj(cx);
   289   if (argc < 1) {
   290     ::JS_ReportError(cx, "Function %s requires at least 2 parameter",
   291                      *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
   292     return NS_ERROR_DOM_TYPE_ERR;
   293   }
   295   int32_t interval = 0;
   296   if (argc > 1) {
   297     JS::Rooted<JS::Value> arg(cx, argv[1]);
   299     if (!JS::ToInt32(cx, arg, &interval)) {
   300       ::JS_ReportError(cx,
   301                        "Second argument to %s must be a millisecond interval",
   302                        aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
   303       return NS_ERROR_DOM_TYPE_ERR;
   304     }
   305   }
   307   if (argc == 1) {
   308     // If no interval was specified, treat this like a timeout, to avoid
   309     // setting an interval of 0 milliseconds.
   310     *aIsInterval = false;
   311   }
   313   JS::Rooted<JS::Value> arg(cx, argv[0]);
   314   switch (::JS_TypeOfValue(cx, arg)) {
   315   case JSTYPE_FUNCTION:
   316     funobj = &arg.toObject();
   317     break;
   319   case JSTYPE_STRING:
   320   case JSTYPE_OBJECT:
   321     {
   322       JSString *str = JS::ToString(cx, arg);
   323       if (!str)
   324         return NS_ERROR_OUT_OF_MEMORY;
   326       expr = ::JS_FlattenString(cx, str);
   327       if (!expr)
   328           return NS_ERROR_OUT_OF_MEMORY;
   330       argv[0] = JS::StringValue(str);
   331     }
   332     break;
   334   default:
   335     ::JS_ReportError(cx, "useless %s call (missing quotes around argument?)",
   336                      *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
   338     // Return an error that nsGlobalWindow can recognize and turn into NS_OK.
   339     return NS_ERROR_DOM_TYPE_ERR;
   340   }
   342   if (expr) {
   343     // if CSP is enabled, and setTimeout/setInterval was called with a string,
   344     // disable the registration and log an error
   345     ErrorResult error;
   346     *aAllowEval = CheckCSPForEval(cx, aWindow, error);
   347     if (error.Failed() || !*aAllowEval) {
   348       return error.ErrorCode();
   349     }
   351     mExpr.Append(JS_GetFlatStringChars(expr),
   352                  JS_GetStringLength(JS_FORGET_STRING_FLATNESS(expr)));
   354     // Get the calling location.
   355     const char *filename;
   356     if (nsJSUtils::GetCallingLocation(cx, &filename, &mLineNo)) {
   357       mFileName.Assign(filename);
   358     }
   359   } else if (funobj) {
   360     *aAllowEval = true;
   362     mozilla::HoldJSObjects(this);
   364     mFunction = new Function(funobj, GetIncumbentGlobal());
   366     // Create our arg array.  argc is the number of arguments passed
   367     // to setTimeout or setInterval; the first two are our callback
   368     // and the delay, so only arguments after that need to go in our
   369     // array.
   370     // std::max(argc - 2, 0) wouldn't work right because argc is unsigned.
   371     uint32_t argCount = std::max(argc, 2u) - 2;
   373     FallibleTArray<JS::Heap<JS::Value> > args;
   374     if (!args.SetCapacity(argCount)) {
   375       // No need to drop here, since we already have a non-null mFunction
   376       return NS_ERROR_OUT_OF_MEMORY;
   377     }
   378     for (uint32_t idx = 0; idx < argCount; ++idx) {
   379       *args.AppendElement() = argv[idx + 2];
   380     }
   381     args.SwapElements(mArgs);
   382   } else {
   383     NS_WARNING("No func and no expr - why are we here?");
   384   }
   385   *aInterval = interval;
   386   return NS_OK;
   387 }
   389 const char16_t *
   390 nsJSScriptTimeoutHandler::GetHandlerText()
   391 {
   392   NS_ASSERTION(!mFunction, "No expression, so no handler text!");
   393   return mExpr.get();
   394 }
   396 nsresult NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow,
   397                                    bool *aIsInterval,
   398                                    int32_t *aInterval,
   399                                    nsIScriptTimeoutHandler **aRet)
   400 {
   401   *aRet = nullptr;
   402   nsRefPtr<nsJSScriptTimeoutHandler> handler = new nsJSScriptTimeoutHandler();
   403   bool allowEval;
   404   nsresult rv = handler->Init(aWindow, aIsInterval, aInterval, &allowEval);
   405   if (NS_FAILED(rv) || !allowEval) {
   406     return rv;
   407   }
   409   handler.forget(aRet);
   411   return NS_OK;
   412 }
   414 already_AddRefed<nsIScriptTimeoutHandler>
   415 NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction,
   416                           const Sequence<JS::Value>& aArguments,
   417                           ErrorResult& aError)
   418 {
   419   FallibleTArray<JS::Heap<JS::Value> > args;
   420   if (!args.AppendElements(aArguments)) {
   421     aError.Throw(NS_ERROR_OUT_OF_MEMORY);
   422     return 0;
   423   }
   425   nsRefPtr<nsJSScriptTimeoutHandler> handler =
   426     new nsJSScriptTimeoutHandler(aWindow, aFunction, args, aError);
   427   return aError.Failed() ? nullptr : handler.forget();
   428 }
   430 already_AddRefed<nsIScriptTimeoutHandler>
   431 NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
   432                           const nsAString& aExpression, ErrorResult& aError)
   433 {
   434   ErrorResult rv;
   435   bool allowEval = false;
   436   nsRefPtr<nsJSScriptTimeoutHandler> handler =
   437     new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, rv);
   438   if (rv.Failed() || !allowEval) {
   439     return nullptr;
   440   }
   442   return handler.forget();
   443 }

mercurial