Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6 * Module for reading Property Lists (.plist) files
7 * ------------------------------------------------
8 * This module functions as a reader for Apple Property Lists (.plist files).
9 * It supports both binary and xml formatted property lists. It does not
10 * support the legacy ASCII format. Reading of Cocoa's Keyed Archives serialized
11 * to binary property lists isn't supported either.
12 *
13 * Property Lists objects are represented by standard JS and Mozilla types,
14 * namely:
15 *
16 * XML type Cocoa Class Returned type(s)
17 * --------------------------------------------------------------------------
18 * <true/> / <false/> NSNumber TYPE_PRIMITIVE boolean
19 * <integer> / <real> NSNumber TYPE_PRIMITIVE number
20 * TYPE_INT64 String [1]
21 * Not Available NSNull TYPE_PRIMITIVE null [2]
22 * TYPE_PRIMITIVE undefined [3]
23 * <date/> NSDate TYPE_DATE Date
24 * <data/> NSData TYPE_UINT8_ARRAY Uint8Array
25 * <array/> NSArray TYPE_ARRAY Array
26 * Not Available NSSet TYPE_ARRAY Array [2][4]
27 * <dict/> NSDictionary TYPE_DICTIONARY Dict (from Dict.jsm)
28 *
29 * Use PropertyListUtils.getObjectType to detect the type of a Property list
30 * object.
31 *
32 * -------------
33 * 1) Property lists supports storing U/Int64 numbers, while JS can only handle
34 * numbers that are in this limits of float-64 (±2^53). For numbers that
35 * do not outbound this limits, simple primitive number are always used.
36 * Otherwise, a String object.
37 * 2) About NSNull and NSSet values: While the xml format has no support for
38 * representing null and set values, the documentation for the binary format
39 * states that it supports storing both types. However, the Cocoa APIs for
40 * serializing property lists do not seem to support either types (test with
41 * NSPropertyListSerialization::propertyList:isValidForFormat). Furthermore,
42 * if an array or a dictioanry contains a NSNull or a NSSet value, they cannot
43 * be serialized to a property list.
44 * As for usage within OS X, not surprisingly there's no known usage of
45 * storing either of these types in a property list. It seems that, for now,
46 * Apple is keeping the features of binary and xml formats in sync, probably as
47 * long as the XML format is not officially deprecated.
48 * 3) Not used anywhere.
49 * 4) About NSSet representation: For the time being, we represent those
50 * theoretical NSSet objects the same way NSArray is represented.
51 * While this would most certainly work, it is not the right way to handle
52 * it. A more correct representation for a set is a js generator, which would
53 * read the set lazily and has no indices semantics.
54 */
56 "use strict";
58 this.EXPORTED_SYMBOLS = ["PropertyListUtils"];
60 const Cc = Components.classes;
61 const Ci = Components.interfaces;
62 const Cu = Components.utils;
64 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
66 XPCOMUtils.defineLazyModuleGetter(this, "Dict",
67 "resource://gre/modules/Dict.jsm");
68 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
69 "resource://gre/modules/ctypes.jsm");
70 XPCOMUtils.defineLazyModuleGetter(this, "Services",
71 "resource://gre/modules/Services.jsm");
73 this.PropertyListUtils = Object.freeze({
74 /**
75 * Asynchronously reads a file as a property list.
76 *
77 * @param aFile (nsIDOMBlob/nsILocalFile)
78 * the file to be read as a property list.
79 * @param aCallback
80 * If the property list is read successfully, aPropertyListRoot is set
81 * to the root object of the property list.
82 * Use getPropertyListObjectType to detect its type.
83 * If it's not read successfully, aPropertyListRoot is set to null.
84 * The reaon for failure is reported to the Error Console.
85 */
86 read: function PLU_read(aFile, aCallback) {
87 if (!(aFile instanceof Ci.nsILocalFile || aFile instanceof Ci.nsIDOMFile))
88 throw new Error("aFile is not a file object");
89 if (typeof(aCallback) != "function")
90 throw new Error("Invalid value for aCallback");
92 // We guarantee not to throw directly for any other exceptions, and always
93 // call aCallback.
94 Services.tm.mainThread.dispatch(function() {
95 let file = aFile;
96 try {
97 if (file instanceof Ci.nsILocalFile) {
98 if (!file.exists())
99 throw new Error("The file pointed by aFile does not exist");
101 file = new File(file);
102 }
104 let fileReader = Cc["@mozilla.org/files/filereader;1"].
105 createInstance(Ci.nsIDOMFileReader);
106 let onLoadEnd = function() {
107 let root = null;
108 try {
109 fileReader.removeEventListener("loadend", onLoadEnd, false);
110 if (fileReader.readyState != fileReader.DONE)
111 throw new Error("Could not read file contents: " + fileReader.error);
113 root = this._readFromArrayBufferSync(fileReader.result);
114 }
115 finally {
116 aCallback(root);
117 }
118 }.bind(this);
119 fileReader.addEventListener("loadend", onLoadEnd, false);
120 fileReader.readAsArrayBuffer(file);
121 }
122 catch(ex) {
123 aCallback(null);
124 throw ex;
125 }
126 }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
127 },
129 /**
130 * DO NOT USE ME. Once Bug 718189 is fixed, this method won't be public.
131 *
132 * Synchronously read an ArrayBuffer contents as a property list.
133 */
134 _readFromArrayBufferSync: function PLU__readFromArrayBufferSync(aBuffer) {
135 if (BinaryPropertyListReader.prototype.canProcess(aBuffer))
136 return new BinaryPropertyListReader(aBuffer).root;
138 // Convert the buffer into an XML tree.
139 let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
140 createInstance(Ci.nsIDOMParser);
141 let bytesView = new Uint8Array(aBuffer);
142 try {
143 let doc = domParser.parseFromBuffer(bytesView, bytesView.length,
144 "application/xml");
145 return new XMLPropertyListReader(doc).root;
146 }
147 catch(ex) {
148 throw new Error("aBuffer cannot be parsed as a DOM document: " + ex);
149 }
150 return null;
151 },
153 TYPE_PRIMITIVE: 0,
154 TYPE_DATE: 1,
155 TYPE_UINT8_ARRAY: 2,
156 TYPE_ARRAY: 3,
157 TYPE_DICTIONARY: 4,
158 TYPE_INT64: 5,
160 /**
161 * Get the type in which the given property list object is represented.
162 * Check the header for the mapping between the TYPE* constants to js types
163 * and objects.
164 *
165 * @return one of the TYPE_* constants listed above.
166 * @note this method is merely for convenience. It has no magic to detect
167 * that aObject is indeed a property list object created by this module.
168 */
169 getObjectType: function PLU_getObjectType(aObject) {
170 if (aObject === null || typeof(aObject) != "object")
171 return this.TYPE_PRIMITIVE;
173 // Given current usage, we could assume that aObject was created in the
174 // scope of this module, but in future, this util may be used as part of
175 // serializing js objects to a property list - in which case the object
176 // would most likely be created in the caller's scope.
177 let global = Cu.getGlobalForObject(aObject);
179 if (global.Dict && aObject instanceof global.Dict)
180 return this.TYPE_DICTIONARY;
181 if (Array.isArray(aObject))
182 return this.TYPE_ARRAY;
183 if (aObject instanceof global.Date)
184 return this.TYPE_DATE;
185 if (aObject instanceof global.Uint8Array)
186 return this.TYPE_UINT8_ARRAY;
187 if (aObject instanceof global.String && "__INT_64_WRAPPER__" in aObject)
188 return this.TYPE_INT64;
190 throw new Error("aObject is not as a property list object.");
191 },
193 /**
194 * Wraps a 64-bit stored in the form of a string primitive as a String object,
195 * which we can later distiguish from regular string values.
196 * @param aPrimitive
197 * a number in the form of either a primitive string or a primitive number.
198 * @return a String wrapper around aNumberStr that can later be identified
199 * as holding 64-bit number using getObjectType.
200 */
201 wrapInt64: function PLU_wrapInt64(aPrimitive) {
202 if (typeof(aPrimitive) != "string" && typeof(aPrimitive) != "number")
203 throw new Error("aPrimitive should be a string primitive");
205 let wrapped = new String(aPrimitive);
206 Object.defineProperty(wrapped, "__INT_64_WRAPPER__", { value: true });
207 return wrapped;
208 }
209 });
211 /**
212 * Here's the base structure of binary-format property lists.
213 * 1) Header - magic number
214 * - 6 bytes - "bplist"
215 * - 2 bytes - version number. This implementation only supports version 00.
216 * 2) Objects Table
217 * Variable-sized objects, see _readObject for how various types of objects
218 * are constructed.
219 * 3) Offsets Table
220 * The offset of each object in the objects table. The integer size is
221 * specified in the trailer.
222 * 4) Trailer
223 * - 6 unused bytes
224 * - 1 byte: the size of integers in the offsets table
225 * - 1 byte: the size of object references for arrays, sets and
226 * dictionaries.
227 * - 8 bytes: the number of objects in the objects table
228 * - 8 bytes: the index of the root object's offset in the offsets table.
229 * - 8 bytes: the offset of the offsets table.
230 *
231 * Note: all integers are stored in big-endian form.
232 */
234 /**
235 * Reader for binary-format property lists.
236 *
237 * @param aBuffer
238 * ArrayBuffer object from which the binary plist should be read.
239 */
240 function BinaryPropertyListReader(aBuffer) {
241 this._buffer = aBuffer;
243 const JS_MAX_INT = Math.pow(2,53);
244 this._JS_MAX_INT_SIGNED = ctypes.Int64(JS_MAX_INT);
245 this._JS_MAX_INT_UNSIGNED = ctypes.UInt64(JS_MAX_INT);
246 this._JS_MIN_INT = ctypes.Int64(-JS_MAX_INT);
248 try {
249 this._readTrailerInfo();
250 this._readObjectsOffsets();
251 }
252 catch(ex) {
253 throw new Error("Could not read aBuffer as a binary property list");
254 }
255 this._objects = [];
256 }
258 BinaryPropertyListReader.prototype = {
259 /**
260 * Checks if the given ArrayBuffer can be read as a binary property list.
261 * It can be called on the prototype.
262 */
263 canProcess: function BPLR_canProcess(aBuffer)
264 [String.fromCharCode(c) for each (c in new Uint8Array(aBuffer, 0, 8))].
265 join("") == "bplist00",
267 get root() this._readObject(this._rootObjectIndex),
269 _readTrailerInfo: function BPLR__readTrailer() {
270 // The first 6 bytes of the 32-bytes trailer are unused
271 let trailerOffset = this._buffer.byteLength - 26;
272 [this._offsetTableIntegerSize, this._objectRefSize] =
273 this._readUnsignedInts(trailerOffset, 1, 2);
275 [this._numberOfObjects, this._rootObjectIndex, this._offsetTableOffset] =
276 this._readUnsignedInts(trailerOffset + 2, 8, 3);
277 },
279 _readObjectsOffsets: function BPLR__readObjectsOffsets() {
280 this._offsetTable = this._readUnsignedInts(this._offsetTableOffset,
281 this._offsetTableIntegerSize,
282 this._numberOfObjects);
283 },
285 // TODO: This should be removed once DataView is implemented (Bug 575688).
286 _swapForBigEndian:
287 function BPLR__swapForBigEndian(aByteOffset, aIntSize, aNumberOfInts) {
288 let bytesCount = aIntSize * aNumberOfInts;
289 let bytes = new Uint8Array(this._buffer, aByteOffset, bytesCount);
290 let swapped = new Uint8Array(bytesCount);
291 for (let i = 0; i < aNumberOfInts; i++) {
292 for (let j = 0; j < aIntSize; j++) {
293 swapped[(i * aIntSize) + j] = bytes[(i * aIntSize) + (aIntSize - 1 - j)];
294 }
295 }
296 return swapped;
297 },
299 _readSignedInt64: function BPLR__readSignedInt64(aByteOffset) {
300 let swapped = this._swapForBigEndian(aByteOffset, 8, 1);
301 let lo = new Uint32Array(swapped.buffer, 0, 1)[0];
302 let hi = new Int32Array(swapped.buffer, 4, 1)[0];
303 let int64 = ctypes.Int64.join(hi, lo);
304 if (ctypes.Int64.compare(int64, this._JS_MAX_INT_SIGNED) == 1 ||
305 ctypes.Int64.compare(int64, this._JS_MIN_INT) == -1)
306 return PropertyListUtils.wrapInt64(int64.toString());
308 return parseInt(int64.toString(), 10);
309 },
311 _readReal: function BPLR__readReal(aByteOffset, aRealSize) {
312 let swapped = this._swapForBigEndian(aByteOffset, aRealSize, 1);
313 if (aRealSize == 4)
314 return new Float32Array(swapped.buffer, 0, 1)[0];
315 if (aRealSize == 8)
316 return new Float64Array(swapped.buffer, 0, 1)[0];
318 throw new Error("Unsupported real size: " + aRealSize);
319 },
321 OBJECT_TYPE_BITS: {
322 SIMPLE: parseInt("0000", 2),
323 INTEGER: parseInt("0001", 2),
324 REAL: parseInt("0010", 2),
325 DATE: parseInt("0011", 2),
326 DATA: parseInt("0100", 2),
327 ASCII_STRING: parseInt("0101", 2),
328 UNICODE_STRING: parseInt("0110", 2),
329 UID: parseInt("1000", 2),
330 ARRAY: parseInt("1010", 2),
331 SET: parseInt("1100", 2),
332 DICTIONARY: parseInt("1101", 2)
333 },
335 ADDITIONAL_INFO_BITS: {
336 // Applies to OBJECT_TYPE_BITS.SIMPLE
337 NULL: parseInt("0000", 2),
338 FALSE: parseInt("1000", 2),
339 TRUE: parseInt("1001", 2),
340 FILL_BYTE: parseInt("1111", 2),
341 // Applies to OBJECT_TYPE_BITS.DATE
342 DATE: parseInt("0011", 2),
343 // Applies to OBJECT_TYPE_BITS.DATA, ASCII_STRING, UNICODE_STRING, ARRAY,
344 // SET and DICTIONARY.
345 LENGTH_INT_SIZE_FOLLOWS: parseInt("1111", 2)
346 },
348 /**
349 * Returns an object descriptor in the form of two integers: object type and
350 * additional info.
351 *
352 * @param aByteOffset
353 * the descriptor's offset.
354 * @return [objType, additionalInfo] - the object type and additional info.
355 * @see OBJECT_TYPE_BITS and ADDITIONAL_INFO_BITS
356 */
357 _readObjectDescriptor: function BPLR__readObjectDescriptor(aByteOffset) {
358 // The first four bits hold the object type. For some types, additional
359 // info is held in the other 4 bits.
360 let byte = this._readUnsignedInts(aByteOffset, 1, 1)[0];
361 return [(byte & 0xF0) >> 4, byte & 0x0F];
362 },
364 _readDate: function BPLR__readDate(aByteOffset) {
365 // That's the reference date of NSDate.
366 let date = new Date("1 January 2001, GMT");
368 // NSDate values are float values, but setSeconds takes an integer.
369 date.setMilliseconds(this._readReal(aByteOffset, 8) * 1000);
370 return date;
371 },
373 /**
374 * Reads a portion of the buffer as a string.
375 *
376 * @param aByteOffset
377 * The offset in the buffer at which the string starts
378 * @param aNumberOfChars
379 * The length of the string to be read (that is the number of
380 * characters, not bytes).
381 * @param aUnicode
382 * Whether or not it is a unicode string.
383 * @return the string read.
384 *
385 * @note this is tested to work well with unicode surrogate pairs. Because
386 * all unicode characters are read as 2-byte integers, unicode surrogate
387 * pairs are read from the buffer in the form of two integers, as required
388 * by String.fromCharCode.
389 */
390 _readString:
391 function BPLR__readString(aByteOffset, aNumberOfChars, aUnicode) {
392 let codes = this._readUnsignedInts(aByteOffset, aUnicode ? 2 : 1,
393 aNumberOfChars);
394 return [String.fromCharCode(c) for each (c in codes)].join("");
395 },
397 /**
398 * Reads an array of unsigned integers from the buffer. Integers larger than
399 * one byte are read in big endian form.
400 *
401 * @param aByteOffset
402 * The offset in the buffer at which the array starts.
403 * @param aIntSize
404 * The size of each int in the array.
405 * @param aLength
406 * The number of ints in the array.
407 * @param [optional] aBigIntAllowed (default: false)
408 * Whether or not to accept integers which outbounds JS limits for
409 * numbers (±2^53) in the form of a String.
410 * @return an array of integers (number primitive and/or Strings for large
411 * numbers (see header)).
412 * @throws if aBigIntAllowed is false and one of the integers in the array
413 * cannot be represented by a primitive js number.
414 */
415 _readUnsignedInts:
416 function BPLR__readUnsignedInts(aByteOffset, aIntSize, aLength, aBigIntAllowed) {
417 if (aIntSize == 1)
418 return new Uint8Array(this._buffer, aByteOffset, aLength);
420 // There are two reasons for the complexity you see here:
421 // (1) 64-bit integers - For which we use ctypes. When possible, the
422 // number is converted back to js's default float-64 type.
423 // (2) The DataView object for ArrayBuffer, which takes care of swaping
424 // bytes, is not yet implemented (bug 575688).
425 let swapped = this._swapForBigEndian(aByteOffset, aIntSize, aLength);
426 if (aIntSize == 2)
427 return new Uint16Array(swapped.buffer);
428 if (aIntSize == 4)
429 return new Uint32Array(swapped.buffer);
430 if (aIntSize == 8) {
431 let intsArray = [];
432 let lo_hi_view = new Uint32Array(swapped.buffer);
433 for (let i = 0; i < lo_hi_view.length; i += 2) {
434 let [lo, hi] = [lo_hi_view[i], lo_hi_view[i+1]];
435 let uint64 = ctypes.UInt64.join(hi, lo);
436 if (ctypes.UInt64.compare(uint64, this._JS_MAX_INT_UNSIGNED) == 1) {
437 if (aBigIntAllowed === true)
438 intsArray.push(PropertyListUtils.wrapInt64(uint64.toString()));
439 else
440 throw new Error("Integer too big to be read as float 64");
441 }
442 else {
443 intsArray.push(parseInt(uint64.toString(), 10));
444 }
445 }
446 return intsArray;
447 }
448 throw new Error("Unsupported size: " + aIntSize);
449 },
451 /**
452 * Reads from the buffer the data object-count and the offset at which the
453 * first object starts.
454 *
455 * @param aObjectOffset
456 * the object's offset.
457 * @return [offset, count] - the offset in the buffer at which the first
458 * object in data starts, and the number of objects.
459 */
460 _readDataOffsetAndCount:
461 function BPLR__readDataOffsetAndCount(aObjectOffset) {
462 // The length of some objects in the data can be stored in two ways:
463 // * If it is small enough, it is stored in the second four bits of the
464 // object descriptors.
465 // * Otherwise, those bits are set to 1111, indicating that the next byte
466 // consists of the integer size of the data-length (also stored in the form
467 // of an object descriptor). The length follows this byte.
468 let [, maybeLength] = this._readObjectDescriptor(aObjectOffset);
469 if (maybeLength != this.ADDITIONAL_INFO_BITS.LENGTH_INT_SIZE_FOLLOWS)
470 return [aObjectOffset + 1, maybeLength];
472 let [, intSizeInfo] = this._readObjectDescriptor(aObjectOffset + 1);
474 // The int size is 2^intSizeInfo.
475 let intSize = Math.pow(2, intSizeInfo);
476 let dataLength = this._readUnsignedInts(aObjectOffset + 2, intSize, 1)[0];
477 return [aObjectOffset + 2 + intSize, dataLength];
478 },
480 /**
481 * Read array from the buffer and wrap it as a js array.
482 * @param aObjectOffset
483 * the offset in the buffer at which the array starts.
484 * @param aNumberOfObjects
485 * the number of objects in the array.
486 * @return a js array.
487 */
488 _wrapArray: function BPLR__wrapArray(aObjectOffset, aNumberOfObjects) {
489 let refs = this._readUnsignedInts(aObjectOffset,
490 this._objectRefSize,
491 aNumberOfObjects);
493 let array = new Array(aNumberOfObjects);
494 let readObjectBound = this._readObject.bind(this);
496 // Each index in the returned array is a lazy getter for its object.
497 Array.prototype.forEach.call(refs, function(ref, objIndex) {
498 Object.defineProperty(array, objIndex, {
499 get: function() {
500 delete array[objIndex];
501 return array[objIndex] = readObjectBound(ref);
502 },
503 configurable: true,
504 enumerable: true
505 });
506 }, this);
507 return array;
508 },
510 /**
511 * Reads dictionary from the buffer and wraps it as a Dict object (as defined
512 * in Dict.jsm).
513 * @param aObjectOffset
514 * the offset in the buffer at which the dictionary starts
515 * @param aNumberOfObjects
516 * the number of keys in the dictionary
517 * @return Dict.jsm-style dictionary.
518 */
519 _wrapDictionary: function(aObjectOffset, aNumberOfObjects) {
520 // A dictionary in the binary format is stored as a list of references to
521 // key-objects, followed by a list of references to the value-objects for
522 // those keys. The size of each list is aNumberOfObjects * this._objectRefSize.
523 let dict = new Dict();
524 if (aNumberOfObjects == 0)
525 return dict;
527 let keyObjsRefs = this._readUnsignedInts(aObjectOffset, this._objectRefSize,
528 aNumberOfObjects);
529 let valObjsRefs =
530 this._readUnsignedInts(aObjectOffset + aNumberOfObjects * this._objectRefSize,
531 this._objectRefSize, aNumberOfObjects);
532 for (let i = 0; i < aNumberOfObjects; i++) {
533 let key = this._readObject(keyObjsRefs[i]);
534 let readBound = this._readObject.bind(this, valObjsRefs[i]);
535 dict.setAsLazyGetter(key, readBound);
536 }
537 return dict;
538 },
540 /**
541 * Reads an object at the spcified index in the object table
542 * @param aObjectIndex
543 * index at the object table
544 * @return the property list object at the given index.
545 */
546 _readObject: function BPLR__readObject(aObjectIndex) {
547 // If the object was previously read, return the cached object.
548 if (this._objects[aObjectIndex] !== undefined)
549 return this._objects[aObjectIndex];
551 let objOffset = this._offsetTable[aObjectIndex];
552 let [objType, additionalInfo] = this._readObjectDescriptor(objOffset);
553 let value;
554 switch (objType) {
555 case this.OBJECT_TYPE_BITS.SIMPLE: {
556 switch (additionalInfo) {
557 case this.ADDITIONAL_INFO_BITS.NULL:
558 value = null;
559 break;
560 case this.ADDITIONAL_INFO_BITS.FILL_BYTE:
561 value = undefined;
562 break;
563 case this.ADDITIONAL_INFO_BITS.FALSE:
564 value = false;
565 break;
566 case this.ADDITIONAL_INFO_BITS.TRUE:
567 value = true;
568 break;
569 default:
570 throw new Error("Unexpected value!");
571 }
572 break;
573 }
575 case this.OBJECT_TYPE_BITS.INTEGER: {
576 // The integer is sized 2^additionalInfo.
577 let intSize = Math.pow(2, additionalInfo);
579 // For objects, 64-bit integers are always signed. Negative integers
580 // are always represented by a 64-bit integer.
581 if (intSize == 8)
582 value = this._readSignedInt64(objOffset + 1);
583 else
584 value = this._readUnsignedInts(objOffset + 1, intSize, 1, true)[0];
585 break;
586 }
588 case this.OBJECT_TYPE_BITS.REAL: {
589 // The real is sized 2^additionalInfo.
590 value = this._readReal(objOffset + 1, Math.pow(2, additionalInfo));
591 break;
592 }
594 case this.OBJECT_TYPE_BITS.DATE: {
595 if (additionalInfo != this.ADDITIONAL_INFO_BITS.DATE)
596 throw new Error("Unexpected value");
598 value = this._readDate(objOffset + 1);
599 break;
600 }
602 case this.OBJECT_TYPE_BITS.DATA: {
603 let [offset, bytesCount] = this._readDataOffsetAndCount(objOffset);
604 value = this._readUnsignedInts(offset, 1, bytesCount);
605 break;
606 }
608 case this.OBJECT_TYPE_BITS.ASCII_STRING: {
609 let [offset, charsCount] = this._readDataOffsetAndCount(objOffset);
610 value = this._readString(offset, charsCount, false);
611 break;
612 }
614 case this.OBJECT_TYPE_BITS.UNICODE_STRING: {
615 let [offset, unicharsCount] = this._readDataOffsetAndCount(objOffset);
616 value = this._readString(offset, unicharsCount, true);
617 break;
618 }
620 case this.OBJECT_TYPE_BITS.UID: {
621 // UIDs are only used in Keyed Archives, which are not yet supported.
622 throw new Error("Keyed Archives are not supported");
623 }
625 case this.OBJECT_TYPE_BITS.ARRAY:
626 case this.OBJECT_TYPE_BITS.SET: {
627 // Note: For now, we fallback to handle sets the same way we handle
628 // arrays. See comments in the header of this file.
630 // The bytes following the count are references to objects (indices).
631 // Each reference is an unsigned int with size=this._objectRefSize.
632 let [offset, objectsCount] = this._readDataOffsetAndCount(objOffset);
633 value = this._wrapArray(offset, objectsCount);
634 break;
635 }
637 case this.OBJECT_TYPE_BITS.DICTIONARY: {
638 let [offset, objectsCount] = this._readDataOffsetAndCount(objOffset);
639 value = this._wrapDictionary(offset, objectsCount);
640 break;
641 }
643 default: {
644 throw new Error("Unknown object type: " + objType);
645 }
646 }
648 return this._objects[aObjectIndex] = value;
649 }
650 };
652 /**
653 * Reader for XML property lists.
654 *
655 * @param aDOMDoc
656 * the DOM document to be read as a property list.
657 */
658 function XMLPropertyListReader(aDOMDoc) {
659 let docElt = aDOMDoc.documentElement;
660 if (!docElt || docElt.localName != "plist" || !docElt.firstElementChild)
661 throw new Error("aDoc is not a property list document");
663 this._plistRootElement = docElt.firstElementChild;
664 }
666 XMLPropertyListReader.prototype = {
667 get root() this._readObject(this._plistRootElement),
669 /**
670 * Convert a dom element to a property list object.
671 * @param aDOMElt
672 * a dom element in a xml tree of a property list.
673 * @return a js object representing the property list object.
674 */
675 _readObject: function XPLR__readObject(aDOMElt) {
676 switch (aDOMElt.localName) {
677 case "true":
678 return true;
679 case "false":
680 return false;
681 case "string":
682 case "key":
683 return aDOMElt.textContent;
684 case "integer":
685 return this._readInteger(aDOMElt);
686 case "real": {
687 let number = parseFloat(aDOMElt.textContent.trim());
688 if (isNaN(number))
689 throw "Could not parse float value";
690 return number;
691 }
692 case "date":
693 return new Date(aDOMElt.textContent);
694 case "data":
695 // Strip spaces and new lines.
696 let base64str = aDOMElt.textContent.replace(/\s*/g, "");
697 let decoded = atob(base64str);
698 return new Uint8Array([decoded.charCodeAt(i) for (i in decoded)]);
699 case "dict":
700 return this._wrapDictionary(aDOMElt);
701 case "array":
702 return this._wrapArray(aDOMElt);
703 default:
704 throw new Error("Unexpected tagname");
705 }
706 },
708 _readInteger: function XPLR__readInteger(aDOMElt) {
709 // The integer may outbound js's max/min integer value. We recognize this
710 // case by comparing the parsed number to the original string value.
711 // In case of an outbound, we fallback to return the number as a string.
712 let numberAsString = aDOMElt.textContent.toString();
713 let parsedNumber = parseInt(numberAsString, 10);
714 if (isNaN(parsedNumber))
715 throw new Error("Could not parse integer value");
717 if (parsedNumber.toString() == numberAsString)
718 return parsedNumber;
720 return PropertyListUtils.wrapInt64(numberAsString);
721 },
723 _wrapDictionary: function XPLR__wrapDictionary(aDOMElt) {
724 // <dict>
725 // <key>my true bool</key>
726 // <true/>
727 // <key>my string key</key>
728 // <string>My String Key</string>
729 // </dict>
730 if (aDOMElt.children.length % 2 != 0)
731 throw new Error("Invalid dictionary");
732 let dict = new Dict();
733 for (let i = 0; i < aDOMElt.children.length; i += 2) {
734 let keyElem = aDOMElt.children[i];
735 let valElem = aDOMElt.children[i + 1];
737 if (keyElem.localName != "key")
738 throw new Error("Invalid dictionary");
740 let keyName = this._readObject(keyElem);
741 let readBound = this._readObject.bind(this, valElem);
742 dict.setAsLazyGetter(keyName, readBound);
743 }
744 return dict;
745 },
747 _wrapArray: function XPLR__wrapArray(aDOMElt) {
748 // <array>
749 // <string>...</string>
750 // <integer></integer>
751 // <dict>
752 // ....
753 // </dict>
754 // </array>
756 // Each element in the array is a lazy getter for its property list object.
757 let array = [];
758 let readObjectBound = this._readObject.bind(this);
759 Array.prototype.forEach.call(aDOMElt.children, function(elem, elemIndex) {
760 Object.defineProperty(array, elemIndex, {
761 get: function() {
762 delete array[elemIndex];
763 return array[elemIndex] = readObjectBound(elem);
764 },
765 configurable: true,
766 enumerable: true
767 });
768 });
769 return array;
770 }
771 };