KeyPath.cpp 17 KB

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