123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "KeyPath.h"
- #include "IDBObjectStore.h"
- #include "Key.h"
- #include "ReportInternalError.h"
- #include "nsCharSeparatedTokenizer.h"
- #include "nsJSUtils.h"
- #include "nsPrintfCString.h"
- #include "xpcpublic.h"
- #include "mozilla/dom/BindingDeclarations.h"
- #include "mozilla/dom/BlobBinding.h"
- #include "mozilla/dom/File.h" // for Blob
- #include "mozilla/dom/IDBObjectStoreBinding.h"
- namespace mozilla {
- namespace dom {
- namespace indexedDB {
- namespace {
- inline
- bool
- IgnoreWhitespace(char16_t c)
- {
- return false;
- }
- typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
- bool
- IsValidKeyPathString(const nsAString& aKeyPath)
- {
- NS_ASSERTION(!aKeyPath.IsVoid(), "What?");
- KeyPathTokenizer tokenizer(aKeyPath, '.');
- while (tokenizer.hasMoreTokens()) {
- nsString token(tokenizer.nextToken());
- if (!token.Length()) {
- return false;
- }
- if (!JS_IsIdentifier(token.get(), token.Length())) {
- return false;
- }
- }
- // If the very last character was a '.', the tokenizer won't give us an empty
- // token, but the keyPath is still invalid.
- if (!aKeyPath.IsEmpty() &&
- aKeyPath.CharAt(aKeyPath.Length() - 1) == '.') {
- return false;
- }
- return true;
- }
- enum KeyExtractionOptions {
- DoNotCreateProperties,
- CreateProperties
- };
- nsresult
- GetJSValFromKeyPathString(JSContext* aCx,
- const JS::Value& aValue,
- const nsAString& aKeyPathString,
- JS::Value* aKeyJSVal,
- KeyExtractionOptions aOptions,
- KeyPath::ExtractOrCreateKeyCallback aCallback,
- void* aClosure)
- {
- NS_ASSERTION(aCx, "Null pointer!");
- NS_ASSERTION(IsValidKeyPathString(aKeyPathString),
- "This will explode!");
- NS_ASSERTION(!(aCallback || aClosure) || aOptions == CreateProperties,
- "This is not allowed!");
- NS_ASSERTION(aOptions != CreateProperties || aCallback,
- "If properties are created, there must be a callback!");
- nsresult rv = NS_OK;
- *aKeyJSVal = aValue;
- KeyPathTokenizer tokenizer(aKeyPathString, '.');
- nsString targetObjectPropName;
- JS::Rooted<JSObject*> targetObject(aCx, nullptr);
- JS::Rooted<JS::Value> currentVal(aCx, aValue);
- JS::Rooted<JSObject*> obj(aCx);
- while (tokenizer.hasMoreTokens()) {
- const nsDependentSubstring& token = tokenizer.nextToken();
- NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath");
- const char16_t* keyPathChars = token.BeginReading();
- const size_t keyPathLen = token.Length();
- if (!targetObject) {
- // We're still walking the chain of existing objects
- // http://w3c.github.io/IndexedDB/#dfn-evaluate-a-key-path-on-a-value
- // step 4 substep 1: check for .length on a String value.
- if (currentVal.isString() && !tokenizer.hasMoreTokens() &&
- token.EqualsLiteral("length") && aOptions == DoNotCreateProperties) {
- aKeyJSVal->setNumber(double(JS_GetStringLength(currentVal.toString())));
- break;
- }
- if (!currentVal.isObject()) {
- return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
- }
- obj = ¤tVal.toObject();
- // We call JS_GetOwnUCPropertyDescriptor on purpose (as opposed to
- // JS_GetUCPropertyDescriptor) to avoid searching the prototype chain.
- JS::Rooted<JS::PropertyDescriptor> desc(aCx);
- bool ok = JS_GetOwnUCPropertyDescriptor(aCx, obj, keyPathChars,
- keyPathLen, &desc);
- IDB_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
- JS::Rooted<JS::Value> intermediate(aCx);
- bool hasProp = false;
- if (desc.object()) {
- intermediate = desc.value();
- hasProp = true;
- } else {
- // If we get here it means the object doesn't have the property or the
- // property is available throuch a getter. We don't want to call any
- // getters to avoid potential re-entrancy.
- // The blob object is special since its properties are available
- // only through getters but we still want to support them for key
- // extraction. So they need to be handled manually.
- Blob* blob;
- if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
- if (token.EqualsLiteral("size")) {
- ErrorResult rv;
- uint64_t size = blob->GetSize(rv);
- MOZ_ALWAYS_TRUE(!rv.Failed());
- intermediate = JS_NumberValue(size);
- hasProp = true;
- } else if (token.EqualsLiteral("type")) {
- nsString type;
- blob->GetType(type);
- JSString* string =
- JS_NewUCStringCopyN(aCx, type.get(), type.Length());
- intermediate = JS::StringValue(string);
- hasProp = true;
- } else {
- RefPtr<File> file = blob->ToFile();
- if (file) {
- if (token.EqualsLiteral("name")) {
- nsString name;
- file->GetName(name);
- JSString* string =
- JS_NewUCStringCopyN(aCx, name.get(), name.Length());
- intermediate = JS::StringValue(string);
- hasProp = true;
- } else if (token.EqualsLiteral("lastModified")) {
- ErrorResult rv;
- int64_t lastModifiedDate = file->GetLastModified(rv);
- MOZ_ALWAYS_TRUE(!rv.Failed());
- intermediate = JS_NumberValue(lastModifiedDate);
- hasProp = true;
- } else if (token.EqualsLiteral("lastModifiedDate")) {
- ErrorResult rv;
- Date lastModifiedDate = file->GetLastModifiedDate(rv);
- MOZ_ALWAYS_TRUE(!rv.Failed());
- lastModifiedDate.ToDateObject(aCx, &intermediate);
- hasProp = true;
- }
- }
- }
- }
- }
- if (hasProp) {
- // Treat explicitly undefined as an error.
- if (intermediate.isUndefined()) {
- return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
- }
- if (tokenizer.hasMoreTokens()) {
- // ...and walk to it if there are more steps...
- currentVal = intermediate;
- }
- else {
- // ...otherwise use it as key
- *aKeyJSVal = intermediate;
- }
- }
- else {
- // If the property doesn't exist, fall into below path of starting
- // to define properties, if allowed.
- if (aOptions == DoNotCreateProperties) {
- return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
- }
- targetObject = obj;
- targetObjectPropName = token;
- }
- }
- if (targetObject) {
- // We have started inserting new objects or are about to just insert
- // the first one.
- aKeyJSVal->setUndefined();
- if (tokenizer.hasMoreTokens()) {
- // If we're not at the end, we need to add a dummy object to the
- // chain.
- JS::Rooted<JSObject*> dummy(aCx, JS_NewPlainObject(aCx));
- if (!dummy) {
- IDB_REPORT_INTERNAL_ERR();
- rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- break;
- }
- if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
- token.Length(), dummy, JSPROP_ENUMERATE)) {
- IDB_REPORT_INTERNAL_ERR();
- rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- break;
- }
- obj = dummy;
- }
- else {
- JS::Rooted<JSObject*> dummy(aCx,
- JS_NewObject(aCx, IDBObjectStore::DummyPropClass()));
- if (!dummy) {
- IDB_REPORT_INTERNAL_ERR();
- rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- break;
- }
- if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(),
- token.Length(), dummy, JSPROP_ENUMERATE)) {
- IDB_REPORT_INTERNAL_ERR();
- rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- break;
- }
- obj = dummy;
- }
- }
- }
- // We guard on rv being a success because we need to run the property
- // deletion code below even if we should not be running the callback.
- if (NS_SUCCEEDED(rv) && aCallback) {
- rv = (*aCallback)(aCx, aClosure);
- }
- if (targetObject) {
- // If this fails, we lose, and the web page sees a magical property
- // appear on the object :-(
- JS::ObjectOpResult succeeded;
- if (!JS_DeleteUCProperty(aCx, targetObject,
- targetObjectPropName.get(),
- targetObjectPropName.Length(),
- succeeded)) {
- IDB_REPORT_INTERNAL_ERR();
- return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- }
- IDB_ENSURE_TRUE(succeeded, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
- }
- NS_ENSURE_SUCCESS(rv, rv);
- return rv;
- }
- } // namespace
- // static
- nsresult
- KeyPath::Parse(const nsAString& aString, KeyPath* aKeyPath)
- {
- KeyPath keyPath(0);
- keyPath.SetType(STRING);
- if (!keyPath.AppendStringWithValidation(aString)) {
- return NS_ERROR_FAILURE;
- }
- *aKeyPath = keyPath;
- return NS_OK;
- }
- //static
- nsresult
- KeyPath::Parse(const Sequence<nsString>& aStrings, KeyPath* aKeyPath)
- {
- KeyPath keyPath(0);
- keyPath.SetType(ARRAY);
- for (uint32_t i = 0; i < aStrings.Length(); ++i) {
- if (!keyPath.AppendStringWithValidation(aStrings[i])) {
- return NS_ERROR_FAILURE;
- }
- }
- *aKeyPath = keyPath;
- return NS_OK;
- }
- // static
- nsresult
- KeyPath::Parse(const Nullable<OwningStringOrStringSequence>& aValue, KeyPath* aKeyPath)
- {
- KeyPath keyPath(0);
- aKeyPath->SetType(NONEXISTENT);
- if (aValue.IsNull()) {
- *aKeyPath = keyPath;
- return NS_OK;
- }
- if (aValue.Value().IsString()) {
- return Parse(aValue.Value().GetAsString(), aKeyPath);
- }
- MOZ_ASSERT(aValue.Value().IsStringSequence());
- const Sequence<nsString>& seq = aValue.Value().GetAsStringSequence();
- if (seq.Length() == 0) {
- return NS_ERROR_FAILURE;
- }
- return Parse(seq, aKeyPath);
- }
- void
- KeyPath::SetType(KeyPathType aType)
- {
- mType = aType;
- mStrings.Clear();
- }
- bool
- KeyPath::AppendStringWithValidation(const nsAString& aString)
- {
- if (!IsValidKeyPathString(aString)) {
- return false;
- }
- if (IsString()) {
- NS_ASSERTION(mStrings.Length() == 0, "Too many strings!");
- mStrings.AppendElement(aString);
- return true;
- }
- if (IsArray()) {
- mStrings.AppendElement(aString);
- return true;
- }
- NS_NOTREACHED("What?!");
- return false;
- }
- nsresult
- KeyPath::ExtractKey(JSContext* aCx,
- const JS::Value& aValue,
- Key& aKey,
- bool aCallGetters) const
- {
- uint32_t len = mStrings.Length();
- JS::Rooted<JS::Value> value(aCx);
- aKey.Unset();
- for (uint32_t i = 0; i < len; ++i) {
- nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
- value.address(),
- DoNotCreateProperties, nullptr,
- nullptr);
- if (NS_FAILED(rv)) {
- return rv;
- }
- if (NS_FAILED(aKey.AppendItem(aCx,
- IsArray() && i == 0,
- value,
- aCallGetters))) {
- NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
- return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
- }
- }
- aKey.FinishArray();
- return NS_OK;
- }
- nsresult
- KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
- JS::Value* aOutVal) const
- {
- NS_ASSERTION(IsValid(), "This doesn't make sense!");
- if (IsString()) {
- return GetJSValFromKeyPathString(aCx, aValue, mStrings[0], aOutVal,
- DoNotCreateProperties, nullptr, nullptr);
- }
- const uint32_t len = mStrings.Length();
- JS::Rooted<JSObject*> arrayObj(aCx, JS_NewArrayObject(aCx, len));
- if (!arrayObj) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- JS::Rooted<JS::Value> value(aCx);
- for (uint32_t i = 0; i < len; ++i) {
- nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
- value.address(),
- DoNotCreateProperties, nullptr,
- nullptr);
- if (NS_FAILED(rv)) {
- return rv;
- }
- if (!JS_DefineElement(aCx, arrayObj, i, value, JSPROP_ENUMERATE)) {
- IDB_REPORT_INTERNAL_ERR();
- return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- }
- }
- aOutVal->setObject(*arrayObj);
- return NS_OK;
- }
- nsresult
- KeyPath::ExtractOrCreateKey(JSContext* aCx,
- const JS::Value& aValue,
- Key& aKey,
- ExtractOrCreateKeyCallback aCallback,
- void* aClosure,
- bool aCallGetters) const
- {
- NS_ASSERTION(IsString(), "This doesn't make sense!");
- JS::Rooted<JS::Value> value(aCx);
- aKey.Unset();
- nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[0],
- value.address(),
- CreateProperties, aCallback,
- aClosure);
- if (NS_FAILED(rv)) {
- return rv;
- }
- if (NS_FAILED(aKey.AppendItem(aCx, false, value, aCallGetters))) {
- NS_ASSERTION(aKey.IsUnset(), "Should be unset");
- return value.isUndefined() ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
- }
- aKey.FinishArray();
- return NS_OK;
- }
- void
- KeyPath::SerializeToString(nsAString& aString) const
- {
- NS_ASSERTION(IsValid(), "Check to see if I'm valid first!");
- if (IsString()) {
- aString = mStrings[0];
- return;
- }
- if (IsArray()) {
- // We use a comma in the beginning to indicate that it's an array of
- // key paths. This is to be able to tell a string-keypath from an
- // array-keypath which contains only one item.
- // It also makes serializing easier :-)
- uint32_t len = mStrings.Length();
- for (uint32_t i = 0; i < len; ++i) {
- aString.Append(',');
- aString.Append(mStrings[i]);
- }
- return;
- }
- NS_NOTREACHED("What?");
- }
- // static
- KeyPath
- KeyPath::DeserializeFromString(const nsAString& aString)
- {
- KeyPath keyPath(0);
- if (!aString.IsEmpty() && aString.First() == ',') {
- keyPath.SetType(ARRAY);
- // We use a comma in the beginning to indicate that it's an array of
- // key paths. This is to be able to tell a string-keypath from an
- // array-keypath which contains only one item.
- nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> tokenizer(aString, ',');
- tokenizer.nextToken();
- while (tokenizer.hasMoreTokens()) {
- keyPath.mStrings.AppendElement(tokenizer.nextToken());
- }
- return keyPath;
- }
- keyPath.SetType(STRING);
- keyPath.mStrings.AppendElement(aString);
- return keyPath;
- }
- nsresult
- KeyPath::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aValue) const
- {
- if (IsArray()) {
- uint32_t len = mStrings.Length();
- JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, len));
- if (!array) {
- IDB_WARNING("Failed to make array!");
- return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- }
- for (uint32_t i = 0; i < len; ++i) {
- JS::Rooted<JS::Value> val(aCx);
- nsString tmp(mStrings[i]);
- if (!xpc::StringToJsval(aCx, tmp, &val)) {
- IDB_REPORT_INTERNAL_ERR();
- return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- }
- if (!JS_DefineElement(aCx, array, i, val, JSPROP_ENUMERATE)) {
- IDB_REPORT_INTERNAL_ERR();
- return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- }
- }
- aValue.setObject(*array);
- return NS_OK;
- }
- if (IsString()) {
- nsString tmp(mStrings[0]);
- if (!xpc::StringToJsval(aCx, tmp, aValue)) {
- IDB_REPORT_INTERNAL_ERR();
- return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
- }
- return NS_OK;
- }
- aValue.setNull();
- return NS_OK;
- }
- nsresult
- KeyPath::ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aValue) const
- {
- JS::Rooted<JS::Value> value(aCx);
- nsresult rv = ToJSVal(aCx, &value);
- if (NS_SUCCEEDED(rv)) {
- aValue = value;
- }
- return rv;
- }
- bool
- KeyPath::IsAllowedForObjectStore(bool aAutoIncrement) const
- {
- // Any keypath that passed validation is allowed for non-autoIncrement
- // objectStores.
- if (!aAutoIncrement) {
- return true;
- }
- // Array keypaths are not allowed for autoIncrement objectStores.
- if (IsArray()) {
- return false;
- }
- // Neither are empty strings.
- if (IsEmpty()) {
- return false;
- }
- // Everything else is ok.
- return true;
- }
- } // namespace indexedDB
- } // namespace dom
- } // namespace mozilla
|