dom/indexedDB/KeyPath.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 }

mercurial