/src/mozilla-central/dom/indexedDB/Key.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
8 | | #include "Key.h" |
9 | | |
10 | | #include <algorithm> |
11 | | #include <stdint.h> // for UINT32_MAX, uintptr_t |
12 | | #include "IndexedDatabaseManager.h" |
13 | | #include "js/Date.h" |
14 | | #include "js/Value.h" |
15 | | #include "jsfriendapi.h" |
16 | | #include "mozilla/Casting.h" |
17 | | #include "mozilla/CheckedInt.h" |
18 | | #include "mozilla/EndianUtils.h" |
19 | | #include "mozilla/FloatingPoint.h" |
20 | | #include "mozIStorageStatement.h" |
21 | | #include "mozIStorageValueArray.h" |
22 | | #include "nsAlgorithm.h" |
23 | | #include "nsJSUtils.h" |
24 | | #include "ReportInternalError.h" |
25 | | #include "unicode/ucol.h" |
26 | | #include "xpcpublic.h" |
27 | | |
28 | | namespace mozilla { |
29 | | namespace dom { |
30 | | namespace indexedDB { |
31 | | |
32 | | /* |
33 | | Here's how we encode keys: |
34 | | |
35 | | Basic strategy is the following |
36 | | |
37 | | Numbers: 0x10 n n n n n n n n ("n"s are encoded 64bit float) |
38 | | Dates: 0x20 n n n n n n n n ("n"s are encoded 64bit float) |
39 | | Strings: 0x30 s s s ... 0 ("s"s are encoded unicode bytes) |
40 | | Binaries: 0x40 s s s ... 0 ("s"s are encoded unicode bytes) |
41 | | Arrays: 0x50 i i i ... 0 ("i"s are encoded array items) |
42 | | |
43 | | |
44 | | When encoding floats, 64bit IEEE 754 are almost sortable, except that |
45 | | positive sort lower than negative, and negative sort descending. So we use |
46 | | the following encoding: |
47 | | |
48 | | value < 0 ? |
49 | | (-to64bitInt(value)) : |
50 | | (to64bitInt(value) | 0x8000000000000000) |
51 | | |
52 | | |
53 | | When encoding strings, we use variable-size encoding per the following table |
54 | | |
55 | | Chars 0 - 7E are encoded as 0xxxxxxx with 1 added |
56 | | Chars 7F - (3FFF+7F) are encoded as 10xxxxxx xxxxxxxx with 7F subtracted |
57 | | Chars (3FFF+80) - FFFF are encoded as 11xxxxxx xxxxxxxx xx000000 |
58 | | |
59 | | This ensures that the first byte is never encoded as 0, which means that the |
60 | | string terminator (per basic-stategy table) sorts before any character. |
61 | | The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize |
62 | | the chance that the last character is 0. See below for why. |
63 | | |
64 | | When encoding binaries, the algorithm is the same to how strings are encoded. |
65 | | Since each octet in binary is in the range of [0-255], it'll take 1 to 2 encoded |
66 | | unicode bytes. |
67 | | |
68 | | When encoding Arrays, we use an additional trick. Rather than adding a byte |
69 | | containing the value 0x50 to indicate type, we instead add 0x50 to the next byte. |
70 | | This is usually the byte containing the type of the first item in the array. |
71 | | So simple examples are |
72 | | |
73 | | ["foo"] 0x80 s s s 0 0 // 0x80 is 0x30 + 0x50 |
74 | | [1, 2] 0x60 n n n n n n n n 1 n n n n n n n n 0 // 0x60 is 0x10 + 0x50 |
75 | | |
76 | | Whe do this iteratively if the first item in the array is also an array |
77 | | |
78 | | [["foo"]] 0xA0 s s s 0 0 0 |
79 | | |
80 | | However, to avoid overflow in the byte, we only do this 3 times. If the first |
81 | | item in an array is an array, and that array also has an array as first item, |
82 | | we simply write out the total value accumulated so far and then follow the |
83 | | "normal" rules. |
84 | | |
85 | | [[["foo"]]] 0xF0 0x30 s s s 0 0 0 0 |
86 | | |
87 | | There is another edge case that can happen though, which is that the array |
88 | | doesn't have a first item to which we can add 0x50 to the type. Instead the |
89 | | next byte would normally be the array terminator (per basic-strategy table) |
90 | | so we simply add the 0x50 there. |
91 | | |
92 | | [[]] 0xA0 0 // 0xA0 is 0x50 + 0x50 + 0 |
93 | | [] 0x50 // 0x50 is 0x50 + 0 |
94 | | [[], "foo"] 0xA0 0x30 s s s 0 0 // 0xA0 is 0x50 + 0x50 + 0 |
95 | | |
96 | | Note that the max-3-times rule kicks in before we get a chance to add to the |
97 | | array terminator |
98 | | |
99 | | [[[]]] 0xF0 0 0 0 // 0xF0 is 0x50 + 0x50 + 0x50 |
100 | | |
101 | | As a final optimization we do a post-encoding step which drops all 0s at the |
102 | | end of the encoded buffer. |
103 | | |
104 | | "foo" // 0x30 s s s |
105 | | 1 // 0x10 bf f0 |
106 | | ["a", "b"] // 0x80 s 0 0x30 s |
107 | | [1, 2] // 0x60 bf f0 0 0 0 0 0 0 0x10 c0 |
108 | | [[]] // 0x80 |
109 | | */ |
110 | | |
111 | | nsresult |
112 | | Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const |
113 | 0 | { |
114 | 0 | if (IsUnset()) { |
115 | 0 | aTarget.Unset(); |
116 | 0 | return NS_OK; |
117 | 0 | } |
118 | 0 | |
119 | 0 | if (IsFloat() || IsDate() || IsBinary()) { |
120 | 0 | aTarget.mBuffer = mBuffer; |
121 | 0 | return NS_OK; |
122 | 0 | } |
123 | 0 | |
124 | 0 | aTarget.mBuffer.Truncate(); |
125 | 0 | aTarget.mBuffer.SetCapacity(mBuffer.Length()); |
126 | 0 |
|
127 | 0 | auto* it = reinterpret_cast<const unsigned char*>(mBuffer.BeginReading()); |
128 | 0 | auto* end = reinterpret_cast<const unsigned char*>(mBuffer.EndReading()); |
129 | 0 |
|
130 | 0 | // First we do a pass and see if there are any strings in this key. We only |
131 | 0 | // want to copy/decode when necessary. |
132 | 0 | bool canShareBuffers = true; |
133 | 0 | while (it < end) { |
134 | 0 | auto type = *it % eMaxType; |
135 | 0 | if (type == eTerminator || type == eArray) { |
136 | 0 | it++; |
137 | 0 | } else if (type == eFloat || type == eDate) { |
138 | 0 | it++; |
139 | 0 | it += std::min(sizeof(uint64_t), size_t(end - it)); |
140 | 0 | } else { |
141 | 0 | // We have a string! |
142 | 0 | canShareBuffers = false; |
143 | 0 | break; |
144 | 0 | } |
145 | 0 | } |
146 | 0 |
|
147 | 0 | if (canShareBuffers) { |
148 | 0 | MOZ_ASSERT(it == end); |
149 | 0 | aTarget.mBuffer = mBuffer; |
150 | 0 | return NS_OK; |
151 | 0 | } |
152 | 0 |
|
153 | 0 | // A string was found, so we need to copy the data we've read so far |
154 | 0 | auto* start = reinterpret_cast<const unsigned char*>(mBuffer.BeginReading()); |
155 | 0 | if (it > start) { |
156 | 0 | char* buffer; |
157 | 0 | if (!aTarget.mBuffer.GetMutableData(&buffer, it-start)) { |
158 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
159 | 0 | } |
160 | 0 | |
161 | 0 | while (start < it) { |
162 | 0 | *(buffer++) = *(start++); |
163 | 0 | } |
164 | 0 | } |
165 | 0 |
|
166 | 0 | // Now continue decoding |
167 | 0 | while (it < end) { |
168 | 0 | char* buffer; |
169 | 0 | uint32_t oldLen = aTarget.mBuffer.Length(); |
170 | 0 | auto type = *it % eMaxType; |
171 | 0 |
|
172 | 0 | if (type == eTerminator || type == eArray) { |
173 | 0 | // Copy array TypeID and terminator from raw key |
174 | 0 | if (!aTarget.mBuffer.GetMutableData(&buffer, oldLen + 1)) { |
175 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
176 | 0 | } |
177 | 0 | *(buffer + oldLen) = *(it++); |
178 | 0 | } else if (type == eFloat || type == eDate) { |
179 | 0 | // Copy number from raw key |
180 | 0 | if (!aTarget.mBuffer.GetMutableData(&buffer, |
181 | 0 | oldLen + 1 + sizeof(uint64_t))) { |
182 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
183 | 0 | } |
184 | 0 | buffer += oldLen; |
185 | 0 | *(buffer++) = *(it++); |
186 | 0 |
|
187 | 0 | const size_t byteCount = std::min(sizeof(uint64_t), size_t(end - it)); |
188 | 0 | for (size_t count = 0; count < byteCount; count++) { |
189 | 0 | *(buffer++) = (*it++); |
190 | 0 | } |
191 | 0 | } else { |
192 | 0 | // Decode string and reencode |
193 | 0 | uint8_t typeOffset = *it - eString; |
194 | 0 | MOZ_ASSERT((typeOffset % eArray == 0) && (typeOffset / eArray <= 2)); |
195 | 0 |
|
196 | 0 | nsDependentString str; |
197 | 0 | DecodeString(it, end, str); |
198 | 0 | nsresult rv = aTarget.EncodeLocaleString(str, typeOffset, aLocale); |
199 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
200 | 0 | return rv; |
201 | 0 | } |
202 | 0 | } |
203 | 0 | } |
204 | 0 | aTarget.TrimBuffer(); |
205 | 0 | return NS_OK; |
206 | 0 | } |
207 | | |
208 | | nsresult |
209 | | Key::EncodeJSValInternal(JSContext* aCx, JS::Handle<JS::Value> aVal, |
210 | | uint8_t aTypeOffset, uint16_t aRecursionDepth) |
211 | 0 | { |
212 | 0 | static_assert(eMaxType * kMaxArrayCollapse < 256, |
213 | 0 | "Unable to encode jsvals."); |
214 | 0 |
|
215 | 0 | if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) { |
216 | 0 | return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; |
217 | 0 | } |
218 | 0 | |
219 | 0 | if (aVal.isString()) { |
220 | 0 | nsAutoJSString str; |
221 | 0 | if (!str.init(aCx, aVal)) { |
222 | 0 | IDB_REPORT_INTERNAL_ERR(); |
223 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
224 | 0 | } |
225 | 0 | return EncodeString(str, aTypeOffset); |
226 | 0 | } |
227 | 0 | |
228 | 0 | if (aVal.isNumber()) { |
229 | 0 | double d = aVal.toNumber(); |
230 | 0 | if (mozilla::IsNaN(d)) { |
231 | 0 | return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; |
232 | 0 | } |
233 | 0 | EncodeNumber(d, eFloat + aTypeOffset); |
234 | 0 | return NS_OK; |
235 | 0 | } |
236 | 0 | |
237 | 0 | if (aVal.isObject()) { |
238 | 0 | JS::Rooted<JSObject*> obj(aCx, &aVal.toObject()); |
239 | 0 |
|
240 | 0 | js::ESClass cls; |
241 | 0 | if (!js::GetBuiltinClass(aCx, obj, &cls)) { |
242 | 0 | IDB_REPORT_INTERNAL_ERR(); |
243 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
244 | 0 | } |
245 | 0 | if (cls == js::ESClass::Array) { |
246 | 0 | aTypeOffset += eMaxType; |
247 | 0 |
|
248 | 0 | if (aTypeOffset == eMaxType * kMaxArrayCollapse) { |
249 | 0 | mBuffer.Append(aTypeOffset); |
250 | 0 | aTypeOffset = 0; |
251 | 0 | } |
252 | 0 | NS_ASSERTION((aTypeOffset % eMaxType) == 0 && |
253 | 0 | aTypeOffset < (eMaxType * kMaxArrayCollapse), |
254 | 0 | "Wrong typeoffset"); |
255 | 0 |
|
256 | 0 | uint32_t length; |
257 | 0 | if (!JS_GetArrayLength(aCx, obj, &length)) { |
258 | 0 | IDB_REPORT_INTERNAL_ERR(); |
259 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
260 | 0 | } |
261 | 0 |
|
262 | 0 | for (uint32_t index = 0; index < length; index++) { |
263 | 0 | JS::Rooted<JS::Value> val(aCx); |
264 | 0 | if (!JS_GetElement(aCx, obj, index, &val)) { |
265 | 0 | IDB_REPORT_INTERNAL_ERR(); |
266 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
267 | 0 | } |
268 | 0 |
|
269 | 0 | nsresult rv = EncodeJSValInternal(aCx, val, aTypeOffset, |
270 | 0 | aRecursionDepth + 1); |
271 | 0 | if (NS_FAILED(rv)) { |
272 | 0 | return rv; |
273 | 0 | } |
274 | 0 | |
275 | 0 | aTypeOffset = 0; |
276 | 0 | } |
277 | 0 |
|
278 | 0 | mBuffer.Append(eTerminator + aTypeOffset); |
279 | 0 |
|
280 | 0 | return NS_OK; |
281 | 0 | } |
282 | 0 | |
283 | 0 | if (cls == js::ESClass::Date) { |
284 | 0 | bool valid; |
285 | 0 | if (!js::DateIsValid(aCx, obj, &valid)) { |
286 | 0 | IDB_REPORT_INTERNAL_ERR(); |
287 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
288 | 0 | } |
289 | 0 | if (!valid) { |
290 | 0 | return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; |
291 | 0 | } |
292 | 0 | double t; |
293 | 0 | if (!js::DateGetMsecSinceEpoch(aCx, obj, &t)) { |
294 | 0 | IDB_REPORT_INTERNAL_ERR(); |
295 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
296 | 0 | } |
297 | 0 | EncodeNumber(t, eDate + aTypeOffset); |
298 | 0 | return NS_OK; |
299 | 0 | } |
300 | 0 | |
301 | 0 | if (JS_IsArrayBufferObject(obj)) { |
302 | 0 | return EncodeBinary(obj, /* aIsViewObject */ false, aTypeOffset); |
303 | 0 | } |
304 | 0 | |
305 | 0 | if (JS_IsArrayBufferViewObject(obj)) { |
306 | 0 | return EncodeBinary(obj, /* aIsViewObject */ true, aTypeOffset); |
307 | 0 | } |
308 | 0 | } |
309 | 0 | |
310 | 0 | return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; |
311 | 0 | } |
312 | | |
313 | | // static |
314 | | nsresult |
315 | | Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd, |
316 | | JSContext* aCx, uint8_t aTypeOffset, JS::MutableHandle<JS::Value> aVal, |
317 | | uint16_t aRecursionDepth) |
318 | 0 | { |
319 | 0 | if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) { |
320 | 0 | return NS_ERROR_DOM_INDEXEDDB_DATA_ERR; |
321 | 0 | } |
322 | 0 | |
323 | 0 | if (*aPos - aTypeOffset >= eArray) { |
324 | 0 | JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0)); |
325 | 0 | if (!array) { |
326 | 0 | NS_WARNING("Failed to make array!"); |
327 | 0 | IDB_REPORT_INTERNAL_ERR(); |
328 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
329 | 0 | } |
330 | 0 |
|
331 | 0 | aTypeOffset += eMaxType; |
332 | 0 |
|
333 | 0 | if (aTypeOffset == eMaxType * kMaxArrayCollapse) { |
334 | 0 | ++aPos; |
335 | 0 | aTypeOffset = 0; |
336 | 0 | } |
337 | 0 |
|
338 | 0 | uint32_t index = 0; |
339 | 0 | JS::Rooted<JS::Value> val(aCx); |
340 | 0 | while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) { |
341 | 0 | nsresult rv = DecodeJSValInternal(aPos, aEnd, aCx, aTypeOffset, |
342 | 0 | &val, aRecursionDepth + 1); |
343 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
344 | 0 |
|
345 | 0 | aTypeOffset = 0; |
346 | 0 |
|
347 | 0 | if (!JS_DefineElement(aCx, array, index++, val, JSPROP_ENUMERATE)) { |
348 | 0 | NS_WARNING("Failed to set array element!"); |
349 | 0 | IDB_REPORT_INTERNAL_ERR(); |
350 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
351 | 0 | } |
352 | 0 | } |
353 | 0 |
|
354 | 0 | NS_ASSERTION(aPos >= aEnd || (*aPos % eMaxType) == eTerminator, |
355 | 0 | "Should have found end-of-array marker"); |
356 | 0 | ++aPos; |
357 | 0 |
|
358 | 0 | aVal.setObject(*array); |
359 | 0 | } |
360 | 0 | else if (*aPos - aTypeOffset == eString) { |
361 | 0 | nsString key; |
362 | 0 | DecodeString(aPos, aEnd, key); |
363 | 0 | if (!xpc::StringToJsval(aCx, key, aVal)) { |
364 | 0 | IDB_REPORT_INTERNAL_ERR(); |
365 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
366 | 0 | } |
367 | 0 | } |
368 | 0 | else if (*aPos - aTypeOffset == eDate) { |
369 | 0 | double msec = static_cast<double>(DecodeNumber(aPos, aEnd)); |
370 | 0 | JS::ClippedTime time = JS::TimeClip(msec); |
371 | 0 | MOZ_ASSERT(msec == time.toDouble(), |
372 | 0 | "encoding from a Date object not containing an invalid date " |
373 | 0 | "means we should always have clipped values"); |
374 | 0 | JSObject* date = JS::NewDateObject(aCx, time); |
375 | 0 | if (!date) { |
376 | 0 | IDB_WARNING("Failed to make date!"); |
377 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
378 | 0 | } |
379 | 0 |
|
380 | 0 | aVal.setObject(*date); |
381 | 0 | } |
382 | 0 | else if (*aPos - aTypeOffset == eFloat) { |
383 | 0 | aVal.setDouble(DecodeNumber(aPos, aEnd)); |
384 | 0 | } |
385 | 0 | else if (*aPos - aTypeOffset == eBinary) { |
386 | 0 | JSObject* binary = DecodeBinary(aPos, aEnd, aCx); |
387 | 0 | if (!binary) { |
388 | 0 | IDB_REPORT_INTERNAL_ERR(); |
389 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
390 | 0 | } |
391 | 0 |
|
392 | 0 | aVal.setObject(*binary); |
393 | 0 | } |
394 | 0 | else { |
395 | 0 | MOZ_ASSERT_UNREACHABLE("Unknown key type!"); |
396 | 0 | } |
397 | 0 |
|
398 | 0 | return NS_OK; |
399 | 0 | } |
400 | | |
401 | 0 | #define ONE_BYTE_LIMIT 0x7E |
402 | 0 | #define TWO_BYTE_LIMIT (0x3FFF+0x7F) |
403 | | |
404 | 0 | #define ONE_BYTE_ADJUST 1 |
405 | 0 | #define TWO_BYTE_ADJUST (-0x7F) |
406 | 0 | #define THREE_BYTE_SHIFT 6 |
407 | | |
408 | | nsresult |
409 | | Key::EncodeJSVal(JSContext* aCx, |
410 | | JS::Handle<JS::Value> aVal, |
411 | | uint8_t aTypeOffset) |
412 | 0 | { |
413 | 0 | return EncodeJSValInternal(aCx, aVal, aTypeOffset, 0); |
414 | 0 | } |
415 | | |
416 | | nsresult |
417 | | Key::EncodeString(const nsAString& aString, uint8_t aTypeOffset) |
418 | 0 | { |
419 | 0 | const char16_t* start = aString.BeginReading(); |
420 | 0 | const char16_t* end = aString.EndReading(); |
421 | 0 | return EncodeString(start, end, aTypeOffset); |
422 | 0 | } |
423 | | |
424 | | template <typename T> |
425 | | nsresult |
426 | | Key::EncodeString(const T* aStart, const T* aEnd, uint8_t aTypeOffset) |
427 | 0 | { |
428 | 0 | return EncodeAsString(aStart, aEnd, eString + aTypeOffset); |
429 | 0 | } Unexecuted instantiation: nsresult mozilla::dom::indexedDB::Key::EncodeString<char16_t>(char16_t const*, char16_t const*, unsigned char) Unexecuted instantiation: nsresult mozilla::dom::indexedDB::Key::EncodeString<unsigned char>(unsigned char const*, unsigned char const*, unsigned char) |
430 | | |
431 | | template <typename T> |
432 | | nsresult |
433 | | Key::EncodeAsString(const T* aStart, const T* aEnd, uint8_t aType) |
434 | 0 | { |
435 | 0 | // First measure how long the encoded string will be. |
436 | 0 | if (NS_WARN_IF(aStart > aEnd || UINT32_MAX - 2 < uintptr_t(aEnd - aStart))) { |
437 | 0 | IDB_REPORT_INTERNAL_ERR(); |
438 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
439 | 0 | } |
440 | 0 |
|
441 | 0 | // The +2 is for initial 3 and trailing 0. We'll compensate for multi-byte |
442 | 0 | // chars below. |
443 | 0 | uint32_t checkedSize = aEnd - aStart; |
444 | 0 | CheckedUint32 size = checkedSize; |
445 | 0 | size += 2; |
446 | 0 |
|
447 | 0 | MOZ_ASSERT(size.isValid()); |
448 | 0 |
|
449 | 0 | const T* start = aStart; |
450 | 0 | const T* end = aEnd; |
451 | 0 | for (const T* iter = start; iter < end; ++iter) { |
452 | 0 | if (*iter > ONE_BYTE_LIMIT) { |
453 | 0 | size += char16_t(*iter) > TWO_BYTE_LIMIT ? 2 : 1; |
454 | 0 | if (!size.isValid()) { |
455 | 0 | IDB_REPORT_INTERNAL_ERR(); |
456 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
457 | 0 | } |
458 | 0 | } |
459 | 0 | } |
460 | 0 |
|
461 | 0 | // Allocate memory for the new size |
462 | 0 | uint32_t oldLen = mBuffer.Length(); |
463 | 0 | size += oldLen; |
464 | 0 |
|
465 | 0 | if (!size.isValid()) { |
466 | 0 | IDB_REPORT_INTERNAL_ERR(); |
467 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
468 | 0 | } |
469 | 0 |
|
470 | 0 | char* buffer; |
471 | 0 | if (!mBuffer.GetMutableData(&buffer, size.value())) { |
472 | 0 | IDB_REPORT_INTERNAL_ERR(); |
473 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
474 | 0 | } |
475 | 0 | buffer += oldLen; |
476 | 0 |
|
477 | 0 | // Write type marker |
478 | 0 | *(buffer++) = aType; |
479 | 0 |
|
480 | 0 | // Encode string |
481 | 0 | for (const T* iter = start; iter < end; ++iter) { |
482 | 0 | if (*iter <= ONE_BYTE_LIMIT) { |
483 | 0 | *(buffer++) = *iter + ONE_BYTE_ADJUST; |
484 | 0 | } |
485 | 0 | else if (char16_t(*iter) <= TWO_BYTE_LIMIT) { |
486 | 0 | char16_t c = char16_t(*iter) + TWO_BYTE_ADJUST + 0x8000; |
487 | 0 | *(buffer++) = (char)(c >> 8); |
488 | 0 | *(buffer++) = (char)(c & 0xFF); |
489 | 0 | } |
490 | 0 | else { |
491 | 0 | uint32_t c = (uint32_t(*iter) << THREE_BYTE_SHIFT) | 0x00C00000; |
492 | 0 | *(buffer++) = (char)(c >> 16); |
493 | 0 | *(buffer++) = (char)(c >> 8); |
494 | 0 | *(buffer++) = (char)c; |
495 | 0 | } |
496 | 0 | } |
497 | 0 |
|
498 | 0 | // Write end marker |
499 | 0 | *(buffer++) = eTerminator; |
500 | 0 |
|
501 | 0 | NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes"); |
502 | 0 |
|
503 | 0 | return NS_OK; |
504 | 0 | } Unexecuted instantiation: nsresult mozilla::dom::indexedDB::Key::EncodeAsString<char16_t>(char16_t const*, char16_t const*, unsigned char) Unexecuted instantiation: nsresult mozilla::dom::indexedDB::Key::EncodeAsString<unsigned char>(unsigned char const*, unsigned char const*, unsigned char) |
505 | | |
506 | | nsresult |
507 | | Key::EncodeLocaleString(const nsDependentString& aString, uint8_t aTypeOffset, |
508 | | const nsCString& aLocale) |
509 | 0 | { |
510 | 0 | const int length = aString.Length(); |
511 | 0 | if (length == 0) { |
512 | 0 | return NS_OK; |
513 | 0 | } |
514 | 0 | const UChar* ustr = reinterpret_cast<const UChar*>(aString.BeginReading()); |
515 | 0 |
|
516 | 0 | UErrorCode uerror = U_ZERO_ERROR; |
517 | 0 | UCollator* collator = ucol_open(aLocale.get(), &uerror); |
518 | 0 | if (NS_WARN_IF(U_FAILURE(uerror))) { |
519 | 0 | return NS_ERROR_FAILURE; |
520 | 0 | } |
521 | 0 | MOZ_ASSERT(collator); |
522 | 0 |
|
523 | 0 | AutoTArray<uint8_t, 128> keyBuffer; |
524 | 0 | int32_t sortKeyLength = ucol_getSortKey(collator, ustr, length, |
525 | 0 | keyBuffer.Elements(), |
526 | 0 | keyBuffer.Length()); |
527 | 0 | if (sortKeyLength > (int32_t)keyBuffer.Length()) { |
528 | 0 | keyBuffer.SetLength(sortKeyLength); |
529 | 0 | sortKeyLength = ucol_getSortKey(collator, ustr, length, |
530 | 0 | keyBuffer.Elements(), |
531 | 0 | sortKeyLength); |
532 | 0 | } |
533 | 0 |
|
534 | 0 | ucol_close(collator); |
535 | 0 | if (NS_WARN_IF(sortKeyLength == 0)) { |
536 | 0 | return NS_ERROR_FAILURE; |
537 | 0 | } |
538 | 0 | |
539 | 0 | return EncodeString(keyBuffer.Elements(), |
540 | 0 | keyBuffer.Elements()+sortKeyLength, |
541 | 0 | aTypeOffset); |
542 | 0 | } |
543 | | |
544 | | // static |
545 | | nsresult |
546 | | Key::DecodeJSVal(const unsigned char*& aPos, |
547 | | const unsigned char* aEnd, |
548 | | JSContext* aCx, |
549 | | JS::MutableHandle<JS::Value> aVal) |
550 | 0 | { |
551 | 0 | return DecodeJSValInternal(aPos, aEnd, aCx, 0, aVal, 0); |
552 | 0 | } |
553 | | |
554 | | // static |
555 | | void |
556 | | Key::DecodeString(const unsigned char*& aPos, const unsigned char* aEnd, |
557 | | nsString& aString) |
558 | 0 | { |
559 | 0 | NS_ASSERTION(*aPos % eMaxType == eString, "Don't call me!"); |
560 | 0 |
|
561 | 0 | const unsigned char* buffer = aPos + 1; |
562 | 0 |
|
563 | 0 | // First measure how big the decoded string will be. |
564 | 0 | uint32_t size = 0; |
565 | 0 | const unsigned char* iter; |
566 | 0 | for (iter = buffer; iter < aEnd && *iter != eTerminator; ++iter) { |
567 | 0 | if (*iter & 0x80) { |
568 | 0 | iter += (*iter & 0x40) ? 2 : 1; |
569 | 0 | } |
570 | 0 | ++size; |
571 | 0 | } |
572 | 0 |
|
573 | 0 | // Set end so that we don't have to check for null termination in the loop |
574 | 0 | // below |
575 | 0 | if (iter < aEnd) { |
576 | 0 | aEnd = iter; |
577 | 0 | } |
578 | 0 |
|
579 | 0 | char16_t* out; |
580 | 0 | if (size && !aString.GetMutableData(&out, size)) { |
581 | 0 | return; |
582 | 0 | } |
583 | 0 | |
584 | 0 | for (iter = buffer; iter < aEnd;) { |
585 | 0 | if (!(*iter & 0x80)) { |
586 | 0 | *out = *(iter++) - ONE_BYTE_ADJUST; |
587 | 0 | } |
588 | 0 | else if (!(*iter & 0x40)) { |
589 | 0 | char16_t c = (char16_t(*(iter++)) << 8); |
590 | 0 | if (iter < aEnd) { |
591 | 0 | c |= *(iter++); |
592 | 0 | } |
593 | 0 | *out = c - TWO_BYTE_ADJUST - 0x8000; |
594 | 0 | } |
595 | 0 | else { |
596 | 0 | uint32_t c = uint32_t(*(iter++)) << (16 - THREE_BYTE_SHIFT); |
597 | 0 | if (iter < aEnd) { |
598 | 0 | c |= uint32_t(*(iter++)) << (8 - THREE_BYTE_SHIFT); |
599 | 0 | } |
600 | 0 | if (iter < aEnd) { |
601 | 0 | c |= *(iter++) >> THREE_BYTE_SHIFT; |
602 | 0 | } |
603 | 0 | *out = (char16_t)c; |
604 | 0 | } |
605 | 0 |
|
606 | 0 | ++out; |
607 | 0 | } |
608 | 0 |
|
609 | 0 | NS_ASSERTION(!size || out == aString.EndReading(), |
610 | 0 | "Should have written the whole string"); |
611 | 0 |
|
612 | 0 | aPos = iter + 1; |
613 | 0 | } |
614 | | |
615 | | void |
616 | | Key::EncodeNumber(double aFloat, uint8_t aType) |
617 | 0 | { |
618 | 0 | // Allocate memory for the new size |
619 | 0 | uint32_t oldLen = mBuffer.Length(); |
620 | 0 | char* buffer; |
621 | 0 | if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) { |
622 | 0 | return; |
623 | 0 | } |
624 | 0 | buffer += oldLen; |
625 | 0 |
|
626 | 0 | *(buffer++) = aType; |
627 | 0 |
|
628 | 0 | uint64_t bits = BitwiseCast<uint64_t>(aFloat); |
629 | 0 | // Note: The subtraction from 0 below is necessary to fix |
630 | 0 | // MSVC build warning C4146 (negating an unsigned value). |
631 | 0 | const uint64_t signbit = FloatingPoint<double>::kSignBit; |
632 | 0 | uint64_t number = bits & signbit ? (0 - bits) : (bits | signbit); |
633 | 0 |
|
634 | 0 | mozilla::BigEndian::writeUint64(buffer, number); |
635 | 0 | } |
636 | | |
637 | | // static |
638 | | double |
639 | | Key::DecodeNumber(const unsigned char*& aPos, const unsigned char* aEnd) |
640 | 0 | { |
641 | 0 | NS_ASSERTION(*aPos % eMaxType == eFloat || |
642 | 0 | *aPos % eMaxType == eDate, "Don't call me!"); |
643 | 0 |
|
644 | 0 | ++aPos; |
645 | 0 |
|
646 | 0 | uint64_t number = 0; |
647 | 0 | memcpy(&number, aPos, std::min<size_t>(sizeof(number), aEnd - aPos)); |
648 | 0 | number = mozilla::NativeEndian::swapFromBigEndian(number); |
649 | 0 |
|
650 | 0 | aPos += sizeof(number); |
651 | 0 |
|
652 | 0 | // Note: The subtraction from 0 below is necessary to fix |
653 | 0 | // MSVC build warning C4146 (negating an unsigned value). |
654 | 0 | const uint64_t signbit = FloatingPoint<double>::kSignBit; |
655 | 0 | uint64_t bits = number & signbit ? (number & ~signbit) : (0 - number); |
656 | 0 |
|
657 | 0 | return BitwiseCast<double>(bits); |
658 | 0 | } |
659 | | |
660 | | nsresult |
661 | | Key::EncodeBinary(JSObject* aObject, bool aIsViewObject, uint8_t aTypeOffset) |
662 | 0 | { |
663 | 0 | uint8_t* bufferData; |
664 | 0 | uint32_t bufferLength; |
665 | 0 | bool unused; |
666 | 0 |
|
667 | 0 | if (aIsViewObject) { |
668 | 0 | js::GetArrayBufferViewLengthAndData(aObject, &bufferLength, &unused, &bufferData); |
669 | 0 | } else { |
670 | 0 | js::GetArrayBufferLengthAndData(aObject, &bufferLength, &unused, &bufferData); |
671 | 0 | } |
672 | 0 |
|
673 | 0 | return EncodeAsString(bufferData, bufferData + bufferLength, eBinary + aTypeOffset); |
674 | 0 | } |
675 | | |
676 | | // static |
677 | | JSObject* |
678 | | Key::DecodeBinary(const unsigned char*& aPos, |
679 | | const unsigned char* aEnd, |
680 | | JSContext* aCx) |
681 | 0 | { |
682 | 0 | MOZ_ASSERT(*aPos % eMaxType == eBinary, "Don't call me!"); |
683 | 0 |
|
684 | 0 | const unsigned char* buffer = ++aPos; |
685 | 0 |
|
686 | 0 | // First measure how big the decoded array buffer will be. |
687 | 0 | size_t size = 0; |
688 | 0 | const unsigned char* iter; |
689 | 0 | for (iter = buffer; iter < aEnd && *iter != eTerminator; ++iter) { |
690 | 0 | if (*iter & 0x80) { |
691 | 0 | iter++; |
692 | 0 | } |
693 | 0 | ++size; |
694 | 0 | } |
695 | 0 |
|
696 | 0 | if (!size) { |
697 | 0 | return JS_NewArrayBuffer(aCx, 0); |
698 | 0 | } |
699 | 0 | |
700 | 0 | uint8_t* out = static_cast<uint8_t*>(JS_malloc(aCx, size)); |
701 | 0 | if (NS_WARN_IF(!out)) { |
702 | 0 | return nullptr; |
703 | 0 | } |
704 | 0 | |
705 | 0 | uint8_t* pos = out; |
706 | 0 |
|
707 | 0 | // Set end so that we don't have to check for null termination in the loop |
708 | 0 | // below |
709 | 0 | if (iter < aEnd) { |
710 | 0 | aEnd = iter; |
711 | 0 | } |
712 | 0 |
|
713 | 0 | for (iter = buffer; iter < aEnd;) { |
714 | 0 | if (!(*iter & 0x80)) { |
715 | 0 | *pos = *(iter++) - ONE_BYTE_ADJUST; |
716 | 0 | } |
717 | 0 | else { |
718 | 0 | uint16_t c = (uint16_t(*(iter++)) << 8); |
719 | 0 | if (iter < aEnd) { |
720 | 0 | c |= *(iter++); |
721 | 0 | } |
722 | 0 | *pos = static_cast<uint8_t>(c - TWO_BYTE_ADJUST - 0x8000); |
723 | 0 | } |
724 | 0 |
|
725 | 0 | ++pos; |
726 | 0 | } |
727 | 0 |
|
728 | 0 | aPos = iter + 1; |
729 | 0 |
|
730 | 0 | MOZ_ASSERT(static_cast<size_t>(pos - out) == size, |
731 | 0 | "Should have written the whole buffer"); |
732 | 0 |
|
733 | 0 | return JS_NewArrayBufferWithContents(aCx, size, out); |
734 | 0 | } |
735 | | |
736 | | nsresult |
737 | | Key::BindToStatement(mozIStorageStatement* aStatement, |
738 | | const nsACString& aParamName) const |
739 | 0 | { |
740 | 0 | nsresult rv; |
741 | 0 | if (IsUnset()) { |
742 | 0 | rv = aStatement->BindNullByName(aParamName); |
743 | 0 | } else { |
744 | 0 | rv = aStatement->BindBlobByName(aParamName, |
745 | 0 | reinterpret_cast<const uint8_t*>(mBuffer.get()), mBuffer.Length()); |
746 | 0 | } |
747 | 0 |
|
748 | 0 | return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
749 | 0 | } |
750 | | |
751 | | nsresult |
752 | | Key::SetFromStatement(mozIStorageStatement* aStatement, |
753 | | uint32_t aIndex) |
754 | 0 | { |
755 | 0 | return SetFromSource(aStatement, aIndex); |
756 | 0 | } |
757 | | |
758 | | nsresult |
759 | | Key::SetFromValueArray(mozIStorageValueArray* aValues, |
760 | | uint32_t aIndex) |
761 | 0 | { |
762 | 0 | return SetFromSource(aValues, aIndex); |
763 | 0 | } |
764 | | |
765 | | nsresult |
766 | | Key::SetFromJSVal(JSContext* aCx, |
767 | | JS::Handle<JS::Value> aVal) |
768 | 0 | { |
769 | 0 | mBuffer.Truncate(); |
770 | 0 |
|
771 | 0 | if (aVal.isNull() || aVal.isUndefined()) { |
772 | 0 | Unset(); |
773 | 0 | return NS_OK; |
774 | 0 | } |
775 | 0 | |
776 | 0 | nsresult rv = EncodeJSVal(aCx, aVal, 0); |
777 | 0 | if (NS_FAILED(rv)) { |
778 | 0 | Unset(); |
779 | 0 | return rv; |
780 | 0 | } |
781 | 0 | TrimBuffer(); |
782 | 0 |
|
783 | 0 | return NS_OK; |
784 | 0 | } |
785 | | |
786 | | nsresult |
787 | | Key::ToJSVal(JSContext* aCx, |
788 | | JS::MutableHandle<JS::Value> aVal) const |
789 | 0 | { |
790 | 0 | if (IsUnset()) { |
791 | 0 | aVal.setUndefined(); |
792 | 0 | return NS_OK; |
793 | 0 | } |
794 | 0 | |
795 | 0 | const unsigned char* pos = BufferStart(); |
796 | 0 | nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, aVal); |
797 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
798 | 0 | return rv; |
799 | 0 | } |
800 | 0 | |
801 | 0 | MOZ_ASSERT(pos >= BufferEnd()); |
802 | 0 |
|
803 | 0 | return NS_OK; |
804 | 0 | } |
805 | | |
806 | | nsresult |
807 | | Key::ToJSVal(JSContext* aCx, |
808 | | JS::Heap<JS::Value>& aVal) const |
809 | 0 | { |
810 | 0 | JS::Rooted<JS::Value> value(aCx); |
811 | 0 | nsresult rv = ToJSVal(aCx, &value); |
812 | 0 | if (NS_SUCCEEDED(rv)) { |
813 | 0 | aVal = value; |
814 | 0 | } |
815 | 0 | return rv; |
816 | 0 | } |
817 | | |
818 | | nsresult |
819 | | Key::AppendItem(JSContext* aCx, bool aFirstOfArray, JS::Handle<JS::Value> aVal) |
820 | 0 | { |
821 | 0 | nsresult rv = EncodeJSVal(aCx, aVal, aFirstOfArray ? eMaxType : 0); |
822 | 0 | if (NS_FAILED(rv)) { |
823 | 0 | Unset(); |
824 | 0 | return rv; |
825 | 0 | } |
826 | 0 | |
827 | 0 | return NS_OK; |
828 | 0 | } |
829 | | |
830 | | template <typename T> |
831 | | nsresult |
832 | | Key::SetFromSource(T* aSource, uint32_t aIndex) |
833 | 0 | { |
834 | 0 | const uint8_t* data; |
835 | 0 | uint32_t dataLength = 0; |
836 | 0 |
|
837 | 0 | nsresult rv = aSource->GetSharedBlob(aIndex, &dataLength, &data); |
838 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
839 | 0 | return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; |
840 | 0 | } |
841 | 0 | |
842 | 0 | mBuffer.Assign(reinterpret_cast<const char*>(data), dataLength); |
843 | 0 |
|
844 | 0 | return NS_OK; |
845 | 0 | } Unexecuted instantiation: nsresult mozilla::dom::indexedDB::Key::SetFromSource<mozIStorageStatement>(mozIStorageStatement*, unsigned int) Unexecuted instantiation: nsresult mozilla::dom::indexedDB::Key::SetFromSource<mozIStorageValueArray>(mozIStorageValueArray*, unsigned int) |
846 | | |
847 | | #ifdef DEBUG |
848 | | |
849 | | void |
850 | | Key::Assert(bool aCondition) const |
851 | | { |
852 | | MOZ_ASSERT(aCondition); |
853 | | } |
854 | | |
855 | | #endif // DEBUG |
856 | | |
857 | | } // namespace indexedDB |
858 | | } // namespace dom |
859 | | } // namespace mozilla |