LCOV - code coverage report
Current view: top level - dom/indexedDB - KeyPath.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 247 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 16 0.0 %
Legend: Lines: hit not hit

          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 "KeyPath.h"
       8             : #include "IDBObjectStore.h"
       9             : #include "Key.h"
      10             : #include "ReportInternalError.h"
      11             : 
      12             : #include "nsCharSeparatedTokenizer.h"
      13             : #include "nsJSUtils.h"
      14             : #include "nsPrintfCString.h"
      15             : #include "xpcpublic.h"
      16             : 
      17             : #include "mozilla/dom/BindingDeclarations.h"
      18             : #include "mozilla/dom/IDBObjectStoreBinding.h"
      19             : 
      20             : namespace mozilla {
      21             : namespace dom {
      22             : namespace indexedDB {
      23             : 
      24             : namespace {
      25             : 
      26             : inline
      27             : bool
      28           0 : IgnoreWhitespace(char16_t c)
      29             : {
      30           0 :   return false;
      31             : }
      32             : 
      33             : typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
      34             : 
      35             : bool
      36           0 : IsValidKeyPathString(const nsAString& aKeyPath)
      37             : {
      38           0 :   NS_ASSERTION(!aKeyPath.IsVoid(), "What?");
      39             : 
      40           0 :   KeyPathTokenizer tokenizer(aKeyPath, '.');
      41             : 
      42           0 :   while (tokenizer.hasMoreTokens()) {
      43           0 :     nsString token(tokenizer.nextToken());
      44             : 
      45           0 :     if (!token.Length()) {
      46           0 :       return false;
      47             :     }
      48             : 
      49           0 :     if (!JS_IsIdentifier(token.get(), token.Length())) {
      50           0 :       return false;
      51             :     }
      52             :   }
      53             : 
      54             :   // If the very last character was a '.', the tokenizer won't give us an empty
      55             :   // token, but the keyPath is still invalid.
      56           0 :   if (!aKeyPath.IsEmpty() &&
      57           0 :       aKeyPath.CharAt(aKeyPath.Length() - 1) == '.') {
      58           0 :     return false;
      59             :   }
      60             : 
      61           0 :   return true;
      62             : }
      63             : 
      64             : enum KeyExtractionOptions {
      65             :   DoNotCreateProperties,
      66             :   CreateProperties
      67             : };
      68             : 
      69             : nsresult
      70           0 : GetJSValFromKeyPathString(JSContext* aCx,
      71             :                           const JS::Value& aValue,
      72             :                           const nsAString& aKeyPathString,
      73             :                           JS::Value* aKeyJSVal,
      74             :                           KeyExtractionOptions aOptions,
      75             :                           KeyPath::ExtractOrCreateKeyCallback aCallback,
      76             :                           void* aClosure)
      77             : {
      78           0 :   NS_ASSERTION(aCx, "Null pointer!");
      79           0 :   NS_ASSERTION(IsValidKeyPathString(aKeyPathString),
      80             :                "This will explode!");
      81           0 :   NS_ASSERTION(!(aCallback || aClosure) || aOptions == CreateProperties,
      82             :                "This is not allowed!");
      83           0 :   NS_ASSERTION(aOptions != CreateProperties || aCallback,
      84             :                "If properties are created, there must be a callback!");
      85             : 
      86           0 :   nsresult rv = NS_OK;
      87           0 :   *aKeyJSVal = aValue;
      88             : 
      89           0 :   KeyPathTokenizer tokenizer(aKeyPathString, '.');
      90             : 
      91           0 :   nsString targetObjectPropName;
      92           0 :   JS::Rooted<JSObject*> targetObject(aCx, nullptr);
      93           0 :   JS::Rooted<JS::Value> currentVal(aCx, aValue);
      94           0 :   JS::Rooted<JSObject*> obj(aCx);
      95             : 
      96           0 :   while (tokenizer.hasMoreTokens()) {
      97           0 :     const nsDependentSubstring& token = tokenizer.nextToken();
      98             : 
      99           0 :     NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath");
     100             : 
     101           0 :     const char16_t* keyPathChars = token.BeginReading();
     102           0 :     const size_t keyPathLen = token.Length();
     103             : 
     104             :     bool hasProp;
     105           0 :     if (!targetObject) {
     106             :       // We're still walking the chain of existing objects
     107             :       // http://w3c.github.io/IndexedDB/#dfn-evaluate-a-key-path-on-a-value
     108             :       // step 4 substep 1: check for .length on a String value.
     109           0 :       if (currentVal.isString() && !tokenizer.hasMoreTokens() &&
     110           0 :           token.EqualsLiteral("length") && aOptions == DoNotCreateProperties) {
     111           0 :         aKeyJSVal->setNumber(double(JS_GetStringLength(currentVal.toString())));
     112           0 :         break;
     113             :       }
     114             : 
     115           0 :       if (!currentVal.isObject()) {
     116           0 :         return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     117             :       }
     118           0 :       obj = &currentVal.toObject();
     119             : 
     120           0 :       bool ok = JS_HasUCProperty(aCx, obj, keyPathChars, keyPathLen,
     121           0 :                                  &hasProp);
     122           0 :       IDB_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     123             : 
     124           0 :       if (hasProp) {
     125             :         // Get if the property exists...
     126           0 :         JS::Rooted<JS::Value> intermediate(aCx);
     127           0 :         bool ok = JS_GetUCProperty(aCx, obj, keyPathChars, keyPathLen, &intermediate);
     128           0 :         IDB_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     129             : 
     130             :         // Treat explicitly undefined as an error.
     131           0 :         if (intermediate.isUndefined()) {
     132           0 :           return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     133             :         }
     134           0 :         if (tokenizer.hasMoreTokens()) {
     135             :           // ...and walk to it if there are more steps...
     136           0 :           currentVal = intermediate;
     137             :         }
     138             :         else {
     139             :           // ...otherwise use it as key
     140           0 :           *aKeyJSVal = intermediate;
     141             :         }
     142             :       }
     143             :       else {
     144             :         // If the property doesn't exist, fall into below path of starting
     145             :         // to define properties, if allowed.
     146           0 :         if (aOptions == DoNotCreateProperties) {
     147           0 :           return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     148             :         }
     149             : 
     150           0 :         targetObject = obj;
     151           0 :         targetObjectPropName = token;
     152             :       }
     153             :     }
     154             : 
     155           0 :     if (targetObject) {
     156             :       // We have started inserting new objects or are about to just insert
     157             :       // the first one.
     158             : 
     159           0 :       aKeyJSVal->setUndefined();
     160             : 
     161           0 :       if (tokenizer.hasMoreTokens()) {
     162             :         // If we're not at the end, we need to add a dummy object to the
     163             :         // chain.
     164           0 :         JS::Rooted<JSObject*> dummy(aCx, JS_NewPlainObject(aCx));
     165           0 :         if (!dummy) {
     166           0 :           IDB_REPORT_INTERNAL_ERR();
     167           0 :           rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     168           0 :           break;
     169             :         }
     170             : 
     171           0 :         if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
     172           0 :                                  token.Length(), dummy, JSPROP_ENUMERATE)) {
     173           0 :           IDB_REPORT_INTERNAL_ERR();
     174           0 :           rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     175           0 :           break;
     176             :         }
     177             : 
     178           0 :         obj = dummy;
     179             :       }
     180             :       else {
     181             :         JS::Rooted<JSObject*> dummy(aCx,
     182           0 :           JS_NewObject(aCx, IDBObjectStore::DummyPropClass()));
     183           0 :         if (!dummy) {
     184           0 :           IDB_REPORT_INTERNAL_ERR();
     185           0 :           rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     186           0 :           break;
     187             :         }
     188             : 
     189           0 :         if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
     190           0 :                                  token.Length(), dummy, JSPROP_ENUMERATE)) {
     191           0 :           IDB_REPORT_INTERNAL_ERR();
     192           0 :           rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     193           0 :           break;
     194             :         }
     195             : 
     196           0 :         obj = dummy;
     197             :       }
     198             :     }
     199             :   }
     200             : 
     201             :   // We guard on rv being a success because we need to run the property
     202             :   // deletion code below even if we should not be running the callback.
     203           0 :   if (NS_SUCCEEDED(rv) && aCallback) {
     204           0 :     rv = (*aCallback)(aCx, aClosure);
     205             :   }
     206             : 
     207           0 :   if (targetObject) {
     208             :     // If this fails, we lose, and the web page sees a magical property
     209             :     // appear on the object :-(
     210           0 :     JS::ObjectOpResult succeeded;
     211           0 :     if (!JS_DeleteUCProperty(aCx, targetObject,
     212             :                              targetObjectPropName.get(),
     213           0 :                              targetObjectPropName.Length(),
     214             :                              succeeded)) {
     215           0 :       IDB_REPORT_INTERNAL_ERR();
     216           0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     217             :     }
     218           0 :     IDB_ENSURE_TRUE(succeeded, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     219             :   }
     220             : 
     221           0 :   NS_ENSURE_SUCCESS(rv, rv);
     222           0 :   return rv;
     223             : }
     224             : 
     225             : } // namespace
     226             : 
     227             : // static
     228             : nsresult
     229           0 : KeyPath::Parse(const nsAString& aString, KeyPath* aKeyPath)
     230             : {
     231           0 :   KeyPath keyPath(0);
     232           0 :   keyPath.SetType(STRING);
     233             : 
     234           0 :   if (!keyPath.AppendStringWithValidation(aString)) {
     235           0 :     return NS_ERROR_FAILURE;
     236             :   }
     237             : 
     238           0 :   *aKeyPath = keyPath;
     239           0 :   return NS_OK;
     240             : }
     241             : 
     242             : //static
     243             : nsresult
     244           0 : KeyPath::Parse(const Sequence<nsString>& aStrings, KeyPath* aKeyPath)
     245             : {
     246           0 :   KeyPath keyPath(0);
     247           0 :   keyPath.SetType(ARRAY);
     248             : 
     249           0 :   for (uint32_t i = 0; i < aStrings.Length(); ++i) {
     250           0 :     if (!keyPath.AppendStringWithValidation(aStrings[i])) {
     251           0 :       return NS_ERROR_FAILURE;
     252             :     }
     253             :   }
     254             : 
     255           0 :   *aKeyPath = keyPath;
     256           0 :   return NS_OK;
     257             : }
     258             : 
     259             : // static
     260             : nsresult
     261           0 : KeyPath::Parse(const Nullable<OwningStringOrStringSequence>& aValue, KeyPath* aKeyPath)
     262             : {
     263           0 :   KeyPath keyPath(0);
     264             : 
     265           0 :   aKeyPath->SetType(NONEXISTENT);
     266             : 
     267           0 :   if (aValue.IsNull()) {
     268           0 :     *aKeyPath = keyPath;
     269           0 :     return NS_OK;
     270             :   }
     271             : 
     272           0 :   if (aValue.Value().IsString()) {
     273           0 :     return Parse(aValue.Value().GetAsString(), aKeyPath);
     274             :   }
     275             : 
     276           0 :   MOZ_ASSERT(aValue.Value().IsStringSequence());
     277             : 
     278           0 :   const Sequence<nsString>& seq = aValue.Value().GetAsStringSequence();
     279           0 :   if (seq.Length() == 0) {
     280           0 :     return NS_ERROR_FAILURE;
     281             :   }
     282           0 :   return Parse(seq, aKeyPath);
     283             : }
     284             : 
     285             : void
     286           0 : KeyPath::SetType(KeyPathType aType)
     287             : {
     288           0 :   mType = aType;
     289           0 :   mStrings.Clear();
     290           0 : }
     291             : 
     292             : bool
     293           0 : KeyPath::AppendStringWithValidation(const nsAString& aString)
     294             : {
     295           0 :   if (!IsValidKeyPathString(aString)) {
     296           0 :     return false;
     297             :   }
     298             : 
     299           0 :   if (IsString()) {
     300           0 :     NS_ASSERTION(mStrings.Length() == 0, "Too many strings!");
     301           0 :     mStrings.AppendElement(aString);
     302           0 :     return true;
     303             :   }
     304             : 
     305           0 :   if (IsArray()) {
     306           0 :     mStrings.AppendElement(aString);
     307           0 :     return true;
     308             :   }
     309             : 
     310           0 :   NS_NOTREACHED("What?!");
     311           0 :   return false;
     312             : }
     313             : 
     314             : nsresult
     315           0 : KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const
     316             : {
     317           0 :   uint32_t len = mStrings.Length();
     318           0 :   JS::Rooted<JS::Value> value(aCx);
     319             : 
     320           0 :   aKey.Unset();
     321             : 
     322           0 :   for (uint32_t i = 0; i < len; ++i) {
     323           0 :     nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
     324             :                                             value.address(),
     325             :                                             DoNotCreateProperties, nullptr,
     326           0 :                                             nullptr);
     327           0 :     if (NS_FAILED(rv)) {
     328           0 :       return rv;
     329             :     }
     330             : 
     331           0 :     if (NS_FAILED(aKey.AppendItem(aCx, IsArray() && i == 0, value))) {
     332           0 :       NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
     333           0 :       return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     334             :     }
     335             :   }
     336             : 
     337           0 :   aKey.FinishArray();
     338             : 
     339           0 :   return NS_OK;
     340             : }
     341             : 
     342             : nsresult
     343           0 : KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
     344             :                            JS::Value* aOutVal) const
     345             : {
     346           0 :   NS_ASSERTION(IsValid(), "This doesn't make sense!");
     347             : 
     348           0 :   if (IsString()) {
     349           0 :     return GetJSValFromKeyPathString(aCx, aValue, mStrings[0], aOutVal,
     350           0 :                                      DoNotCreateProperties, nullptr, nullptr);
     351             :   }
     352             : 
     353           0 :   const uint32_t len = mStrings.Length();
     354           0 :   JS::Rooted<JSObject*> arrayObj(aCx, JS_NewArrayObject(aCx, len));
     355           0 :   if (!arrayObj) {
     356           0 :     return NS_ERROR_OUT_OF_MEMORY;
     357             :   }
     358             : 
     359           0 :   JS::Rooted<JS::Value> value(aCx);
     360           0 :   for (uint32_t i = 0; i < len; ++i) {
     361           0 :     nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
     362             :                                             value.address(),
     363             :                                             DoNotCreateProperties, nullptr,
     364           0 :                                             nullptr);
     365           0 :     if (NS_FAILED(rv)) {
     366           0 :       return rv;
     367             :     }
     368             : 
     369           0 :     if (!JS_DefineElement(aCx, arrayObj, i, value, JSPROP_ENUMERATE)) {
     370           0 :       IDB_REPORT_INTERNAL_ERR();
     371           0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     372             :     }
     373             :   }
     374             : 
     375           0 :   aOutVal->setObject(*arrayObj);
     376           0 :   return NS_OK;
     377             : }
     378             : 
     379             : nsresult
     380           0 : KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue,
     381             :                             Key& aKey, ExtractOrCreateKeyCallback aCallback,
     382             :                             void* aClosure) const
     383             : {
     384           0 :   NS_ASSERTION(IsString(), "This doesn't make sense!");
     385             : 
     386           0 :   JS::Rooted<JS::Value> value(aCx);
     387             : 
     388           0 :   aKey.Unset();
     389             : 
     390           0 :   nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[0],
     391             :                                           value.address(),
     392             :                                           CreateProperties, aCallback,
     393           0 :                                           aClosure);
     394           0 :   if (NS_FAILED(rv)) {
     395           0 :     return rv;
     396             :   }
     397             : 
     398           0 :   if (NS_FAILED(aKey.AppendItem(aCx, false, value))) {
     399           0 :     NS_ASSERTION(aKey.IsUnset(), "Should be unset");
     400           0 :     return value.isUndefined() ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     401             :   }
     402             : 
     403           0 :   aKey.FinishArray();
     404             : 
     405           0 :   return NS_OK;
     406             : }
     407             : 
     408             : void
     409           0 : KeyPath::SerializeToString(nsAString& aString) const
     410             : {
     411           0 :   NS_ASSERTION(IsValid(), "Check to see if I'm valid first!");
     412             : 
     413           0 :   if (IsString()) {
     414           0 :     aString = mStrings[0];
     415           0 :     return;
     416             :   }
     417             : 
     418           0 :   if (IsArray()) {
     419             :     // We use a comma in the beginning to indicate that it's an array of
     420             :     // key paths. This is to be able to tell a string-keypath from an
     421             :     // array-keypath which contains only one item.
     422             :     // It also makes serializing easier :-)
     423           0 :     uint32_t len = mStrings.Length();
     424           0 :     for (uint32_t i = 0; i < len; ++i) {
     425           0 :       aString.Append(',');
     426           0 :       aString.Append(mStrings[i]);
     427             :     }
     428             : 
     429           0 :     return;
     430             :   }
     431             : 
     432           0 :   NS_NOTREACHED("What?");
     433             : }
     434             : 
     435             : // static
     436             : KeyPath
     437           0 : KeyPath::DeserializeFromString(const nsAString& aString)
     438             : {
     439           0 :   KeyPath keyPath(0);
     440             : 
     441           0 :   if (!aString.IsEmpty() && aString.First() == ',') {
     442           0 :     keyPath.SetType(ARRAY);
     443             : 
     444             :     // We use a comma in the beginning to indicate that it's an array of
     445             :     // key paths. This is to be able to tell a string-keypath from an
     446             :     // array-keypath which contains only one item.
     447           0 :     nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> tokenizer(aString, ',');
     448           0 :     tokenizer.nextToken();
     449           0 :     while (tokenizer.hasMoreTokens()) {
     450           0 :       keyPath.mStrings.AppendElement(tokenizer.nextToken());
     451             :     }
     452             : 
     453           0 :     return keyPath;
     454             :   }
     455             : 
     456           0 :   keyPath.SetType(STRING);
     457           0 :   keyPath.mStrings.AppendElement(aString);
     458             : 
     459           0 :   return keyPath;
     460             : }
     461             : 
     462             : nsresult
     463           0 : KeyPath::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aValue) const
     464             : {
     465           0 :   if (IsArray()) {
     466           0 :     uint32_t len = mStrings.Length();
     467           0 :     JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, len));
     468           0 :     if (!array) {
     469           0 :       IDB_WARNING("Failed to make array!");
     470           0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     471             :     }
     472             : 
     473           0 :     for (uint32_t i = 0; i < len; ++i) {
     474           0 :       JS::Rooted<JS::Value> val(aCx);
     475           0 :       nsString tmp(mStrings[i]);
     476           0 :       if (!xpc::StringToJsval(aCx, tmp, &val)) {
     477           0 :         IDB_REPORT_INTERNAL_ERR();
     478           0 :         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     479             :       }
     480             : 
     481           0 :       if (!JS_DefineElement(aCx, array, i, val, JSPROP_ENUMERATE)) {
     482           0 :         IDB_REPORT_INTERNAL_ERR();
     483           0 :         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     484             :       }
     485             :     }
     486             : 
     487           0 :     aValue.setObject(*array);
     488           0 :     return NS_OK;
     489             :   }
     490             : 
     491           0 :   if (IsString()) {
     492           0 :     nsString tmp(mStrings[0]);
     493           0 :     if (!xpc::StringToJsval(aCx, tmp, aValue)) {
     494           0 :       IDB_REPORT_INTERNAL_ERR();
     495           0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     496             :     }
     497           0 :     return NS_OK;
     498             :   }
     499             : 
     500           0 :   aValue.setNull();
     501           0 :   return NS_OK;
     502             : }
     503             : 
     504             : nsresult
     505           0 : KeyPath::ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aValue) const
     506             : {
     507           0 :   JS::Rooted<JS::Value> value(aCx);
     508           0 :   nsresult rv = ToJSVal(aCx, &value);
     509           0 :   if (NS_SUCCEEDED(rv)) {
     510           0 :     aValue = value;
     511             :   }
     512           0 :   return rv;
     513             : }
     514             : 
     515             : bool
     516           0 : KeyPath::IsAllowedForObjectStore(bool aAutoIncrement) const
     517             : {
     518             :   // Any keypath that passed validation is allowed for non-autoIncrement
     519             :   // objectStores.
     520           0 :   if (!aAutoIncrement) {
     521           0 :     return true;
     522             :   }
     523             : 
     524             :   // Array keypaths are not allowed for autoIncrement objectStores.
     525           0 :   if (IsArray()) {
     526           0 :     return false;
     527             :   }
     528             : 
     529             :   // Neither are empty strings.
     530           0 :   if (IsEmpty()) {
     531           0 :     return false;
     532             :   }
     533             : 
     534             :   // Everything else is ok.
     535           0 :   return true;
     536             : }
     537             : 
     538             : } // namespace indexedDB
     539             : } // namespace dom
     540             : } // namespace mozilla

Generated by: LCOV version 1.13