Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 <algorithm>
8 :
9 : #include "mozilla/Attributes.h"
10 : #include "mozilla/Likely.h"
11 : #include "mozilla/Maybe.h"
12 : #include "mozilla/dom/FunctionBinding.h"
13 : #include "nsAXPCNativeCallContext.h"
14 : #include "nsCOMPtr.h"
15 : #include "nsContentUtils.h"
16 : #include "nsError.h"
17 : #include "nsGlobalWindow.h"
18 : #include "nsIContentSecurityPolicy.h"
19 : #include "nsIDocument.h"
20 : #include "nsIScriptTimeoutHandler.h"
21 : #include "nsIXPConnect.h"
22 : #include "nsJSUtils.h"
23 : #include "WorkerPrivate.h"
24 :
25 : static const char kSetIntervalStr[] = "setInterval";
26 : static const char kSetTimeoutStr[] = "setTimeout";
27 :
28 : using namespace mozilla;
29 : using namespace mozilla::dom;
30 : using namespace mozilla::dom::workers;
31 :
32 : // Our JS nsIScriptTimeoutHandler implementation.
33 : class nsJSScriptTimeoutHandler final : public nsIScriptTimeoutHandler
34 : {
35 : public:
36 : // nsISupports
37 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
38 169 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler)
39 :
40 : nsJSScriptTimeoutHandler();
41 : // This will call SwapElements on aArguments with an empty array.
42 : nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow,
43 : Function& aFunction,
44 : nsTArray<JS::Heap<JS::Value>>&& aArguments,
45 : ErrorResult& aError);
46 : nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow,
47 : const nsAString& aExpression, bool* aAllowEval,
48 : ErrorResult& aError);
49 : nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
50 : Function& aFunction,
51 : nsTArray<JS::Heap<JS::Value>>&& aArguments);
52 : nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
53 : const nsAString& aExpression);
54 :
55 : virtual const nsAString& GetHandlerText() override;
56 :
57 7 : virtual Function* GetCallback() override
58 : {
59 7 : return mFunction;
60 : }
61 :
62 7 : virtual const nsTArray<JS::Value>& GetArgs() override
63 : {
64 7 : return mArgs;
65 : }
66 :
67 0 : virtual nsresult Call() override
68 : {
69 0 : return NS_OK;
70 : }
71 :
72 11 : virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
73 : uint32_t* aColumn) override
74 : {
75 11 : *aFileName = mFileName.get();
76 11 : *aLineNo = mLineNo;
77 11 : *aColumn = mColumn;
78 11 : }
79 :
80 0 : virtual void MarkForCC() override
81 : {
82 0 : if (mFunction) {
83 0 : mFunction->MarkForCC();
84 : }
85 0 : }
86 :
87 : void ReleaseJSObjects();
88 :
89 : private:
90 : ~nsJSScriptTimeoutHandler();
91 :
92 : void Init(JSContext* aCx,
93 : nsTArray<JS::Heap<JS::Value>>&& aArguments);
94 : void Init(JSContext* aCx);
95 :
96 : // filename, line number and JS language version string of the
97 : // caller of setTimeout()
98 : nsCString mFileName;
99 : uint32_t mLineNo;
100 : uint32_t mColumn;
101 : nsTArray<JS::Heap<JS::Value>> mArgs;
102 :
103 : // The expression to evaluate or function to call. If mFunction is non-null
104 : // it should be used, else use mExpr.
105 : nsString mExpr;
106 : RefPtr<Function> mFunction;
107 : };
108 :
109 :
110 : // nsJSScriptTimeoutHandler
111 : // QueryInterface implementation for nsJSScriptTimeoutHandler
112 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler)
113 :
114 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler)
115 0 : tmp->ReleaseJSObjects();
116 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
117 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler)
118 0 : if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
119 0 : nsAutoCString name("nsJSScriptTimeoutHandler");
120 0 : if (tmp->mFunction) {
121 0 : JSObject* obj = tmp->mFunction->CallablePreserveColor();
122 0 : JSFunction* fun = JS_GetObjectFunction(js::UncheckedUnwrap(obj));
123 0 : if (fun && JS_GetFunctionId(fun)) {
124 0 : JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun));
125 0 : size_t size = 1 + JS_PutEscapedFlatString(nullptr, 0, funId, 0);
126 0 : char *funIdName = new char[size];
127 0 : if (funIdName) {
128 0 : JS_PutEscapedFlatString(funIdName, size, funId, 0);
129 0 : name.AppendLiteral(" [");
130 0 : name.Append(funIdName);
131 0 : delete[] funIdName;
132 0 : name.Append(']');
133 : }
134 : }
135 : } else {
136 0 : name.AppendLiteral(" [");
137 0 : name.Append(tmp->mFileName);
138 0 : name.Append(':');
139 0 : name.AppendInt(tmp->mLineNo);
140 0 : name.Append(':');
141 0 : name.AppendInt(tmp->mColumn);
142 0 : name.Append(']');
143 : }
144 0 : cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get());
145 : }
146 : else {
147 0 : NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSScriptTimeoutHandler,
148 : tmp->mRefCnt.get())
149 : }
150 :
151 0 : if (tmp->mFunction) {
152 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
153 : }
154 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
155 :
156 7 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
157 8 : for (uint32_t i = 0; i < tmp->mArgs.Length(); ++i) {
158 1 : NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgs[i])
159 : }
160 7 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
161 :
162 76 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSScriptTimeoutHandler)
163 29 : NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler)
164 11 : NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
165 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
166 0 : NS_INTERFACE_MAP_END
167 :
168 51 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
169 46 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
170 :
171 : static bool
172 0 : CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError)
173 : {
174 : // if CSP is enabled, and setTimeout/setInterval was called with a string,
175 : // disable the registration and log an error
176 0 : nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
177 0 : if (!doc) {
178 : // if there's no document, we don't have to do anything.
179 0 : return true;
180 : }
181 :
182 0 : nsCOMPtr<nsIContentSecurityPolicy> csp;
183 0 : aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
184 0 : if (aError.Failed()) {
185 0 : return false;
186 : }
187 :
188 0 : if (!csp) {
189 0 : return true;
190 : }
191 :
192 0 : bool allowsEval = true;
193 0 : bool reportViolation = false;
194 0 : aError = csp->GetAllowsEval(&reportViolation, &allowsEval);
195 0 : if (aError.Failed()) {
196 0 : return false;
197 : }
198 :
199 0 : if (reportViolation) {
200 : // TODO : need actual script sample in violation report.
201 0 : NS_NAMED_LITERAL_STRING(scriptSample,
202 : "call to eval() or related function blocked by CSP");
203 :
204 : // Get the calling location.
205 0 : uint32_t lineNum = 0;
206 0 : nsAutoString fileNameString;
207 0 : if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum)) {
208 0 : fileNameString.AssignLiteral("unknown");
209 : }
210 :
211 0 : csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
212 : fileNameString, scriptSample, lineNum,
213 0 : EmptyString(), EmptyString());
214 : }
215 :
216 0 : return allowsEval;
217 : }
218 :
219 0 : nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler()
220 : : mLineNo(0)
221 0 : , mColumn(0)
222 : {
223 0 : }
224 :
225 11 : nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
226 : nsGlobalWindow *aWindow,
227 : Function& aFunction,
228 : nsTArray<JS::Heap<JS::Value>>&& aArguments,
229 11 : ErrorResult& aError)
230 : : mLineNo(0)
231 : , mColumn(0)
232 11 : , mFunction(&aFunction)
233 : {
234 11 : if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
235 : // This window was already closed, or never properly initialized,
236 : // don't let a timer be scheduled on such a window.
237 0 : aError.Throw(NS_ERROR_NOT_INITIALIZED);
238 0 : return;
239 : }
240 :
241 11 : Init(aCx, Move(aArguments));
242 : }
243 :
244 0 : nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
245 : nsGlobalWindow *aWindow,
246 : const nsAString& aExpression,
247 : bool* aAllowEval,
248 0 : ErrorResult& aError)
249 : : mLineNo(0)
250 : , mColumn(0)
251 0 : , mExpr(aExpression)
252 : {
253 0 : if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
254 : // This window was already closed, or never properly initialized,
255 : // don't let a timer be scheduled on such a window.
256 0 : aError.Throw(NS_ERROR_NOT_INITIALIZED);
257 0 : return;
258 : }
259 :
260 0 : *aAllowEval = CheckCSPForEval(aCx, aWindow, aError);
261 0 : if (aError.Failed() || !*aAllowEval) {
262 0 : return;
263 : }
264 :
265 0 : Init(aCx);
266 : }
267 :
268 0 : nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
269 : WorkerPrivate* aWorkerPrivate,
270 : Function& aFunction,
271 0 : nsTArray<JS::Heap<JS::Value>>&& aArguments)
272 : : mLineNo(0)
273 : , mColumn(0)
274 0 : , mFunction(&aFunction)
275 : {
276 0 : MOZ_ASSERT(aWorkerPrivate);
277 0 : aWorkerPrivate->AssertIsOnWorkerThread();
278 :
279 0 : Init(aCx, Move(aArguments));
280 0 : }
281 :
282 0 : nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
283 : WorkerPrivate* aWorkerPrivate,
284 0 : const nsAString& aExpression)
285 : : mLineNo(0)
286 : , mColumn(0)
287 0 : , mExpr(aExpression)
288 : {
289 0 : MOZ_ASSERT(aWorkerPrivate);
290 0 : aWorkerPrivate->AssertIsOnWorkerThread();
291 :
292 0 : Init(aCx);
293 0 : }
294 :
295 0 : nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
296 : {
297 0 : ReleaseJSObjects();
298 0 : }
299 :
300 : void
301 11 : nsJSScriptTimeoutHandler::Init(JSContext* aCx,
302 : nsTArray<JS::Heap<JS::Value>>&& aArguments)
303 : {
304 11 : mozilla::HoldJSObjects(this);
305 11 : mArgs = Move(aArguments);
306 :
307 11 : Init(aCx);
308 11 : }
309 :
310 : void
311 11 : nsJSScriptTimeoutHandler::Init(JSContext* aCx)
312 : {
313 : // Get the calling location.
314 11 : nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
315 11 : }
316 :
317 : void
318 0 : nsJSScriptTimeoutHandler::ReleaseJSObjects()
319 : {
320 0 : if (mFunction) {
321 0 : mFunction = nullptr;
322 0 : mArgs.Clear();
323 0 : mozilla::DropJSObjects(this);
324 : }
325 0 : }
326 :
327 : const nsAString&
328 0 : nsJSScriptTimeoutHandler::GetHandlerText()
329 : {
330 0 : NS_ASSERTION(!mFunction, "No expression, so no handler text!");
331 0 : return mExpr;
332 : }
333 :
334 : already_AddRefed<nsIScriptTimeoutHandler>
335 11 : NS_CreateJSTimeoutHandler(JSContext *aCx, nsGlobalWindow *aWindow,
336 : Function& aFunction,
337 : const Sequence<JS::Value>& aArguments,
338 : ErrorResult& aError)
339 : {
340 22 : nsTArray<JS::Heap<JS::Value>> args;
341 11 : if (!args.AppendElements(aArguments, fallible)) {
342 0 : aError.Throw(NS_ERROR_OUT_OF_MEMORY);
343 0 : return nullptr;
344 : }
345 :
346 : RefPtr<nsJSScriptTimeoutHandler> handler =
347 33 : new nsJSScriptTimeoutHandler(aCx, aWindow, aFunction, Move(args), aError);
348 11 : return aError.Failed() ? nullptr : handler.forget();
349 : }
350 :
351 : already_AddRefed<nsIScriptTimeoutHandler>
352 0 : NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
353 : const nsAString& aExpression, ErrorResult& aError)
354 : {
355 0 : bool allowEval = false;
356 : RefPtr<nsJSScriptTimeoutHandler> handler =
357 0 : new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, aError);
358 0 : if (aError.Failed() || !allowEval) {
359 0 : return nullptr;
360 : }
361 :
362 0 : return handler.forget();
363 : }
364 :
365 : already_AddRefed<nsIScriptTimeoutHandler>
366 0 : NS_CreateJSTimeoutHandler(JSContext *aCx, WorkerPrivate* aWorkerPrivate,
367 : Function& aFunction,
368 : const Sequence<JS::Value>& aArguments,
369 : ErrorResult& aError)
370 : {
371 0 : nsTArray<JS::Heap<JS::Value>> args;
372 0 : if (!args.AppendElements(aArguments, fallible)) {
373 0 : aError.Throw(NS_ERROR_OUT_OF_MEMORY);
374 0 : return nullptr;
375 : }
376 :
377 : RefPtr<nsJSScriptTimeoutHandler> handler =
378 0 : new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aFunction, Move(args));
379 0 : return handler.forget();
380 : }
381 :
382 : already_AddRefed<nsIScriptTimeoutHandler>
383 0 : NS_CreateJSTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
384 : const nsAString& aExpression)
385 : {
386 : RefPtr<nsJSScriptTimeoutHandler> handler =
387 0 : new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aExpression);
388 0 : return handler.forget();
389 : }
|