Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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/. */
7 #include "KeyPath.h"
8 #include "IDBObjectStore.h"
9 #include "Key.h"
10 #include "ReportInternalError.h"
12 #include "nsCharSeparatedTokenizer.h"
13 #include "nsJSUtils.h"
14 #include "xpcpublic.h"
16 #include "mozilla/dom/BindingDeclarations.h"
18 USING_INDEXEDDB_NAMESPACE
20 namespace {
22 inline
23 bool
24 IgnoreWhitespace(char16_t c)
25 {
26 return false;
27 }
29 typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
31 bool
32 IsValidKeyPathString(JSContext* aCx, const nsAString& aKeyPath)
33 {
34 NS_ASSERTION(!aKeyPath.IsVoid(), "What?");
36 KeyPathTokenizer tokenizer(aKeyPath, '.');
38 while (tokenizer.hasMoreTokens()) {
39 nsString token(tokenizer.nextToken());
41 if (!token.Length()) {
42 return false;
43 }
45 JS::Rooted<JS::Value> stringVal(aCx);
46 if (!xpc::StringToJsval(aCx, token, &stringVal)) {
47 return false;
48 }
50 NS_ASSERTION(stringVal.toString(), "This should never happen");
51 JS::Rooted<JSString*> str(aCx, stringVal.toString());
53 bool isIdentifier = false;
54 if (!JS_IsIdentifier(aCx, str, &isIdentifier) || !isIdentifier) {
55 return false;
56 }
57 }
59 // If the very last character was a '.', the tokenizer won't give us an empty
60 // token, but the keyPath is still invalid.
61 if (!aKeyPath.IsEmpty() &&
62 aKeyPath.CharAt(aKeyPath.Length() - 1) == '.') {
63 return false;
64 }
66 return true;
67 }
69 enum KeyExtractionOptions {
70 DoNotCreateProperties,
71 CreateProperties
72 };
74 nsresult
75 GetJSValFromKeyPathString(JSContext* aCx,
76 const JS::Value& aValue,
77 const nsAString& aKeyPathString,
78 JS::Value* aKeyJSVal,
79 KeyExtractionOptions aOptions,
80 KeyPath::ExtractOrCreateKeyCallback aCallback,
81 void* aClosure)
82 {
83 NS_ASSERTION(aCx, "Null pointer!");
84 NS_ASSERTION(IsValidKeyPathString(aCx, aKeyPathString),
85 "This will explode!");
86 NS_ASSERTION(!(aCallback || aClosure) || aOptions == CreateProperties,
87 "This is not allowed!");
88 NS_ASSERTION(aOptions != CreateProperties || aCallback,
89 "If properties are created, there must be a callback!");
91 nsresult rv = NS_OK;
92 *aKeyJSVal = aValue;
94 KeyPathTokenizer tokenizer(aKeyPathString, '.');
96 nsString targetObjectPropName;
97 JS::Rooted<JSObject*> targetObject(aCx, nullptr);
98 JS::Rooted<JSObject*> obj(aCx,
99 JSVAL_IS_PRIMITIVE(aValue) ? nullptr : JSVAL_TO_OBJECT(aValue));
101 while (tokenizer.hasMoreTokens()) {
102 const nsDependentSubstring& token = tokenizer.nextToken();
104 NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath");
106 const jschar* keyPathChars = token.BeginReading();
107 const size_t keyPathLen = token.Length();
109 bool hasProp;
110 if (!targetObject) {
111 // We're still walking the chain of existing objects
112 if (!obj) {
113 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
114 }
116 bool ok = JS_HasUCProperty(aCx, obj, keyPathChars, keyPathLen,
117 &hasProp);
118 IDB_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
120 if (hasProp) {
121 // Get if the property exists...
122 JS::Rooted<JS::Value> intermediate(aCx);
123 bool ok = JS_GetUCProperty(aCx, obj, keyPathChars, keyPathLen, &intermediate);
124 IDB_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
126 // Treat explicitly undefined as an error.
127 if (intermediate == JSVAL_VOID) {
128 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
129 }
130 if (tokenizer.hasMoreTokens()) {
131 // ...and walk to it if there are more steps...
132 if (JSVAL_IS_PRIMITIVE(intermediate)) {
133 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
134 }
135 obj = JSVAL_TO_OBJECT(intermediate);
136 }
137 else {
138 // ...otherwise use it as key
139 *aKeyJSVal = intermediate;
140 }
141 }
142 else {
143 // If the property doesn't exist, fall into below path of starting
144 // to define properties, if allowed.
145 if (aOptions == DoNotCreateProperties) {
146 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
147 }
149 targetObject = obj;
150 targetObjectPropName = token;
151 }
152 }
154 if (targetObject) {
155 // We have started inserting new objects or are about to just insert
156 // the first one.
158 *aKeyJSVal = JSVAL_VOID;
160 if (tokenizer.hasMoreTokens()) {
161 // If we're not at the end, we need to add a dummy object to the
162 // chain.
163 JS::Rooted<JSObject*> dummy(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(),
164 JS::NullPtr()));
165 if (!dummy) {
166 IDB_REPORT_INTERNAL_ERR();
167 rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
168 break;
169 }
171 if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
172 token.Length(),
173 OBJECT_TO_JSVAL(dummy), nullptr, nullptr,
174 JSPROP_ENUMERATE)) {
175 IDB_REPORT_INTERNAL_ERR();
176 rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
177 break;
178 }
180 obj = dummy;
181 }
182 else {
183 JS::Rooted<JSObject*> dummy(aCx, JS_NewObject(aCx, &IDBObjectStore::sDummyPropJSClass,
184 JS::NullPtr(), JS::NullPtr()));
185 if (!dummy) {
186 IDB_REPORT_INTERNAL_ERR();
187 rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
188 break;
189 }
191 if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
192 token.Length(), OBJECT_TO_JSVAL(dummy),
193 nullptr, nullptr, JSPROP_ENUMERATE)) {
194 IDB_REPORT_INTERNAL_ERR();
195 rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
196 break;
197 }
199 obj = dummy;
200 }
201 }
202 }
204 // We guard on rv being a success because we need to run the property
205 // deletion code below even if we should not be running the callback.
206 if (NS_SUCCEEDED(rv) && aCallback) {
207 rv = (*aCallback)(aCx, aClosure);
208 }
210 if (targetObject) {
211 // If this fails, we lose, and the web page sees a magical property
212 // appear on the object :-(
213 bool succeeded;
214 if (!JS_DeleteUCProperty2(aCx, targetObject,
215 targetObjectPropName.get(),
216 targetObjectPropName.Length(),
217 &succeeded)) {
218 IDB_REPORT_INTERNAL_ERR();
219 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
220 }
221 IDB_ENSURE_TRUE(succeeded, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
222 }
224 NS_ENSURE_SUCCESS(rv, rv);
225 return rv;
226 }
228 } // anonymous namespace
230 // static
231 nsresult
232 KeyPath::Parse(JSContext* aCx, const nsAString& aString, KeyPath* aKeyPath)
233 {
234 KeyPath keyPath(0);
235 keyPath.SetType(STRING);
237 if (!keyPath.AppendStringWithValidation(aCx, aString)) {
238 return NS_ERROR_FAILURE;
239 }
241 *aKeyPath = keyPath;
242 return NS_OK;
243 }
245 //static
246 nsresult
247 KeyPath::Parse(JSContext* aCx, const mozilla::dom::Sequence<nsString>& aStrings,
248 KeyPath* aKeyPath)
249 {
250 KeyPath keyPath(0);
251 keyPath.SetType(ARRAY);
253 for (uint32_t i = 0; i < aStrings.Length(); ++i) {
254 if (!keyPath.AppendStringWithValidation(aCx, aStrings[i])) {
255 return NS_ERROR_FAILURE;
256 }
257 }
259 *aKeyPath = keyPath;
260 return NS_OK;
261 }
263 // static
264 nsresult
265 KeyPath::Parse(JSContext* aCx, const JS::Value& aValue_, KeyPath* aKeyPath)
266 {
267 JS::Rooted<JS::Value> aValue(aCx, aValue_);
268 KeyPath keyPath(0);
270 aKeyPath->SetType(NONEXISTENT);
272 // See if this is a JS array.
273 if (JS_IsArrayObject(aCx, aValue)) {
275 JS::Rooted<JSObject*> obj(aCx, JSVAL_TO_OBJECT(aValue));
277 uint32_t length;
278 if (!JS_GetArrayLength(aCx, obj, &length)) {
279 return NS_ERROR_FAILURE;
280 }
282 if (!length) {
283 return NS_ERROR_FAILURE;
284 }
286 keyPath.SetType(ARRAY);
288 for (uint32_t index = 0; index < length; index++) {
289 JS::Rooted<JS::Value> val(aCx);
290 JSString* jsstr;
291 nsDependentJSString str;
292 if (!JS_GetElement(aCx, obj, index, &val) ||
293 !(jsstr = JS::ToString(aCx, val)) ||
294 !str.init(aCx, jsstr)) {
295 return NS_ERROR_FAILURE;
296 }
298 if (!keyPath.AppendStringWithValidation(aCx, str)) {
299 return NS_ERROR_FAILURE;
300 }
301 }
302 }
303 // Otherwise convert it to a string.
304 else if (!JSVAL_IS_NULL(aValue) && !JSVAL_IS_VOID(aValue)) {
305 JSString* jsstr;
306 nsDependentJSString str;
307 if (!(jsstr = JS::ToString(aCx, aValue)) ||
308 !str.init(aCx, jsstr)) {
309 return NS_ERROR_FAILURE;
310 }
312 keyPath.SetType(STRING);
314 if (!keyPath.AppendStringWithValidation(aCx, str)) {
315 return NS_ERROR_FAILURE;
316 }
317 }
319 *aKeyPath = keyPath;
320 return NS_OK;
321 }
323 void
324 KeyPath::SetType(KeyPathType aType)
325 {
326 mType = aType;
327 mStrings.Clear();
328 }
330 bool
331 KeyPath::AppendStringWithValidation(JSContext* aCx, const nsAString& aString)
332 {
333 if (!IsValidKeyPathString(aCx, aString)) {
334 return false;
335 }
337 if (IsString()) {
338 NS_ASSERTION(mStrings.Length() == 0, "Too many strings!");
339 mStrings.AppendElement(aString);
340 return true;
341 }
343 if (IsArray()) {
344 mStrings.AppendElement(aString);
345 return true;
346 }
348 NS_NOTREACHED("What?!");
349 return false;
350 }
352 nsresult
353 KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const
354 {
355 uint32_t len = mStrings.Length();
356 JS::Rooted<JS::Value> value(aCx);
358 aKey.Unset();
360 for (uint32_t i = 0; i < len; ++i) {
361 nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
362 value.address(),
363 DoNotCreateProperties, nullptr,
364 nullptr);
365 if (NS_FAILED(rv)) {
366 return rv;
367 }
369 if (NS_FAILED(aKey.AppendItem(aCx, IsArray() && i == 0, value))) {
370 NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
371 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
372 }
373 }
375 aKey.FinishArray();
377 return NS_OK;
378 }
380 nsresult
381 KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
382 JS::Value* aOutVal) const
383 {
384 NS_ASSERTION(IsValid(), "This doesn't make sense!");
386 if (IsString()) {
387 return GetJSValFromKeyPathString(aCx, aValue, mStrings[0], aOutVal,
388 DoNotCreateProperties, nullptr, nullptr);
389 }
391 const uint32_t len = mStrings.Length();
392 JS::Rooted<JSObject*> arrayObj(aCx, JS_NewArrayObject(aCx, len));
393 if (!arrayObj) {
394 return NS_ERROR_OUT_OF_MEMORY;
395 }
397 JS::Rooted<JS::Value> value(aCx);
398 for (uint32_t i = 0; i < len; ++i) {
399 nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
400 value.address(),
401 DoNotCreateProperties, nullptr,
402 nullptr);
403 if (NS_FAILED(rv)) {
404 return rv;
405 }
407 if (!JS_SetElement(aCx, arrayObj, i, value)) {
408 IDB_REPORT_INTERNAL_ERR();
409 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
410 }
411 }
413 *aOutVal = OBJECT_TO_JSVAL(arrayObj);
414 return NS_OK;
415 }
417 nsresult
418 KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue,
419 Key& aKey, ExtractOrCreateKeyCallback aCallback,
420 void* aClosure) const
421 {
422 NS_ASSERTION(IsString(), "This doesn't make sense!");
424 JS::Rooted<JS::Value> value(aCx);
426 aKey.Unset();
428 nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[0],
429 value.address(),
430 CreateProperties, aCallback,
431 aClosure);
432 if (NS_FAILED(rv)) {
433 return rv;
434 }
436 if (NS_FAILED(aKey.AppendItem(aCx, false, value))) {
437 NS_ASSERTION(aKey.IsUnset(), "Should be unset");
438 return JSVAL_IS_VOID(value) ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
439 }
441 aKey.FinishArray();
443 return NS_OK;
444 }
446 void
447 KeyPath::SerializeToString(nsAString& aString) const
448 {
449 NS_ASSERTION(IsValid(), "Check to see if I'm valid first!");
451 if (IsString()) {
452 aString = mStrings[0];
453 return;
454 }
456 if (IsArray()) {
457 // We use a comma in the beginning to indicate that it's an array of
458 // key paths. This is to be able to tell a string-keypath from an
459 // array-keypath which contains only one item.
460 // It also makes serializing easier :-)
461 uint32_t len = mStrings.Length();
462 for (uint32_t i = 0; i < len; ++i) {
463 aString.Append(NS_LITERAL_STRING(",") + mStrings[i]);
464 }
466 return;
467 }
469 NS_NOTREACHED("What?");
470 }
472 // static
473 KeyPath
474 KeyPath::DeserializeFromString(const nsAString& aString)
475 {
476 KeyPath keyPath(0);
478 if (!aString.IsEmpty() && aString.First() == ',') {
479 keyPath.SetType(ARRAY);
481 // We use a comma in the beginning to indicate that it's an array of
482 // key paths. This is to be able to tell a string-keypath from an
483 // array-keypath which contains only one item.
484 nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> tokenizer(aString, ',');
485 tokenizer.nextToken();
486 while (tokenizer.hasMoreTokens()) {
487 keyPath.mStrings.AppendElement(tokenizer.nextToken());
488 }
490 return keyPath;
491 }
493 keyPath.SetType(STRING);
494 keyPath.mStrings.AppendElement(aString);
496 return keyPath;
497 }
499 nsresult
500 KeyPath::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aValue) const
501 {
502 if (IsArray()) {
503 uint32_t len = mStrings.Length();
504 JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, len));
505 if (!array) {
506 IDB_WARNING("Failed to make array!");
507 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
508 }
510 for (uint32_t i = 0; i < len; ++i) {
511 JS::Rooted<JS::Value> val(aCx);
512 nsString tmp(mStrings[i]);
513 if (!xpc::StringToJsval(aCx, tmp, &val)) {
514 IDB_REPORT_INTERNAL_ERR();
515 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
516 }
518 if (!JS_SetElement(aCx, array, i, val)) {
519 IDB_REPORT_INTERNAL_ERR();
520 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
521 }
522 }
524 aValue.setObject(*array);
525 return NS_OK;
526 }
528 if (IsString()) {
529 nsString tmp(mStrings[0]);
530 if (!xpc::StringToJsval(aCx, tmp, aValue)) {
531 IDB_REPORT_INTERNAL_ERR();
532 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
533 }
534 return NS_OK;
535 }
537 aValue.setNull();
538 return NS_OK;
539 }
541 nsresult
542 KeyPath::ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aValue) const
543 {
544 JS::Rooted<JS::Value> value(aCx);
545 nsresult rv = ToJSVal(aCx, &value);
546 if (NS_SUCCEEDED(rv)) {
547 aValue = value;
548 }
549 return rv;
550 }
552 bool
553 KeyPath::IsAllowedForObjectStore(bool aAutoIncrement) const
554 {
555 // Any keypath that passed validation is allowed for non-autoIncrement
556 // objectStores.
557 if (!aAutoIncrement) {
558 return true;
559 }
561 // Array keypaths are not allowed for autoIncrement objectStores.
562 if (IsArray()) {
563 return false;
564 }
566 // Neither are empty strings.
567 if (IsEmpty()) {
568 return false;
569 }
571 // Everything else is ok.
572 return true;
573 }