toolkit/modules/PropertyListUtils.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 };

mercurial