dom/mobilemessage/src/gonk/WspPduHelper.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 "use strict";
     7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
     9 Cu.import("resource://gre/modules/wap_consts.js", this);
    11 let DEBUG; // set to true to see debug messages
    13 // Special ASCII characters
    14 const NUL = 0;
    15 const CR = 13;
    16 const LF = 10;
    17 const SP = 32;
    18 const HT = 9;
    19 const DQUOTE = 34;
    20 const DEL = 127;
    22 // Special ASCII character ranges
    23 const CTLS = 32;
    24 const ASCIIS = 128;
    26 /**
    27  * Error class for generic encoding/decoding failures.
    28  */
    29 this.CodeError = function CodeError(message) {
    30   this.name = "CodeError";
    31   this.message = message || "Invalid format";
    32 }
    33 CodeError.prototype = new Error();
    34 CodeError.prototype.constructor = CodeError;
    36 /**
    37  * Error class for unexpected NUL char at decoding text elements.
    38  *
    39  * @param message [optional]
    40  *        A short description for the error.
    41  */
    42 function NullCharError(message) {
    43   this.name = "NullCharError";
    44   this.message = message || "Null character found";
    45 }
    46 NullCharError.prototype = new CodeError();
    47 NullCharError.prototype.constructor = NullCharError;
    49 /**
    50  * Error class for fatal encoding/decoding failures.
    51  *
    52  * This error is only raised when expected format isn't met and the parser
    53  * context can't do anything more to either skip it or hand over to other
    54  * alternative encoding/decoding steps.
    55  *
    56  * @param message [optional]
    57  *        A short description for the error.
    58  */
    59 this.FatalCodeError = function FatalCodeError(message) {
    60   this.name = "FatalCodeError";
    61   this.message = message || "Decoding fails";
    62 }
    63 FatalCodeError.prototype = new Error();
    64 FatalCodeError.prototype.constructor = FatalCodeError;
    66 /**
    67  * Error class for undefined well known encoding.
    68  *
    69  * When a encoded header field/parameter has unknown/unsupported value, we may
    70  * never know how to decode the next value. For example, a parameter of
    71  * undefined well known encoding may be followed by a Q-value, which is
    72  * basically a uintvar. However, there is no way you can distiguish an Q-value
    73  * 0.64, encoded as 0x41, from a string begins with 'A', which is also 0x41.
    74  * The `skipValue` will try the latter one, which is not expected.
    75  *
    76  * @param message [optional]
    77  *        A short description for the error.
    78  */
    79 this.NotWellKnownEncodingError = function NotWellKnownEncodingError(message) {
    80   this.name = "NotWellKnownEncodingError";
    81   this.message = message || "Not well known encoding";
    82 }
    83 NotWellKnownEncodingError.prototype = new FatalCodeError();
    84 NotWellKnownEncodingError.prototype.constructor = NotWellKnownEncodingError;
    86 /**
    87  * Internal helper function to retrieve the value of a property with its name
    88  * specified by `name` inside the object `headers`.
    89  *
    90  * @param headers
    91  *        An object that contains parsed header fields.
    92  * @param name
    93  *        Header name string to be checked.
    94  *
    95  * @return Value of specified header field.
    96  *
    97  * @throws FatalCodeError if headers[name] is undefined.
    98  */
    99 this.ensureHeader = function ensureHeader(headers, name) {
   100   let value = headers[name];
   101   // Header field might have a null value as NoValue
   102   if (value === undefined) {
   103     throw new FatalCodeError("ensureHeader: header " + name + " not defined");
   104   }
   105   return value;
   106 }
   108 /**
   109  * Skip field value.
   110  *
   111  * The WSP field values are encoded so that the length of the field value can
   112  * always be determined, even if the detailed format of a specific field value
   113  * is not known. This makes it possible to skip over individual header fields
   114  * without interpreting their content. ... the first octet in all the field
   115  * values can be interpreted as follows:
   116  *
   117  *   0 -  30 | This octet is followed by the indicated number (0 - 30) of data
   118  *             octets.
   119  *        31 | This octet is followed by a unitvar, which indicates the number
   120  *             of data octets after it.
   121  *  32 - 127 | The value is a text string, terminated by a zero octet (NUL
   122  *             character).
   123  * 128 - 255 | It is an encoded 7-bit value; this header has no more data.
   124  *
   125  * @param data
   126  *        A wrapped object containing raw PDU data.
   127  *
   128  * @return Skipped value of several possible types like string, integer, or
   129  *         an array of octets.
   130  *
   131  * @see WAP-230-WSP-20010705-a clause 8.4.1.2
   132  */
   133 this.skipValue = function skipValue(data) {
   134   let begin = data.offset;
   135   let value = Octet.decode(data);
   136   if (value <= 31) {
   137     if (value == 31) {
   138       value = UintVar.decode(data);
   139     }
   141     if (value) {
   142       // `value` can be larger than 30, max length of a multi-octet integer
   143       // here. So we must decode it as an array instead.
   144       value = Octet.decodeMultiple(data, data.offset + value);
   145     } else {
   146       value = null;
   147     }
   148   } else if (value <= 127) {
   149     data.offset = begin;
   150     value = NullTerminatedTexts.decode(data);
   151   } else {
   152     value &= 0x7F;
   153   }
   155   return value;
   156 }
   158 /**
   159  * Helper function for decoding multiple alternative forms.
   160  *
   161  * @param data
   162  *        A wrapped object containing raw PDU data.
   163  * @param options
   164  *        Extra context for decoding.
   165  *
   166  * @return Decoded value.
   167  */
   168 this.decodeAlternatives = function decodeAlternatives(data, options) {
   169   let begin = data.offset;
   170   for (let i = 2; i < arguments.length; i++) {
   171     try {
   172       return arguments[i].decode(data, options);
   173     } catch (e) {
   174       // Throw the last exception we get
   175       if (i == (arguments.length - 1)) {
   176         throw e;
   177       }
   179       data.offset = begin;
   180     }
   181   }
   182 }
   184 /**
   185  * Helper function for encoding multiple alternative forms.
   186  *
   187  * @param data
   188  *        A wrapped object to store encoded raw data.
   189  * @param value
   190  *        Object value of arbitrary type to be encoded.
   191  * @param options
   192  *        Extra context for encoding.
   193  */
   194 this.encodeAlternatives = function encodeAlternatives(data, value, options) {
   195   let begin = data.offset;
   196   for (let i = 3; i < arguments.length; i++) {
   197     try {
   198       arguments[i].encode(data, value, options);
   199       return;
   200     } catch (e) {
   201       // Throw the last exception we get
   202       if (i == (arguments.length - 1)) {
   203         throw e;
   204       }
   206       data.offset = begin;
   207     }
   208   }
   209 }
   211 this.Octet = {
   212   /**
   213    * @param data
   214    *        A wrapped object containing raw PDU data.
   215    *
   216    * @throws RangeError if no more data is available.
   217    */
   218   decode: function(data) {
   219     if (data.offset >= data.array.length) {
   220       throw new RangeError();
   221     }
   223     return data.array[data.offset++];
   224   },
   226   /**
   227    * @param data
   228    *        A wrapped object containing raw PDU data.
   229    * @param end
   230    *        An ending offset indicating the end of octet array to read.
   231    *
   232    * @return A decoded array object.
   233    *
   234    * @throws RangeError if no enough data to read.
   235    * @throws TypeError if `data` has neither subarray() nor slice() method.
   236    */
   237   decodeMultiple: function(data, end) {
   238     if ((end < data.offset) || (end > data.array.length)) {
   239       throw new RangeError();
   240     }
   241     if (end == data.offset) {
   242       return [];
   243     }
   245     let result;
   246     if (data.array.subarray) {
   247       result = data.array.subarray(data.offset, end);
   248     } else if (data.array.slice) {
   249       result = data.array.slice(data.offset, end);
   250     } else {
   251       throw new TypeError();
   252     }
   254     data.offset = end;
   255     return result;
   256   },
   258   /**
   259    * Internal octet decoding for specific value.
   260    *
   261    * @param data
   262    *        A wrapped object containing raw PDU data.
   263    * @param expected
   264    *        Expected octet value.
   265    *
   266    * @return Expected octet value.
   267    *
   268    * @throws CodeError if read octet is not equal to expected one.
   269    */
   270   decodeEqualTo: function(data, expected) {
   271     if (this.decode(data) != expected) {
   272       throw new CodeError("Octet - decodeEqualTo: doesn't match " + expected);
   273     }
   275     return expected;
   276   },
   278   /**
   279    * @param data
   280    *        A wrapped object to store encoded raw data.
   281    * @param octet
   282    *        Octet value to be encoded.
   283    */
   284   encode: function(data, octet) {
   285     if (data.offset >= data.array.length) {
   286       data.array.push(octet);
   287       data.offset++;
   288     } else {
   289       data.array[data.offset++] = octet;
   290     }
   291   },
   293   /**
   294    * @param data
   295    *        A wrapped object to store encoded raw data.
   296    * @param octet
   297    *        An octet array object.
   298    */
   299   encodeMultiple: function(data, array) {
   300     for (let i = 0; i < array.length; i++) {
   301       this.encode(data, array[i]);
   302     }
   303   },
   304 };
   306 /**
   307  * TEXT = <any OCTET except CTLs, but including LWS>
   308  * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
   309  * LWS = [CRLF] 1*(SP|HT)
   310  * CRLF = CR LF
   311  * CR = <US-ASCII CR, carriage return (13)>
   312  * LF = <US-ASCII LF, linefeed (10)>
   313  * SP = <US-ASCII SP, space (32)>
   314  * HT = <US-ASCII HT, horizontal-tab(9)>
   315  *
   316  * @see RFC 2616 clause 2.2 Basic Rules
   317  */
   318 this.Text = {
   319   /**
   320    * @param data
   321    *        A wrapped object containing raw PDU data.
   322    *
   323    * @return Decoded character.
   324    *
   325    * @throws NullCharError if a NUL character read.
   326    * @throws CodeError if a control character read.
   327    */
   328   decode: function(data) {
   329     let code = Octet.decode(data);
   330     if ((code >= CTLS) && (code != DEL)) {
   331       return String.fromCharCode(code);
   332     }
   334     if (code == NUL) {
   335       throw new NullCharError();
   336     }
   338     if (code != CR) {
   339       throw new CodeError("Text: invalid char code " + code);
   340     }
   342     // "A CRLF is allowed in the definition of TEXT only as part of a header
   343     // field continuation. It is expected that the folding LWS will be
   344     // replaced with a single SP before interpretation of the TEXT value."
   345     // ~ RFC 2616 clause 2.2
   347     let extra;
   349     // Rethrow everything as CodeError. We had already a successful read above.
   350     try {
   351       extra = Octet.decode(data);
   352       if (extra != LF) {
   353         throw new CodeError("Text: doesn't match LWS sequence");
   354       }
   356       extra = Octet.decode(data);
   357       if ((extra != SP) && (extra != HT)) {
   358         throw new CodeError("Text: doesn't match LWS sequence");
   359       }
   360     } catch (e if e instanceof CodeError) {
   361       throw e;
   362     } catch (e) {
   363       throw new CodeError("Text: doesn't match LWS sequence");
   364     }
   366     // Let's eat as many SP|HT as possible.
   367     let begin;
   369     // Do not throw anything here. We had already matched (SP | HT).
   370     try {
   371       do {
   372         begin = data.offset;
   373         extra = Octet.decode(data);
   374       } while ((extra == SP) || (extra == HT));
   375     } catch (e) {}
   377     data.offset = begin;
   378     return " ";
   379   },
   381   /**
   382    * @param data
   383    *        A wrapped object to store encoded raw data.
   384    * @param text
   385    *        String text of one character to be encoded.
   386    * @param asciiOnly
   387    *        A boolean to decide if it's only allowed to encode ASCII (0 ~ 127).
   388    *
   389    * @throws CodeError if a control character got.
   390    */
   391   encode: function(data, text, asciiOnly) {
   392     if (!text) {
   393       throw new CodeError("Text: empty string");
   394     }
   396     let code = text.charCodeAt(0);
   397     if ((code < CTLS) || (code == DEL) || (code > 255) ||
   398         (code >= 128 && asciiOnly)) {
   399       throw new CodeError("Text: invalid char code " + code);
   400     }
   401     Octet.encode(data, code);
   402   },
   403 };
   405 this.NullTerminatedTexts = {
   406   /**
   407    * Decode internal referenced null terminated text string.
   408    *
   409    * @param data
   410    *        A wrapped object containing raw PDU data.
   411    *
   412    * @return Decoded string.
   413    */
   414   decode: function(data) {
   415     let str = "";
   416     try {
   417       // A End-of-string is also a CTL, which should cause a error.
   418       while (true) {
   419         str += Text.decode(data);
   420       }
   421     } catch (e if e instanceof NullCharError) {
   422       return str;
   423     }
   424   },
   426   /**
   427    * @param data
   428    *        A wrapped object to store encoded raw data.
   429    * @param str
   430    *        A String to be encoded.
   431    * @param asciiOnly
   432    *        A boolean to decide if it's only allowed to encode ASCII (0 ~ 127).
   433    */
   434   encode: function(data, str, asciiOnly) {
   435     if (str) {
   436       for (let i = 0; i < str.length; i++) {
   437         Text.encode(data, str.charAt(i), asciiOnly);
   438       }
   439     }
   440     Octet.encode(data, 0);
   441   },
   442 };
   444 /**
   445  * TOKEN = 1*<any CHAR except CTLs or separators>
   446  * CHAR = <any US-ASCII character (octets 0 - 127)>
   447  * SEPARATORS = ()<>@,;:\"/[]?={} SP HT
   448  *
   449  * @see RFC 2616 clause 2.2 Basic Rules
   450  */
   451 this.Token = {
   452   /**
   453    * @param data
   454    *        A wrapped object containing raw PDU data.
   455    *
   456    * @return Decoded character.
   457    *
   458    * @throws NullCharError if a NUL character read.
   459    * @throws CodeError if an invalid character read.
   460    */
   461   decode: function(data) {
   462     let code = Octet.decode(data);
   463     if ((code < ASCIIS) && (code >= CTLS)) {
   464       if ((code == HT) || (code == SP)
   465           || (code == 34) || (code == 40) || (code == 41) // ASCII "()
   466           || (code == 44) || (code == 47)                 // ASCII ,/
   467           || ((code >= 58) && (code <= 64))               // ASCII :;<=>?@
   468           || ((code >= 91) && (code <= 93))               // ASCII [\]
   469           || (code == 123) || (code == 125)) {            // ASCII {}
   470         throw new CodeError("Token: invalid char code " + code);
   471       }
   473       return String.fromCharCode(code);
   474     }
   476     if (code == NUL) {
   477       throw new NullCharError();
   478     }
   480     throw new CodeError("Token: invalid char code " + code);
   481   },
   483   /**
   484    * @param data
   485    *        A wrapped object to store encoded raw data.
   486    * @param token
   487    *        String text of one character to be encoded.
   488    *
   489    * @throws CodeError if an invalid character got.
   490    */
   491   encode: function(data, token) {
   492     if (!token) {
   493       throw new CodeError("Token: empty string");
   494     }
   496     let code = token.charCodeAt(0);
   497     if ((code < ASCIIS) && (code >= CTLS)) {
   498       if ((code == HT) || (code == SP)
   499           || (code == 34) || (code == 40) || (code == 41) // ASCII "()
   500           || (code == 44) || (code == 47)                 // ASCII ,/
   501           || ((code >= 58) && (code <= 64))               // ASCII :;<=>?@
   502           || ((code >= 91) && (code <= 93))               // ASCII [\]
   503           || (code == 123) || (code == 125)) {            // ASCII {}
   504         // Fallback to throw CodeError
   505       } else {
   506         Octet.encode(data, token.charCodeAt(0));
   507         return;
   508       }
   509     }
   511     throw new CodeError("Token: invalid char code " + code);
   512   },
   513 };
   515 /**
   516  * uric       = reserved | unreserved | escaped
   517  * reserved   = ;/?:@&=+$,
   518  * unreserved = alphanum | mark
   519  * mark       = -_.!~*'()
   520  * escaped    = % hex hex
   521  * excluded but used = #%
   522  *
   523  * Or, in decimal, they are: 33,35-59,61,63-90,95,97-122,126
   524  *
   525  * @see RFC 2396 Uniform Resource Indentifiers (URI)
   526  */
   527 this.URIC = {
   528   /**
   529    * @param data
   530    *        A wrapped object containing raw PDU data.
   531    *
   532    * @return Decoded character.
   533    *
   534    * @throws NullCharError if a NUL character read.
   535    * @throws CodeError if an invalid character read.
   536    */
   537   decode: function(data) {
   538     let code = Octet.decode(data);
   539     if (code == NUL) {
   540       throw new NullCharError();
   541     }
   543     if ((code <= CTLS) || (code >= ASCIIS) || (code == 34) || (code == 60)
   544         || (code == 62) || ((code >= 91) && (code <= 94)) || (code == 96)
   545         || ((code >= 123) && (code <= 125)) || (code == 127)) {
   546       throw new CodeError("URIC: invalid char code " + code);
   547     }
   549     return String.fromCharCode(code);
   550   },
   551 };
   553 /**
   554  * If the first character in the TEXT is in the range of 128-255, a Quote
   555  * character must precede it. Otherwise the Quote character must be omitted.
   556  * The Quote is not part of the contents.
   557  *
   558  *   Text-string = [Quote] *TEXT End-of-string
   559  *   Quote = <Octet 127>
   560  *
   561  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   562  */
   563 this.TextString = {
   564   /**
   565    * @param data
   566    *        A wrapped object containing raw PDU data.
   567    *
   568    * @return Decoded string.
   569    */
   570   decode: function(data) {
   571     let begin = data.offset;
   572     let firstCode = Octet.decode(data);
   573     if (firstCode == 127) {
   574       // Quote found, check if first char code is larger-equal than 128.
   575       begin = data.offset;
   576       try {
   577         if (Octet.decode(data) < 128) {
   578           throw new CodeError("Text-string: illegal quote found.");
   579         }
   580       } catch (e if e instanceof CodeError) {
   581         throw e;
   582       } catch (e) {
   583         throw new CodeError("Text-string: unexpected error.");
   584       }
   585     } else if (firstCode >= 128) {
   586       throw new CodeError("Text-string: invalid char code " + firstCode);
   587     }
   589     data.offset = begin;
   590     return NullTerminatedTexts.decode(data);
   591   },
   593   /**
   594    * @param data
   595    *        A wrapped object to store encoded raw data.
   596    * @param str
   597    *        A String to be encoded.
   598    * @param asciiOnly
   599    *        A boolean to decide if it's only allowed to encode ASCII (0 ~ 127).
   600    */
   601   encode: function(data, str, asciiOnly) {
   602     if (!str) {
   603       Octet.encode(data, 0);
   604       return;
   605     }
   607     let firstCharCode = str.charCodeAt(0);
   608     if (firstCharCode >= 128) {
   609       if (asciiOnly) {
   610         throw new CodeError("Text: invalid char code " + code);
   611       }
   613       Octet.encode(data, 127);
   614     }
   616     NullTerminatedTexts.encode(data, str, asciiOnly);
   617   },
   618 };
   620 /**
   621  * Token-text = Token End-of-string
   622  *
   623  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   624  */
   625 this.TokenText = {
   626   /**
   627    * @param data
   628    *        A wrapped object containing raw PDU data.
   629    *
   630    * @return Decoded string.
   631    */
   632   decode: function(data) {
   633     let str = "";
   634     try {
   635       // A End-of-string is also a CTL, which should cause a error.
   636       while (true) {
   637         str += Token.decode(data);
   638       }
   639     } catch (e if e instanceof NullCharError) {
   640       return str;
   641     }
   642   },
   644   /**
   645    * @param data
   646    *        A wrapped object to store encoded raw data.
   647    * @param str
   648    *        A String to be encoded.
   649    */
   650   encode: function(data, str) {
   651     if (str) {
   652       for (let i = 0; i < str.length; i++) {
   653         Token.encode(data, str.charAt(i));
   654       }
   655     }
   656     Octet.encode(data, 0);
   657   },
   658 };
   660 /**
   661  * The TEXT encodes an RFC2616 Quoted-string with the enclosing
   662  * quotation-marks <"> removed.
   663  *
   664  *   Quoted-string = <Octet 34> *TEXT End-of-string
   665  *
   666  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   667  */
   668 this.QuotedString = {
   669   /**
   670    * @param data
   671    *        A wrapped object containing raw PDU data.
   672    *
   673    * @return Decoded string.
   674    *
   675    * @throws CodeError if first octet read is not 0x34.
   676    */
   677   decode: function(data) {
   678     let value = Octet.decode(data);
   679     if (value != 34) {
   680       throw new CodeError("Quoted-string: not quote " + value);
   681     }
   683     return NullTerminatedTexts.decode(data);
   684   },
   686   /**
   687    * @param data
   688    *        A wrapped object to store encoded raw data.
   689    * @param str
   690    *        A String to be encoded.
   691    */
   692   encode: function(data, str) {
   693     Octet.encode(data, 34);
   694     NullTerminatedTexts.encode(data, str);
   695   },
   696 };
   698 /**
   699  * Integers in range 0-127 shall be encoded as a one octet value with the
   700  * most significant bit set to one (1xxx xxxx) and with the value in the
   701  * remaining least significant bits.
   702  *
   703  *   Short-integer = OCTET
   704  *
   705  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   706  */
   707 this.ShortInteger = {
   708   /**
   709    * @param data
   710    *        A wrapped object containing raw PDU data.
   711    *
   712    * @return Decoded integer value.
   713    *
   714    * @throws CodeError if the octet read is less than 0x80.
   715    */
   716   decode: function(data) {
   717     let value = Octet.decode(data);
   718     if (!(value & 0x80)) {
   719       throw new CodeError("Short-integer: invalid value " + value);
   720     }
   722     return (value & 0x7F);
   723   },
   725   /**
   726    * @param data
   727    *        A wrapped object to store encoded raw data.
   728    * @param value
   729    *        A numeric value to be encoded.
   730    *
   731    * @throws CodeError if the octet read is larger-equal than 0x80.
   732    */
   733   encode: function(data, value) {
   734     if (value >= 0x80) {
   735       throw new CodeError("Short-integer: invalid value " + value);
   736     }
   738     Octet.encode(data, value | 0x80);
   739   },
   740 };
   742 /**
   743  * The content octets shall be an unsigned integer value with the most
   744  * significant octet encoded first (big-endian representation). The minimum
   745  * number of octets must be used to encode the value.
   746  *
   747  *   Long-integer = Short-length Multi-octet-integer
   748  *   Short-length = <Any octet 0-30>
   749  *   Multi-octet-integer = 1*30 OCTET
   750  *
   751  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   752  */
   753 this.LongInteger = {
   754   /**
   755    * @param data
   756    *        A wrapped object containing raw PDU data.
   757    * @param length
   758    *        Number of octets to read.
   759    *
   760    * @return A decoded integer value or an octets array of max 30 elements.
   761    */
   762   decodeMultiOctetInteger: function(data, length) {
   763     if (length < 7) {
   764       // Return a integer instead of an array as possible. For a multi-octet
   765       // integer, there are only maximum 53 bits for integer in javascript. We
   766       // will get an inaccurate one beyond that. We can't neither use bitwise
   767       // operation here, for it will be limited in 32 bits.
   768       let value = 0;
   769       while (length--) {
   770         value = value * 256 + Octet.decode(data);
   771       }
   772       return value;
   773     }
   775     return Octet.decodeMultiple(data, data.offset + length);
   776   },
   778   /**
   779    * @param data
   780    *        A wrapped object containing raw PDU data.
   781    *
   782    * @return A decoded integer value or an octets array of max 30 elements.
   783    *
   784    * @throws CodeError if the length read is not in 1..30.
   785    */
   786   decode: function(data) {
   787     let length = Octet.decode(data);
   788     if ((length < 1) || (length > 30)) {
   789       throw new CodeError("Long-integer: invalid length " + length);
   790     }
   792     return this.decodeMultiOctetInteger(data, length);
   793   },
   795   /**
   796    * @param data
   797    *        A wrapped object to store encoded raw data.
   798    * @param numOrArray
   799    *        An octet array of less-equal than 30 elements or an integer
   800    *        greater-equal than 128.
   801    */
   802   encode: function(data, numOrArray) {
   803     if (typeof numOrArray === "number") {
   804       let num = numOrArray;
   805       if (num >= 0x1000000000000) {
   806         throw new CodeError("Long-integer: number too large " + num);
   807       }
   809       let stack = [];
   810       do {
   811         stack.push(Math.floor(num % 256));
   812         num = Math.floor(num / 256);
   813       } while (num);
   815       Octet.encode(data, stack.length);
   816       while (stack.length) {
   817         Octet.encode(data, stack.pop());
   818       }
   819       return;
   820     }
   822     let array = numOrArray;
   823     if ((array.length < 1) || (array.length > 30)) {
   824       throw new CodeError("Long-integer: invalid length " + array.length);
   825     }
   827     Octet.encode(data, array.length);
   828     Octet.encodeMultiple(data, array);
   829   },
   830 };
   832 /**
   833  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   834  */
   835 this.UintVar = {
   836   /**
   837    * @param data
   838    *        A wrapped object containing raw PDU data.
   839    *
   840    * @return Decoded integer value.
   841    */
   842   decode: function(data) {
   843     let value = Octet.decode(data);
   844     let result = value & 0x7F;
   845     while (value & 0x80) {
   846       value = Octet.decode(data);
   847       result = result * 128 + (value & 0x7F);
   848     }
   850     return result;
   851   },
   853   /**
   854    * @param data
   855    *        A wrapped object to store encoded raw data.
   856    * @param value
   857    *        An integer value.
   858    */
   859   encode: function(data, value) {
   860     if (value < 0) {
   861       throw new CodeError("UintVar: invalid value " + value);
   862     }
   864     let stack = [];
   865     while (value >= 128) {
   866       stack.push(Math.floor(value % 128));
   867       value = Math.floor(value / 128);
   868     }
   870     while (stack.length) {
   871       Octet.encode(data, value | 0x80);
   872       value = stack.pop();
   873     }
   874     Octet.encode(data, value);
   875   },
   876 };
   878 /**
   879  * This encoding is used for token values, which have no well-known binary
   880  * encoding, or when the assigned number of the well-known encoding is small
   881  * enough to fit into Short-Integer. We change Extension-Media from
   882  * NullTerminatedTexts to TextString because of Bug 823816.
   883  *
   884  *   Constrained-encoding = Extension-Media | Short-integer
   885  *   Extension-Media = TextString
   886  *
   887  * @see WAP-230-WSP-20010705-a clause 8.4.2.1
   888  * @see https://bugzilla.mozilla.org/show_bug.cgi?id=823816
   889  */
   890 this.ConstrainedEncoding = {
   891   /**
   892    * @param data
   893    *        A wrapped object containing raw PDU data.
   894    *
   895    * @return Decode integer value or string.
   896    */
   897   decode: function(data) {
   898     return decodeAlternatives(data, null, TextString, ShortInteger);
   899   },
   901   /**
   902    * @param data
   903    *        A wrapped object to store encoded raw data.
   904    * @param value
   905    *        An integer or a string value.
   906    */
   907   encode: function(data, value) {
   908     if (typeof value == "number") {
   909       ShortInteger.encode(data, value);
   910     } else {
   911       TextString.encode(data, value);
   912     }
   913   },
   914 };
   916 /**
   917  * Value-length = Short-length | (Length-quote Length)
   918  * Short-length = <Any octet 0-30>
   919  * Length-quote = <Octet 31>
   920  * Length = Uintvar-integer
   921  *
   922  * @see WAP-230-WSP-20010705-a clause 8.4.2.2
   923  */
   924 this.ValueLength = {
   925   /**
   926    * @param data
   927    *        A wrapped object containing raw PDU data.
   928    *
   929    * @return Decoded integer value.
   930    *
   931    * @throws CodeError if the first octet read is larger than 31.
   932    */
   933   decode: function(data) {
   934     let value = Octet.decode(data);
   935     if (value <= 30) {
   936       return value;
   937     }
   939     if (value == 31) {
   940       return UintVar.decode(data);
   941     }
   943     throw new CodeError("Value-length: invalid value " + value);
   944   },
   946   /**
   947    * @param data
   948    *        A wrapped object to store encoded raw data.
   949    * @param value
   950    */
   951   encode: function(data, value) {
   952     if (value <= 30) {
   953       Octet.encode(data, value);
   954     } else {
   955       Octet.encode(data, 31);
   956       UintVar.encode(data, value);
   957     }
   958   },
   959 };
   961 /**
   962  * No-value = <Octet 0>
   963  *
   964  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
   965  */
   966 this.NoValue = {
   967   /**
   968    * @param data
   969    *        A wrapped object containing raw PDU data.
   970    *
   971    * @return Always returns null.
   972    */
   973   decode: function(data) {
   974     Octet.decodeEqualTo(data, 0);
   975     return null;
   976   },
   978   /**
   979    * @param data
   980    *        A wrapped object to store encoded raw data.
   981    * @param value
   982    *        A null or undefined value.
   983    */
   984   encode: function(data, value) {
   985     if (value != null) {
   986       throw new CodeError("No-value: invalid value " + value);
   987     }
   988     Octet.encode(data, 0);
   989   },
   990 };
   992 /**
   993  * Text-value = No-value | Token-text | Quoted-string
   994  *
   995  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
   996  */
   997 this.TextValue = {
   998   /**
   999    * @param data
  1000    *        A wrapped object containing raw PDU data.
  1002    * @return Decoded string or null for No-value.
  1003    */
  1004   decode: function(data) {
  1005     return decodeAlternatives(data, null, NoValue, TokenText, QuotedString);
  1006   },
  1008   /**
  1009    * @param data
  1010    *        A wrapped object to store encoded raw data.
  1011    * @param text
  1012    *        A null or undefined or text string.
  1013    */
  1014   encode: function(data, text) {
  1015     encodeAlternatives(data, text, null, NoValue, TokenText, QuotedString);
  1016   },
  1017 };
  1019 /**
  1020  * Integer-Value = Short-integer | Long-integer
  1022  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
  1023  */
  1024 this.IntegerValue = {
  1025   /**
  1026    * @param data
  1027    *        A wrapped object containing raw PDU data.
  1029    * @return Decoded integer value or array of octets.
  1030    */
  1031   decode: function(data) {
  1032     return decodeAlternatives(data, null, ShortInteger, LongInteger);
  1033   },
  1035   /**
  1036    * @param data
  1037    *        A wrapped object to store encoded raw data.
  1038    * @param value
  1039    *        An integer value or an octet array of less-equal than 31 elements.
  1040    */
  1041   encode: function(data, value) {
  1042     if (typeof value === "number") {
  1043       encodeAlternatives(data, value, null, ShortInteger, LongInteger);
  1044     } else if (Array.isArray(value) || (value instanceof Uint8Array)) {
  1045       LongInteger.encode(data, value);
  1046     } else {
  1047       throw new CodeError("Integer-Value: invalid value type");
  1049   },
  1050 };
  1052 /**
  1053  * The encoding of dates shall be done in number of seconds from
  1054  * 1970-01-01, 00:00:00 GMT.
  1056  *   Date-value = Long-integer
  1058  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
  1059  */
  1060 this.DateValue = {
  1061   /**
  1062    * @param data
  1063    *        A wrapped object containing raw PDU data.
  1065    * @return A Date object.
  1066    */
  1067   decode: function(data) {
  1068     let numOrArray = LongInteger.decode(data);
  1069     let seconds;
  1070     if (typeof numOrArray == "number") {
  1071       seconds = numOrArray;
  1072     } else {
  1073       seconds = 0;
  1074       for (let i = 0; i < numOrArray.length; i++) {
  1075         seconds = seconds * 256 + numOrArray[i];
  1079     return new Date(seconds * 1000);
  1080   },
  1082   /**
  1083    * @param data
  1084    *        A wrapped object to store encoded raw data.
  1085    * @param date
  1086    *        A Date object.
  1087    */
  1088   encode: function(data, date) {
  1089     let seconds = date.getTime() / 1000;
  1090     if (seconds < 0) {
  1091       throw new CodeError("Date-value: negative seconds " + seconds);
  1094     LongInteger.encode(data, seconds);
  1095   },
  1096 };
  1098 /**
  1099  * Delta-seconds-value = Integer-value
  1101  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
  1102  */
  1103 this.DeltaSecondsValue = IntegerValue;
  1105 /**
  1106  * Quality factor 0 and quality factors with one or two decimal digits are
  1107  * encoded into 1-100; three digits ones into 101-1099.
  1109  *   Q-value = 1*2 OCTET
  1111  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
  1112  */
  1113 this.QValue = {
  1114   /**
  1115    * @param data
  1116    *        A wrapped object containing raw PDU data.
  1118    * @return Decoded integer value of 1..1099.
  1120    * @throws CodeError if decoded UintVar is not in range 1..1099.
  1121    */
  1122   decode: function(data) {
  1123     let value = UintVar.decode(data);
  1124     if (value > 0) {
  1125       if (value <= 100) {
  1126         return (value - 1) / 100.0;
  1128       if (value <= 1099) {
  1129         return (value - 100) / 1000.0;
  1133     throw new CodeError("Q-value: invalid value " + value);
  1134   },
  1136   /**
  1137    * @param data
  1138    *        A wrapped object to store encoded raw data.
  1139    * @param value
  1140    *        An integer within the range 1..1099.
  1141    */
  1142   encode: function(data, value) {
  1143     if ((value < 0) || (value >= 1)) {
  1144       throw new CodeError("Q-value: invalid value " + value);
  1147     value *= 1000;
  1148     if ((value % 10) == 0) {
  1149       // Two digits only.
  1150       UintVar.encode(data, Math.floor(value / 10 + 1));
  1151     } else {
  1152       // Three digits.
  1153       UintVar.encode(data, Math.floor(value + 100));
  1155   },
  1156 };
  1158 /**
  1159  * The three most significant bits of the Short-integer value are interpreted
  1160  * to encode a major version number in the range 1-7, and the four least
  1161  * significant bits contain a minor version number in the range 0-14. If
  1162  * there is only a major version number, this is encoded by placing the value
  1163  * 15 in the four least significant bits.
  1165  *   Version-value = Short-integer | Text-string
  1167  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
  1168  */
  1169 this.VersionValue = {
  1170   /**
  1171    * @param data
  1172    *        A wrapped object containing raw PDU data.
  1174    * @return Binary encoded version number.
  1175    */
  1176   decode: function(data) {
  1177     let begin = data.offset;
  1178     let value;
  1179     try {
  1180       value = ShortInteger.decode(data);
  1181       if ((value >= 0x10) && (value < 0x80)) {
  1182         return value;
  1185       throw new CodeError("Version-value: invalid value " + value);
  1186     } catch (e) {}
  1188     data.offset = begin;
  1190     let str = TextString.decode(data);
  1191     if (!str.match(/^[1-7](\.1?\d)?$/)) {
  1192       throw new CodeError("Version-value: invalid value " + str);
  1195     let major = str.charCodeAt(0) - 0x30;
  1196     let minor = 0x0F;
  1197     if (str.length > 1) {
  1198       minor = str.charCodeAt(2) - 0x30;
  1199       if (str.length > 3) {
  1200         minor = 10 + (str.charCodeAt(3) - 0x30);
  1201         if (minor > 14) {
  1202           throw new CodeError("Version-value: invalid minor " + minor);
  1207     return major << 4 | minor;
  1208   },
  1210   /**
  1211    * @param data
  1212    *        A wrapped object to store encoded raw data.
  1213    * @param version
  1214    *        A binary encoded version number.
  1215    */
  1216   encode: function(data, version) {
  1217     if ((version < 0x10) || (version >= 0x80)) {
  1218       throw new CodeError("Version-value: invalid version " + version);
  1221     ShortInteger.encode(data, version);
  1222   },
  1223 };
  1225 /**
  1226  * URI value should be encoded per [RFC2616], but service user may use a
  1227  * different format.
  1229  *   Uri-value = Text-string
  1231  * @see WAP-230-WSP-20010705-a clause 8.4.2.3
  1232  * @see RFC 2616 clause 2.2 Basic Rules
  1233  */
  1234 this.UriValue = {
  1235   /**
  1236    * @param data
  1237    *        A wrapped object containing raw PDU data.
  1239    * @return Decoded uri string.
  1240    */
  1241   decode: function(data) {
  1242     let str = "";
  1243     try {
  1244       // A End-of-string is also a CTL, which should cause a error.
  1245       while (true) {
  1246         str += URIC.decode(data);
  1248     } catch (e if e instanceof NullCharError) {
  1249       return str;
  1251   },
  1252 };
  1254 /**
  1255  * Internal coder for "type" parameter.
  1257  *   Type-value = Constrained-encoding
  1259  * @see WAP-230-WSP-20010705-a table 38
  1260  */
  1261 this.TypeValue = {
  1262   /**
  1263    * @param data
  1264    *        A wrapped object containing raw PDU data.
  1266    * @return Decoded content type string.
  1267    */
  1268   decode: function(data) {
  1269     let numOrStr = ConstrainedEncoding.decode(data);
  1270     if (typeof numOrStr == "string") {
  1271       return numOrStr.toLowerCase();
  1274     let number = numOrStr;
  1275     let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number];
  1276     if (!entry) {
  1277       throw new NotWellKnownEncodingError(
  1278         "Constrained-media: not well known media " + number);
  1281     return entry.type;
  1282   },
  1284   /**
  1285    * @param data
  1286    *        A wrapped object to store encoded raw data.
  1287    * @param type
  1288    *        A content type string.
  1289    */
  1290   encode: function(data, type) {
  1291     let entry = WSP_WELL_KNOWN_CONTENT_TYPES[type.toLowerCase()];
  1292     if (entry) {
  1293       ConstrainedEncoding.encode(data, entry.number);
  1294     } else {
  1295       ConstrainedEncoding.encode(data, type);
  1297   },
  1298 };
  1300 /**
  1301  * Parameter = Typed-parameter | Untyped-parameter
  1303  * For Typed-parameters, the actual expected type of the value is implied by
  1304  * the well-known parameter. In addition to the expected type, there may be no
  1305  * value. If the value cannot be encoded using expected type, it shall be
  1306  * encoded as text.
  1308  *   Typed-parameter = Well-known-parameter-token Typed-value
  1309  *   Well-known-parameter-token = Integer-value
  1310  *   Typed-value = Compact-value | Text-value
  1311  *   Compact-value = Integer-value | Date-value | Delta-seconds-value | Q-value
  1312  *                   | Version-value | Uri-value
  1314  * For Untyped-parameters, the type of the value is unknown, but is shall be
  1315  * encoded as an integer, if that is possible.
  1317  *   Untyped-parameter = Token-text Untyped-value
  1318  *   Untyped-value = Integer-value | Text-value
  1320  * @see WAP-230-WSP-20010705-a clause 8.4.2.4
  1321  */
  1322 this.Parameter = {
  1323   /**
  1324    * @param data
  1325    *        A wrapped object containing raw PDU data.
  1327    * @return A decoded object containing `name` and `value` properties or null
  1328    *         if something wrong. The `name` property must be a string, but the
  1329    *         `value` property can be many different types depending on `name`.
  1331    * @throws CodeError if decoded IntegerValue is an array.
  1332    * @throws NotWellKnownEncodingError if decoded well-known parameter number
  1333    *         is not registered or supported.
  1334    */
  1335   decodeTypedParameter: function(data) {
  1336     let numOrArray = IntegerValue.decode(data);
  1337     // `decodeIntegerValue` can return a array, which doesn't apply here.
  1338     if (typeof numOrArray != "number") {
  1339       throw new CodeError("Typed-parameter: invalid integer type");
  1342     let number = numOrArray;
  1343     let param = WSP_WELL_KNOWN_PARAMS[number];
  1344     if (!param) {
  1345       throw new NotWellKnownEncodingError(
  1346         "Typed-parameter: not well known parameter " + number);
  1349     let begin = data.offset, value;
  1350     try {
  1351       // Althought Text-string is not included in BNF of Compact-value, but
  1352       // some service provider might still pass a less-strict text form and
  1353       // cause a unexpected CodeError raised. For example, the `start`
  1354       // parameter expects its value of Text-value, but service provider might
  1355       // gives "<smil>", which contains illegal characters "<" and ">".
  1356       value = decodeAlternatives(data, null,
  1357                                  param.coder, TextValue, TextString);
  1358     } catch (e) {
  1359       data.offset = begin;
  1361       // Skip current parameter.
  1362       value = skipValue(data);
  1363       debug("Skip malformed typed parameter: "
  1364             + JSON.stringify({name: param.name, value: value}));
  1366       return null;
  1369     return {
  1370       name: param.name,
  1371       value: value,
  1372     };
  1373   },
  1375   /**
  1376    * @param data
  1377    *        A wrapped object containing raw PDU data.
  1379    * @return A decoded object containing `name` and `value` properties or null
  1380    *         if something wrong. The `name` property must be a string, but the
  1381    *         `value` property can be many different types depending on `name`.
  1382    */
  1383   decodeUntypedParameter: function(data) {
  1384     let name = TokenText.decode(data);
  1386     let begin = data.offset, value;
  1387     try {
  1388       value = decodeAlternatives(data, null, IntegerValue, TextValue);
  1389     } catch (e) {
  1390       data.offset = begin;
  1392       // Skip current parameter.
  1393       value = skipValue(data);
  1394       debug("Skip malformed untyped parameter: "
  1395             + JSON.stringify({name: name, value: value}));
  1397       return null;
  1400     return {
  1401       name: name.toLowerCase(),
  1402       value: value,
  1403     };
  1404   },
  1406   /**
  1407    * @param data
  1408    *        A wrapped object containing raw PDU data.
  1410    * @return A decoded object containing `name` and `value` properties or null
  1411    *         if something wrong. The `name` property must be a string, but the
  1412    *         `value` property can be many different types depending on `name`.
  1413    */
  1414   decode: function(data) {
  1415     let begin = data.offset;
  1416     try {
  1417       return this.decodeTypedParameter(data);
  1418     } catch (e) {
  1419       data.offset = begin;
  1420       return this.decodeUntypedParameter(data);
  1422   },
  1424   /**
  1425    * @param data
  1426    *        A wrapped object containing raw PDU data.
  1427    * @param end
  1428    *        Ending offset of following parameters.
  1430    * @return An array of decoded objects.
  1431    */
  1432   decodeMultiple: function(data, end) {
  1433     let params = null, param;
  1435     while (data.offset < end) {
  1436       try {
  1437         param = this.decode(data);
  1438       } catch (e) {
  1439         break;
  1441       if (param) {
  1442         if (!params) {
  1443           params = {};
  1445         params[param.name] = param.value;
  1449     return params;
  1450   },
  1452   /**
  1453    * @param data
  1454    *        A wrapped object to store encoded raw data.
  1455    * @param param
  1456    *        An object containing `name` and `value` properties.
  1457    */
  1458   encodeTypedParameter: function(data, param) {
  1459     let entry = WSP_WELL_KNOWN_PARAMS[param.name.toLowerCase()];
  1460     if (!entry) {
  1461       throw new NotWellKnownEncodingError(
  1462         "Typed-parameter: not well known parameter " + param.name);
  1465     IntegerValue.encode(data, entry.number);
  1466     encodeAlternatives(data, param.value, null,
  1467                        entry.coder, TextValue, TextString);
  1468   },
  1470   /**
  1471    * @param data
  1472    *        A wrapped object to store encoded raw data.
  1473    * @param param
  1474    *        An object containing `name` and `value` properties.
  1475    */
  1476   encodeUntypedParameter: function(data, param) {
  1477     TokenText.encode(data, param.name);
  1478     encodeAlternatives(data, param.value, null, IntegerValue, TextValue);
  1479   },
  1481   /**
  1482    * @param data
  1483    *        A wrapped object to store encoded raw data.
  1484    * @param param
  1485    *        An array of parameter objects.
  1486    */
  1487   encodeMultiple: function(data, params) {
  1488     for (let name in params) {
  1489       this.encode(data, {name: name, value: params[name]});
  1491   },
  1493   /**
  1494    * @param data
  1495    *        A wrapped object to store encoded raw data.
  1496    * @param param
  1497    *        An object containing `name` and `value` properties.
  1498    */
  1499   encode: function(data, param) {
  1500     let begin = data.offset;
  1501     try {
  1502       this.encodeTypedParameter(data, param);
  1503     } catch (e) {
  1504       data.offset = begin;
  1505       this.encodeUntypedParameter(data, param);
  1507   },
  1508 };
  1510 /**
  1511  * Header = Message-header | Shift-sequence
  1512  * Message-header = Well-known-header | Application-header
  1514  * @see WAP-230-WSP-20010705-a clause 8.4.2.6
  1515  */
  1516 this.Header = {
  1517   /**
  1518    * @param data
  1519    *        A wrapped object containing raw PDU data.
  1521    * @return A decoded object containing `name` and `value` properties or null
  1522    *         in case of a failed parsing. The `name` property must be a string,
  1523    *         but the `value` property can be many different types depending on
  1524    *         `name`.
  1525    */
  1526   decodeMessageHeader: function(data) {
  1527     return decodeAlternatives(data, null, WellKnownHeader, ApplicationHeader);
  1528   },
  1530   /**
  1531    * @param data
  1532    *        A wrapped object containing raw PDU data.
  1534    * @return A decoded object containing `name` and `value` properties or null
  1535    *         in case of a failed parsing. The `name` property must be a string,
  1536    *         but the `value` property can be many different types depending on
  1537    *         `name`.
  1538    */
  1539   decode: function(data) {
  1540     // TODO: support header code page shift-sequence
  1541     return this.decodeMessageHeader(data);
  1542   },
  1544   encodeMessageHeader: function(data, header) {
  1545     encodeAlternatives(data, header, null, WellKnownHeader, ApplicationHeader);
  1546   },
  1548   /**
  1549    * @param data
  1550    *        A wrapped object to store encoded raw data.
  1551    * @param header
  1552    *        An object containing two attributes: a string-typed `name` and a
  1553    *        `value` of arbitrary type.
  1554    */
  1555   encode: function(data, header) {
  1556     // TODO: support header code page shift-sequence
  1557     this.encodeMessageHeader(data, header);
  1558   },
  1559 };
  1561 /**
  1562  * Well-known-header = Well-known-field-name Wap-value
  1563  * Well-known-field-name = Short-integer
  1565  * @see WAP-230-WSP-20010705-a clause 8.4.2.6
  1566  */
  1567 this.WellKnownHeader = {
  1568   /**
  1569    * @param data
  1570    *        A wrapped object containing raw PDU data.
  1572    * @return A decoded object containing `name` and `value` properties or null
  1573    *         in case of a failed parsing. The `name` property must be a string,
  1574    *         but the `value` property can be many different types depending on
  1575    *         `name`.
  1577    * @throws NotWellKnownEncodingError if decoded well-known header field
  1578    *         number is not registered or supported.
  1579    */
  1580   decode: function(data) {
  1581     let index = ShortInteger.decode(data);
  1583     let entry = WSP_HEADER_FIELDS[index];
  1584     if (!entry) {
  1585       throw new NotWellKnownEncodingError(
  1586         "Well-known-header: not well known header " + index);
  1589     let begin = data.offset, value;
  1590     try {
  1591       value = decodeAlternatives(data, null, entry.coder, TextValue);
  1592     } catch (e) {
  1593       data.offset = begin;
  1595       value = skipValue(data);
  1596       debug("Skip malformed well known header(" + index + "): "
  1597             + JSON.stringify({name: entry.name, value: value}));
  1599       return null;
  1602     return {
  1603       name: entry.name,
  1604       value: value,
  1605     };
  1606   },
  1608   /**
  1609    * @param data
  1610    *        A wrapped object to store encoded raw data.
  1611    * @param header
  1612    *        An object containing two attributes: a string-typed `name` and a
  1613    *        `value` of arbitrary type.
  1614    */
  1615   encode: function(data, header) {
  1616     let entry = WSP_HEADER_FIELDS[header.name.toLowerCase()];
  1617     if (!entry) {
  1618       throw new NotWellKnownEncodingError(
  1619         "Well-known-header: not well known header " + header.name);
  1622     ShortInteger.encode(data, entry.number);
  1623     encodeAlternatives(data, header.value, null, entry.coder, TextValue);
  1624   },
  1625 };
  1627 /**
  1628  * Application-header = Token-text Application-specific-value
  1629  * Application-specific-value = Text-string
  1631  * @see WAP-230-WSP-20010705-a clause 8.4.2.6
  1632  */
  1633 this.ApplicationHeader = {
  1634   /**
  1635    * @param data
  1636    *        A wrapped object containing raw PDU data.
  1638    * @return A decoded object containing `name` and `value` properties or null
  1639    *         in case of a failed parsing. The `name` property must be a string,
  1640    *         but the `value` property can be many different types depending on
  1641    *         `name`.
  1642    */
  1643   decode: function(data) {
  1644     let name = TokenText.decode(data);
  1646     let begin = data.offset, value;
  1647     try {
  1648       value = TextString.decode(data);
  1649     } catch (e) {
  1650       data.offset = begin;
  1652       value = skipValue(data);
  1653       debug("Skip malformed application header: "
  1654             + JSON.stringify({name: name, value: value}));
  1656       return null;
  1659     return {
  1660       name: name.toLowerCase(),
  1661       value: value,
  1662     };
  1663   },
  1665   /**
  1666    * @param data
  1667    *        A wrapped object to store encoded raw data.
  1668    * @param header
  1669    *        An object containing two attributes: a string-typed `name` and a
  1670    *        `value` of arbitrary type.
  1672    * @throws CodeError if got an empty header name.
  1673    */
  1674   encode: function(data, header) {
  1675     if (!header.name) {
  1676       throw new CodeError("Application-header: empty header name");
  1679     TokenText.encode(data, header.name);
  1680     TextString.encode(data, header.value);
  1681   },
  1682 };
  1684 /**
  1685  * Field-name = Token-text | Well-known-field-name
  1686  * Well-known-field-name = Short-integer
  1688  * @see WAP-230-WSP-20010705-a clause 8.4.2.6
  1689  */
  1690 this.FieldName = {
  1691   /**
  1692    * @param data
  1693    *        A wrapped object containing raw PDU data.
  1695    * @return A field name string.
  1697    * @throws NotWellKnownEncodingError if decoded well-known header field
  1698    *         number is not registered or supported.
  1699    */
  1700   decode: function(data) {
  1701     let begin = data.offset;
  1702     try {
  1703       return TokenText.decode(data).toLowerCase();
  1704     } catch (e) {}
  1706     data.offset = begin;
  1708     let number = ShortInteger.decode(data);
  1709     let entry = WSP_HEADER_FIELDS[number];
  1710     if (!entry) {
  1711       throw new NotWellKnownEncodingError(
  1712         "Field-name: not well known encoding " + number);
  1715     return entry.name;
  1716   },
  1718   /**
  1719    * @param data
  1720    *        A wrapped object to store encoded raw data.
  1721    * @param name
  1722    *        A field name string.
  1723    */
  1724   encode: function(data, name) {
  1725     let entry = WSP_HEADER_FIELDS[name.toLowerCase()];
  1726     if (entry) {
  1727       ShortInteger.encode(data, entry.number);
  1728     } else {
  1729       TokenText.encode(data, name);
  1731   },
  1732 };
  1734 /**
  1735  * Accept-charset-value = Constrained-charset | Accept-charset-general-form
  1736  * Constrained-charset = Any-charset | Constrained-encoding
  1737  * Any-charset = <Octet 128>
  1738  * Accept-charset-general-form = Value-length (Well-known-charset | Token-text) [Q-value]
  1740  * @see WAP-230-WSP-20010705-a clause 8.4.2.8
  1741  */
  1742 this.AcceptCharsetValue = {
  1743   /**
  1744    * @param data
  1745    *        A wrapped object containing raw PDU data.
  1747    * @return A object with a property `charset` of string "*".
  1748    */
  1749   decodeAnyCharset: function(data) {
  1750     Octet.decodeEqualTo(data, 128);
  1751     return {charset: "*"};
  1752   },
  1754   /**
  1755    * @param data
  1756    *        A wrapped object containing raw PDU data.
  1758    * @return A object with a string property `charset` and a optional integer
  1759    *         property `q`.
  1761    * @throws NotWellKnownEncodingError if decoded well-known charset number is
  1762    *         not registered or supported.
  1763    */
  1764   decodeConstrainedCharset: function(data) {
  1765     let begin = data.offset;
  1766     try {
  1767       return this.decodeAnyCharset(data);
  1768     } catch (e) {}
  1770     data.offset = begin;
  1772     let numOrStr = ConstrainedEncoding.decode(data);
  1773     if (typeof numOrStr == "string") {
  1774       return {charset: numOrStr};
  1777     let charset = numOrStr;
  1778     let entry = WSP_WELL_KNOWN_CHARSETS[charset];
  1779     if (!entry) {
  1780       throw new NotWellKnownEncodingError(
  1781         "Constrained-charset: not well known charset: " + charset);
  1784     return {charset: entry.name};
  1785   },
  1787   /**
  1788    * @param data
  1789    *        A wrapped object containing raw PDU data.
  1791    * @return A object with a string property `charset` and a optional integer
  1792    *         property `q`.
  1793    */
  1794   decodeAcceptCharsetGeneralForm: function(data) {
  1795     let length = ValueLength.decode(data);
  1797     let begin = data.offset;
  1798     let end = begin + length;
  1800     let result;
  1801     try {
  1802       result = WellKnownCharset.decode(data);
  1803     } catch (e) {
  1804       data.offset = begin;
  1806       result = {charset: TokenText.decode(data)};
  1807       if (data.offset < end) {
  1808         result.q = QValue.decode(data);
  1812     if (data.offset != end) {
  1813       data.offset = end;
  1816     return result;
  1817   },
  1819   /**
  1820    * @param data
  1821    *        A wrapped object containing raw PDU data.
  1823    * @return A object with a string property `charset` and a optional integer
  1824    *         property `q`.
  1825    */
  1826   decode: function(data) {
  1827     let begin = data.offset;
  1828     try {
  1829       return this.decodeConstrainedCharset(data);
  1830     } catch (e) {
  1831       data.offset = begin;
  1832       return this.decodeAcceptCharsetGeneralForm(data);
  1834   },
  1836   /**
  1837    * @param data
  1838    *        A wrapped object to store encoded raw data.
  1839    * @param value
  1840    *        An object with a string property `charset`.
  1841    */
  1842   encodeAnyCharset: function(data, value) {
  1843     if (!value || !value.charset || (value.charset === "*")) {
  1844       Octet.encode(data, 128);
  1845       return;
  1848     throw new CodeError("Any-charset: invalid value " + value);
  1849   },
  1850 };
  1852 /**
  1853  * Well-known-charset = Any-charset | Integer-value
  1855  * @see WAP-230-WSP-20010705-a clause 8.4.2.8
  1856  */
  1857 this.WellKnownCharset = {
  1858   /**
  1859    * @param data
  1860    *        A wrapped object containing raw PDU data.
  1862    * @return A object with a string property `charset`.
  1864    * @throws CodeError if decoded charset number is an array.
  1865    * @throws NotWellKnownEncodingError if decoded well-known charset number
  1866    *         is not registered or supported.
  1867    */
  1868   decode: function(data) {
  1869     let begin = data.offset;
  1871     try {
  1872       return AcceptCharsetValue.decodeAnyCharset(data);
  1873     } catch (e) {}
  1875     data.offset = begin;
  1877     // `IntegerValue.decode` can return a array, which doesn't apply here.
  1878     let numOrArray = IntegerValue.decode(data);
  1879     if (typeof numOrArray != "number") {
  1880       throw new CodeError("Well-known-charset: invalid integer type");
  1883     let charset = numOrArray;
  1884     let entry = WSP_WELL_KNOWN_CHARSETS[charset];
  1885     if (!entry) {
  1886       throw new NotWellKnownEncodingError(
  1887         "Well-known-charset: not well known charset " + charset);
  1890     return {charset: entry.name};
  1891   },
  1893   /**
  1894    * @param data
  1895    *        A wrapped object to store encoded raw data.
  1896    * @param value
  1897    */
  1898   encode: function(data, value) {
  1899     let begin = data.offset;
  1900     try {
  1901       AcceptCharsetValue.encodeAnyCharset(data, value);
  1902       return;
  1903     } catch (e) {}
  1905     data.offset = begin;
  1906     let entry = WSP_WELL_KNOWN_CHARSETS[value.charset.toLowerCase()];
  1907     if (!entry) {
  1908       throw new NotWellKnownEncodingError(
  1909         "Well-known-charset: not well known charset " + value.charset);
  1912     IntegerValue.encode(data, entry.number);
  1913   },
  1914 };
  1916 /**
  1917  * The short form of the Content-type-value MUST only be used when the
  1918  * well-known media is in the range of 0-127 or a text string. In all other
  1919  * cases the general form MUST be used.
  1921  *   Content-type-value = Constrained-media | Content-general-form
  1922  *   Constrained-media = Constrained-encoding
  1923  *   Content-general-form = Value-length Media-type
  1924  *   Media-type = Media *(Parameter)
  1925  *   Media = Well-known-media | Extension-Media
  1926  *   Well-known-media = Integer-value
  1927  *   Extension-Media = *TEXT End-of-string
  1929  * @see WAP-230-WSP-20010705-a clause 8.4.2.24
  1930  */
  1931 this.ContentTypeValue = {
  1932   /**
  1933    * @param data
  1934    *        A wrapped object containing raw PDU data.
  1936    * @return A decoded object containing `media` and `params` properties or
  1937    *         null in case of a failed parsing. The `media` property must be a
  1938    *         string, and the `params` property is always null.
  1940    * @throws NotWellKnownEncodingError if decoded well-known content type number
  1941    *         is not registered or supported.
  1942    */
  1943   decodeConstrainedMedia: function(data) {
  1944     return {
  1945       media: TypeValue.decode(data),
  1946       params: null,
  1947     };
  1948   },
  1950   /**
  1951    * @param data
  1952    *        A wrapped object containing raw PDU data.
  1954    * @return Decode string.
  1956    * @throws CodeError if decoded content type number is an array.
  1957    * @throws NotWellKnownEncodingError if decoded well-known content type
  1958    *         number is not registered or supported.
  1959    */
  1960   decodeMedia: function(data) {
  1961     let begin = data.offset, number;
  1962     try {
  1963       number = IntegerValue.decode(data);
  1964     } catch (e) {
  1965       data.offset = begin;
  1966       return NullTerminatedTexts.decode(data).toLowerCase();
  1969     // `decodeIntegerValue` can return a array, which doesn't apply here.
  1970     if (typeof number != "number") {
  1971       throw new CodeError("Media: invalid integer type");
  1974     let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number];
  1975     if (!entry) {
  1976       throw new NotWellKnownEncodingError("Media: not well known media " + number);
  1979     return entry.type;
  1980   },
  1982   /**
  1983    * @param data
  1984    *        A wrapped object containing raw PDU data.
  1985    * @param end
  1986    *        Ending offset of the Media-type value.
  1988    * @return A decoded object containing `media` and `params` properties or
  1989    *         null in case of a failed parsing. The `media` property must be a
  1990    *         string, and the `params` property is a hash map from a string to
  1991    *         an value of unspecified type.
  1992    */
  1993   decodeMediaType: function(data, end) {
  1994     let media = this.decodeMedia(data);
  1995     let params = Parameter.decodeMultiple(data, end);
  1997     return {
  1998       media: media,
  1999       params: params,
  2000     };
  2001   },
  2003   /**
  2004    * @param data
  2005    *        A wrapped object containing raw PDU data.
  2007    * @return A decoded object containing `media` and `params` properties or
  2008    *         null in case of a failed parsing. The `media` property must be a
  2009    *         string, and the `params` property is null or a hash map from a
  2010    *         string to an value of unspecified type.
  2011    */
  2012   decodeContentGeneralForm: function(data) {
  2013     let length = ValueLength.decode(data);
  2014     let end = data.offset + length;
  2016     let value = this.decodeMediaType(data, end);
  2018     if (data.offset != end) {
  2019       data.offset = end;
  2022     return value;
  2023   },
  2025   /**
  2026    * @param data
  2027    *        A wrapped object containing raw PDU data.
  2029    * @return A decoded object containing `media` and `params` properties or
  2030    *         null in case of a failed parsing. The `media` property must be a
  2031    *         string, and the `params` property is null or a hash map from a
  2032    *         string to an value of unspecified type.
  2033    */
  2034   decode: function(data) {
  2035     let begin = data.offset;
  2037     try {
  2038       return this.decodeConstrainedMedia(data);
  2039     } catch (e) {
  2040       data.offset = begin;
  2041       return this.decodeContentGeneralForm(data);
  2043   },
  2045   /**
  2046    * @param data
  2047    *        A wrapped object to store encoded raw data.
  2048    * @param value
  2049    *        An object containing `media` and `params` properties.
  2050    */
  2051   encodeConstrainedMedia: function(data, value) {
  2052     if (value.params) {
  2053       throw new CodeError("Constrained-media: should use general form instead");
  2056     TypeValue.encode(data, value.media);
  2057   },
  2059   /**
  2060    * @param data
  2061    *        A wrapped object to store encoded raw data.
  2062    * @param value
  2063    *        An object containing `media` and `params` properties.
  2064    */
  2065   encodeMediaType: function(data, value) {
  2066     let entry = WSP_WELL_KNOWN_CONTENT_TYPES[value.media.toLowerCase()];
  2067     if (entry) {
  2068       IntegerValue.encode(data, entry.number);
  2069     } else {
  2070       NullTerminatedTexts.encode(data, value.media);
  2073     Parameter.encodeMultiple(data, value.params);
  2074   },
  2076   /**
  2077    * @param data
  2078    *        A wrapped object to store encoded raw data.
  2079    * @param value
  2080    *        An object containing `media` and `params` properties.
  2081    */
  2082   encodeContentGeneralForm: function(data, value) {
  2083     let begin = data.offset;
  2084     this.encodeMediaType(data, value);
  2086     // Calculate how much octets will be written and seek back.
  2087     // TODO: use memmove, see bug 730873
  2088     let len = data.offset - begin;
  2089     data.offset = begin;
  2091     ValueLength.encode(data, len);
  2092     this.encodeMediaType(data, value);
  2093   },
  2095   /**
  2096    * @param data
  2097    *        A wrapped object to store encoded raw data.
  2098    * @param value
  2099    *        An object containing `media` and `params` properties.
  2100    */
  2101   encode: function(data, value) {
  2102     let begin = data.offset;
  2104     try {
  2105       this.encodeConstrainedMedia(data, value);
  2106     } catch (e) {
  2107       data.offset = begin;
  2108       this.encodeContentGeneralForm(data, value);
  2110   },
  2111 };
  2113 /**
  2114  * Application-id-value = Uri-value | App-assigned-code
  2115  * App-assigned-code = Integer-value
  2117  * @see WAP-230-WSP-20010705-a clause 8.4.2.54
  2118  */
  2119 this.ApplicationIdValue = {
  2120   /**
  2121    * @param data
  2122    *        A wrapped object containing raw PDU data.
  2124    * @return Decoded string value.
  2126    * @throws CodeError if decoded application id number is an array.
  2127    * @throws NotWellKnownEncodingError if decoded well-known application id
  2128    *         number is not registered or supported.
  2129    */
  2130   decode: function(data) {
  2131     let begin = data.offset;
  2132     try {
  2133       return UriValue.decode(data);
  2134     } catch (e) {}
  2136     data.offset = begin;
  2138     // `decodeIntegerValue` can return a array, which doesn't apply here.
  2139     let numOrArray = IntegerValue.decode(data);
  2140     if (typeof numOrArray != "number") {
  2141       throw new CodeError("Application-id-value: invalid integer type");
  2144     let id = numOrArray;
  2145     let entry = OMNA_PUSH_APPLICATION_IDS[id];
  2146     if (!entry) {
  2147       throw new NotWellKnownEncodingError(
  2148         "Application-id-value: not well known id: " + id);
  2151     return entry.urn;
  2152   },
  2153 };
  2155 this.PduHelper = {
  2156   /**
  2157    * @param data
  2158    *        A UInt8Array of data for decode.
  2159    * @param charset
  2160    *        charset for decode
  2162    * @return Decoded string.
  2163    */
  2164   decodeStringContent: function(data, charset) {
  2165       let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  2166                  .createInstance(Ci.nsIScriptableUnicodeConverter);
  2168       let entry;
  2169       if (charset) {
  2170         entry = WSP_WELL_KNOWN_CHARSETS[charset];
  2172       // Set converter to default one if (entry && entry.converter) is null.
  2173       // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9
  2174       conv.charset = (entry && entry.converter) || "UTF-8";
  2175       try {
  2176         return conv.convertFromByteArray(data, data.length);
  2177       } catch (e) {
  2179       return null;
  2180   },
  2182   /**
  2183   * @param strContent
  2184   *        Decoded string content.
  2185   * @param charset
  2186   *        Charset for encode.
  2188   * @return An encoded UInt8Array of string content.
  2189   */
  2190   encodeStringContent: function(strContent, charset) {
  2191     let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  2192                .createInstance(Ci.nsIScriptableUnicodeConverter);
  2194     let entry;
  2195     if (charset) {
  2196       entry = WSP_WELL_KNOWN_CHARSETS[charset];
  2198     // Set converter to default one if (entry && entry.converter) is null.
  2199     // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9
  2200     conv.charset = (entry && entry.converter) || "UTF-8";
  2201     try {
  2202       return conv.convertToByteArray(strContent);
  2203     } catch (e) {
  2205     return null;
  2206   },
  2208   /**
  2209    * Parse multiple header fields with end mark.
  2211    * @param data
  2212    *        A wrapped object containing raw PDU data.
  2213    * @param end
  2214    *        An ending offset indicating the end of headers.
  2215    * @param headers [optional]
  2216    *        An optional object to store parsed header fields. Created
  2217    *        automatically if undefined.
  2219    * @return A object containing decoded header fields as its attributes.
  2220    */
  2221   parseHeaders: function(data, end, headers) {
  2222     if (!headers) {
  2223       headers = {};
  2226     let header;
  2227     while (data.offset < end) {
  2228       try {
  2229         header = Header.decode(data);
  2230       } catch (e) {
  2231         break;
  2233       if (header) {
  2234         headers[header.name] = header.value;
  2238     if (data.offset != end) {
  2239       debug("Parser expects ending in " + end + ", but in " + data.offset);
  2240       // Explicitly seek to end in case of skipped header fields.
  2241       data.offset = end;
  2244     return headers;
  2245   },
  2247   /**
  2248    * @param data
  2249    *        A wrapped object containing raw PDU data.
  2250    * @param msg
  2251    *        Message object to be populated with decoded header fields.
  2253    * @see WAP-230-WSP-20010705-a clause 8.2.4
  2254    */
  2255   parsePushHeaders: function(data, msg) {
  2256     if (!msg.headers) {
  2257       msg.headers = {};
  2260     let headersLen = UintVar.decode(data);
  2261     let headersEnd = data.offset + headersLen;
  2263     let contentType = ContentTypeValue.decode(data);
  2264     msg.headers["content-type"] = contentType;
  2266     msg.headers = this.parseHeaders(data, headersEnd, msg.headers);
  2267   },
  2269   /**
  2270    * @param data
  2271    *        A wrapped object containing raw PDU data.
  2273    * @return An array of objects representing multipart entries or null in case
  2274    *         of errors found.
  2276    * @see WAP-230-WSP-20010705-a section 8.5
  2277    */
  2278   parseMultiPart: function(data) {
  2279     let nEntries = UintVar.decode(data);
  2280     if (!nEntries) {
  2281       return null;
  2284     let parts = new Array(nEntries);
  2285     for (let i = 0; i < nEntries; i++) {
  2286       // Length of the ContentType and Headers fields combined.
  2287       let headersLen = UintVar.decode(data);
  2288       // Length of the Data field
  2289       let contentLen = UintVar.decode(data);
  2291       let headersEnd = data.offset + headersLen;
  2292       let contentEnd = headersEnd + contentLen;
  2294       try {
  2295         let headers = {};
  2297         let contentType = ContentTypeValue.decode(data);
  2298         headers["content-type"] = contentType;
  2299         headers["content-length"] = contentLen;
  2301         headers = this.parseHeaders(data, headersEnd, headers);
  2303         let octetArray = Octet.decodeMultiple(data, contentEnd);
  2304         let content = null;
  2305         let charset = headers["content-type"].params &&
  2306                       headers["content-type"].params.charset
  2307                     ? headers["content-type"].params.charset.charset
  2308                     : null;
  2310         let mimeType = headers["content-type"].media;
  2312         if (mimeType) {
  2313           if (mimeType == "application/smil") {
  2314             // If the content is a SMIL type, convert it to a string.
  2315             // We hope to save and expose the SMIL content in a string way.
  2316             content = this.decodeStringContent(octetArray, charset);
  2317           } else if (mimeType.indexOf("text/") == 0 && charset != "utf-8") {
  2318             // If the content is a "text/plain" type, we have to make sure
  2319             // the encoding of the blob content should always be "utf-8".
  2320             let tmpStr = this.decodeStringContent(octetArray, charset);
  2321             let encoder = new TextEncoder("UTF-8");
  2322             content = new Blob([encoder.encode(tmpStr)], {type : mimeType});
  2324             // Make up the missing encoding info.
  2325             if (!headers["content-type"].params) {
  2326               headers["content-type"].params = {};
  2328             if (!headers["content-type"].params.charset) {
  2329               headers["content-type"].params.charset = {};
  2331             headers["content-type"].params.charset.charset = "utf-8";
  2335         if (!content) {
  2336           content = new Blob([octetArray], {type : mimeType});
  2339         parts[i] = {
  2340           index: i,
  2341           headers: headers,
  2342           content: content,
  2343         };
  2344       } catch (e) {
  2345         debug("Failed to parse multipart entry, message: " + e.message);
  2346         // Placeholder to keep original index of following entries.
  2347         parts[i] = null;
  2350       if (data.offset != contentEnd) {
  2351         // Seek to entry boundary for next entry.
  2352         data.offset = contentEnd;
  2356     return parts;
  2357   },
  2359   /**
  2360    * @param data
  2361    *        A wrapped object containing raw PDU data.
  2362    * @param isSessionless
  2363    *        Whether or not the PDU contains a session less WSP PDU.
  2364    * @param msg [optional]
  2365    *        Optional pre-defined PDU object.
  2367    * @return Parsed WSP PDU object or null in case of errors found.
  2368    */
  2369   parse: function(data, isSessionless, msg) {
  2370     if (!msg) {
  2371       msg = {
  2372         type: null,
  2373       };
  2376     try {
  2377       if (isSessionless) {
  2378         // "The `transactionId` is used to associate requests with replies in
  2379         // the connectionless session service." ~ WAP-230-WSP-20010705-a 8.2.1
  2380         msg.transactionId = Octet.decode(data);
  2383       msg.type = Octet.decode(data);
  2384       switch (msg.type) {
  2385         case WSP_PDU_TYPE_PUSH:
  2386           this.parsePushHeaders(data, msg);
  2387           break;
  2389     } catch (e) {
  2390       debug("Parse error. Message: " + e.message);
  2391       msg = null;
  2394     return msg;
  2395   },
  2397   /**
  2398    * @param multiStream
  2399    *        An exsiting nsIMultiplexInputStream.
  2400    * @param array
  2401    *        An octet array.
  2402    * @param length
  2403    *        Max number of octets to be coverted into an input stream.
  2404    */
  2405   appendArrayToMultiStream: function(multiStream, array, length) {
  2406     let storageStream = Cc["@mozilla.org/storagestream;1"]
  2407                         .createInstance(Ci.nsIStorageStream);
  2408     storageStream.init(4096, length, null);
  2410     let boStream = Cc["@mozilla.org/binaryoutputstream;1"]
  2411                    .createInstance(Ci.nsIBinaryOutputStream);
  2412     boStream.setOutputStream(storageStream.getOutputStream(0));
  2413     boStream.writeByteArray(array, length);
  2414     boStream.close();
  2416     multiStream.appendStream(storageStream.newInputStream(0));
  2417   },
  2419   /**
  2420    * @param multiStream
  2421    *        An exsiting nsIMultiplexInputStream.
  2422    * @param parts
  2423    *        An array of objects representing multipart entries.
  2425    * @see WAP-230-WSP-20010705-a section 8.5
  2426    */
  2427   composeMultiPart: function(multiStream, parts) {
  2428     // Encode multipart header
  2430       let data = {array: [], offset: 0};
  2431       UintVar.encode(data, parts.length);
  2432       debug("Encoded multipart header: " + JSON.stringify(data.array));
  2433       this.appendArrayToMultiStream(multiStream, data.array, data.offset);
  2436     // Encode each part
  2437     for (let i = 0; i < parts.length; i++) {
  2438       let part = parts[i];
  2439       let data = {array: [], offset: 0};
  2441       // Encode Content-Type
  2442       let contentType = part.headers["content-type"];
  2443       ContentTypeValue.encode(data, contentType);
  2445       // Encode other headers
  2446       if (Object.keys(part).length > 1) {
  2447         // Remove Content-Type temporarily
  2448         delete part.headers["content-type"];
  2450         for (let name in part.headers) {
  2451           Header.encode(data, {name: name, value: part.headers[name]});
  2454         // Restore Content-Type back
  2455         part.headers["content-type"] = contentType;
  2458       // Encode headersLen, DataLen
  2459       let headersLen = data.offset;
  2460       let content = part.content;
  2461       UintVar.encode(data, headersLen);
  2462       if (typeof content === "string") {
  2463         let charset;
  2464         if (contentType && contentType.params && contentType.params.charset &&
  2465           contentType.params.charset.charset) {
  2466           charset = contentType.params.charset.charset;
  2468         content = this.encodeStringContent(content, charset);
  2469         UintVar.encode(data, content.length);
  2470       } else if (part.content instanceof Uint8Array) {
  2471         UintVar.encode(data, content.length);
  2472       } else {
  2473         throw new TypeError();
  2476       // Move them to the beginning of encoded octet array.
  2477       let slice1 = data.array.slice(headersLen);
  2478       let slice2 = data.array.slice(0, headersLen);
  2479       data.array = slice1.concat(slice2);
  2480       debug("Encoded per-part header: " + JSON.stringify(data.array));
  2482       // Append per-part header
  2483       this.appendArrayToMultiStream(multiStream, data.array, data.offset);
  2484       // Append part content
  2485       this.appendArrayToMultiStream(multiStream, content, content.length);
  2487   },
  2488 };
  2490 // WSP Header Field Name Assignments
  2491 // Note: Items commented out are either deprecated or not implemented.
  2492 //       Deprecated items should only be supported for backward compatibility
  2493 //       purpose.
  2494 // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
  2495 this.WSP_HEADER_FIELDS = (function() {
  2496   let names = {};
  2497   function add(name, number, coder) {
  2498     let entry = {
  2499       name: name,
  2500       number: number,
  2501       coder: coder,
  2502     };
  2503     names[name] = names[number] = entry;
  2506   // Encoding Version: 1.1
  2507   //add("accept",               0x00);
  2508   //add("accept-charset",       0x01); Deprecated
  2509   //add("accept-encoding",      0x02); Deprecated
  2510   //add("accept-language",      0x03);
  2511   //add("accept-ranges",        0x04);
  2512   add("age",                    0x05, DeltaSecondsValue);
  2513   //add("allow",                0x06);
  2514   //add("authorization",        0x07);
  2515   //add("cache-control",        0x08); Deprecated
  2516   //add("connection",           0x09);
  2517   //add("content-base",         0x0A); Deprecated
  2518   //add("content-encoding",     0x0B);
  2519   //add("content-language",     0x0C);
  2520   add("content-length",         0x0D, IntegerValue);
  2521   add("content-location",       0x0E, UriValue);
  2522   //add("content-md5",          0x0F);
  2523   //add("content-range",        0x10); Deprecated
  2524   add("content-type",           0x11, ContentTypeValue);
  2525   add("date",                   0x12, DateValue);
  2526   add("etag",                   0x13, TextString);
  2527   add("expires",                0x14, DateValue);
  2528   add("from",                   0x15, TextString);
  2529   add("host",                   0x16, TextString);
  2530   add("if-modified-since",      0x17, DateValue);
  2531   add("if-match",               0x18, TextString);
  2532   add("if-none-match",          0x19, TextString);
  2533   //add("if-range",             0x1A);
  2534   add("if-unmodified-since",    0x1B, DateValue);
  2535   add("location",               0x1C, UriValue);
  2536   add("last-modified",          0x1D, DateValue);
  2537   add("max-forwards",           0x1E, IntegerValue);
  2538   //add("pragma",               0x1F);
  2539   //add("proxy-authenticate",   0x20);
  2540   //add("proxy-authentication", 0x21);
  2541   //add("public",               0x22);
  2542   //add("range",                0x23);
  2543   add("referer",                0x24, UriValue);
  2544   //add("retry-after",          0x25);
  2545   add("server",                 0x26, TextString);
  2546   //add("transfer-encoding",    0x27);
  2547   add("upgrade",                0x28, TextString);
  2548   add("user-agent",             0x29, TextString);
  2549   //add("vary",                 0x2A);
  2550   add("via",                    0x2B, TextString);
  2551   //add("warning",              0x2C);
  2552   //add("www-authenticate",     0x2D);
  2553   //add("content-disposition",  0x2E); Deprecated
  2555   // Encoding Version: 1.2
  2556   add("x-wap-application-id",   0x2F, ApplicationIdValue);
  2557   add("x-wap-content-uri",      0x30, UriValue);
  2558   add("x-wap-initiator-uri",    0x31, UriValue);
  2559   //add("accept-application",   0x32);
  2560   add("bearer-indication",      0x33, IntegerValue);
  2561   add("push-flag",              0x34, ShortInteger);
  2562   add("profile",                0x35, UriValue);
  2563   //add("profile-diff",         0x36);
  2564   //add("profile-warning",      0x37); Deprecated
  2566   // Encoding Version: 1.3
  2567   //add("expect",               0x38);
  2568   //add("te",                   0x39);
  2569   //add("trailer",              0x3A);
  2570   add("accept-charset",         0x3B, AcceptCharsetValue);
  2571   //add("accept-encoding",      0x3C);
  2572   //add("cache-control",        0x3D); Deprecated
  2573   //add("content-range",        0x3E);
  2574   add("x-wap-tod",              0x3F, DateValue);
  2575   add("content-id",             0x40, QuotedString);
  2576   //add("set-cookie",           0x41);
  2577   //add("cookie",               0x42);
  2578   //add("encoding-version",     0x43);
  2580   // Encoding Version: 1.4
  2581   //add("profile-warning",      0x44);
  2582   //add("content-disposition",  0x45);
  2583   //add("x-wap-security",       0x46);
  2584   //add("cache-control",        0x47);
  2586   return names;
  2587 })();
  2589 // WSP Content Type Assignments
  2590 // @see http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.aspx
  2591 this.WSP_WELL_KNOWN_CONTENT_TYPES = (function() {
  2592   let types = {};
  2594   function add(type, number) {
  2595     let entry = {
  2596       type: type,
  2597       number: number,
  2598     };
  2599     // For case like "text/x-vCalendar", we need toLoweCase() for generating
  2600     // the same index.
  2601     types[type.toLowerCase()] = types[number] = entry;
  2604   // Well Known Values
  2605   // Encoding Version: 1.1
  2606   add("*/*", 0x00);
  2607   add("text/*", 0x01);
  2608   add("text/html", 0x02);
  2609   add("text/plain", 0x03);
  2610   add("text/x-hdml", 0x04);
  2611   add("text/x-ttml", 0x05);
  2612   add("text/x-vCalendar", 0x06);
  2613   add("text/x-vCard", 0x07);
  2614   add("text/vnd.wap.wml", 0x08);
  2615   add("text/vnd.wap.wmlscript", 0x09);
  2616   add("text/vnd.wap.wta-event", 0x0A);
  2617   add("multipart/*", 0x0B);
  2618   add("multipart/mixed", 0x0C);
  2619   add("multipart/form-data", 0x0D);
  2620   add("multipart/byterantes", 0x0E);
  2621   add("multipart/alternative", 0x0F);
  2622   add("application/*", 0x10);
  2623   add("application/java-vm", 0x11);
  2624   add("application/x-www-form-urlencoded", 0x12);
  2625   add("application/x-hdmlc", 0x13);
  2626   add("application/vnd.wap.wmlc", 0x14);
  2627   add("application/vnd.wap.wmlscriptc", 0x15);
  2628   add("application/vnd.wap.wta-eventc", 0x16);
  2629   add("application/vnd.wap.uaprof", 0x17);
  2630   add("application/vnd.wap.wtls-ca-certificate", 0x18);
  2631   add("application/vnd.wap.wtls-user-certificate", 0x19);
  2632   add("application/x-x509-ca-cert", 0x1A);
  2633   add("application/x-x509-user-cert", 0x1B);
  2634   add("image/*", 0x1C);
  2635   add("image/gif", 0x1D);
  2636   add("image/jpeg", 0x1E);
  2637   add("image/tiff", 0x1F);
  2638   add("image/png", 0x20);
  2639   add("image/vnd.wap.wbmp", 0x21);
  2640   add("application/vnd.wap.multipart.*", 0x22);
  2641   add("application/vnd.wap.multipart.mixed", 0x23);
  2642   add("application/vnd.wap.multipart.form-data", 0x24);
  2643   add("application/vnd.wap.multipart.byteranges", 0x25);
  2644   add("application/vnd.wap.multipart.alternative", 0x26);
  2645   add("application/xml", 0x27);
  2646   add("text/xml", 0x28);
  2647   add("application/vnd.wap.wbxml", 0x29);
  2648   add("application/x-x968-cross-cert", 0x2A);
  2649   add("application/x-x968-ca-cert", 0x2B);
  2650   add("application/x-x968-user-cert", 0x2C);
  2651   add("text/vnd.wap.si", 0x2D);
  2653   // Encoding Version: 1.2
  2654   add("application/vnd.wap.sic", 0x2E);
  2655   add("text/vnd.wap.sl", 0x2F);
  2656   add("application/vnd.wap.slc", 0x30);
  2657   add("text/vnd.wap.co", 0x31);
  2658   add("application/vnd.wap.coc", 0x32);
  2659   add("application/vnd.wap.multipart.related", 0x33);
  2660   add("application/vnd.wap.sia", 0x34);
  2662   // Encoding Version: 1.3
  2663   add("text/vnd.wap.connectivity-xml", 0x35);
  2664   add("application/vnd.wap.connectivity-wbxml", 0x36);
  2666   // Encoding Version: 1.4
  2667   add("application/pkcs7-mime", 0x37);
  2668   add("application/vnd.wap.hashed-certificate", 0x38);
  2669   add("application/vnd.wap.signed-certificate", 0x39);
  2670   add("application/vnd.wap.cert-response", 0x3A);
  2671   add("application/xhtml+xml", 0x3B);
  2672   add("application/wml+xml", 0x3C);
  2673   add("text/css", 0x3D);
  2674   add("application/vnd.wap.mms-message", 0x3E);
  2675   add("application/vnd.wap.rollover-certificate", 0x3F);
  2677   // Encoding Version: 1.5
  2678   add("application/vnd.wap.locc+wbxml", 0x40);
  2679   add("application/vnd.wap.loc+xml", 0x41);
  2680   add("application/vnd.syncml.dm+wbxml", 0x42);
  2681   add("application/vnd.syncml.dm+xml", 0x43);
  2682   add("application/vnd.syncml.notification", 0x44);
  2683   add("application/vnd.wap.xhtml+xml", 0x45);
  2684   add("application/vnd.wv.csp.cir", 0x46);
  2685   add("application/vnd.oma.dd+xml", 0x47);
  2686   add("application/vnd.oma.drm.message", 0x48);
  2687   add("application/vnd.oma.drm.content", 0x49);
  2688   add("application/vnd.oma.drm.rights+xml", 0x4A);
  2689   add("application/vnd.oma.drm.rights+wbxml", 0x4B);
  2690   add("application/vnd.wv.csp+xml", 0x4C);
  2691   add("application/vnd.wv.csp+wbxml", 0x4D);
  2692   add("application/vnd.syncml.ds.notification", 0x4E);
  2694   // Encoding Version: 1.6
  2695   add("audio/*", 0x4F);
  2696   add("video/*", 0x50);
  2698   // Encoding Version: TBD
  2699   add("application/vnd.oma.dd2+xml", 0x51);
  2700   add("application/mikey", 0x52);
  2701   add("application/vnd.oma.dcd", 0x53);
  2702   add("application/vnd.oma.dcdc", 0x54);
  2703   add("text/x-vMessage", 0x55);
  2704   add("application/vnd.omads-email+wbxml", 0x56);
  2705   add("text/x-vBookmark", 0x57);
  2706   add("application/vnd.syncml.dm.notification", 0x58);
  2707   add("application/octet-stream", 0x5A);
  2709   return types;
  2710 })();
  2712 // WSP Well-Known Parameter Assignments
  2713 // Note: Items commented out are either deprecated or not implemented.
  2714 //       Deprecated items should not be used.
  2715 // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
  2716 this.WSP_WELL_KNOWN_PARAMS = (function() {
  2717   let params = {};
  2719   function add(name, number, coder) {
  2720     let entry = {
  2721       name: name,
  2722       number: number,
  2723       coder: coder,
  2724     };
  2725     params[name] = params[number] = entry;
  2728   // Encoding Version: 1.1
  2729   add("q",                 0x00, QValue);
  2730   add("charset",           0x01, WellKnownCharset);
  2731   add("level",             0x02, VersionValue);
  2732   add("type",              0x03, IntegerValue);
  2733   add("name",              0x05, TextValue); // Deprecated, but used in some carriers, eg. Hinet.
  2734   //add("filename",        0x06); Deprecated
  2735   add("differences",       0x07, FieldName);
  2736   add("padding",           0x08, ShortInteger);
  2738   // Encoding Version: 1.2
  2739   add("type",              0x09, TypeValue);
  2740   add("start",             0x0A, TextValue); // Deprecated, but used in some carriers, eg. T-Mobile.
  2741   //add("start-info",      0x0B); Deprecated
  2743   // Encoding Version: 1.3
  2744   //add("comment",         0x0C); Deprecated
  2745   //add("domain",          0x0D); Deprecated
  2746   add("max-age",           0x0E, DeltaSecondsValue);
  2747   //add("path",            0x0F); Deprecated
  2748   add("secure",            0x10, NoValue);
  2750   // Encoding Version: 1.4
  2751   add("sec",               0x11, ShortInteger);
  2752   add("mac",               0x12, TextValue);
  2753   add("creation-date",     0x13, DateValue);
  2754   add("modification-date", 0x14, DateValue);
  2755   add("read-date",         0x15, DateValue);
  2756   add("size",              0x16, IntegerValue);
  2757   //add("name",            0x17, TextValue); // Not supported in some carriers, eg. Hinet.
  2758   add("filename",          0x18, TextValue);
  2759   //add("start",           0x19, TextValue); // Not supported in some carriers, eg. Hinet.
  2760   add("start-info",        0x1A, TextValue);
  2761   add("comment",           0x1B, TextValue);
  2762   add("domain",            0x1C, TextValue);
  2763   add("path",              0x1D, TextValue);
  2765   return params;
  2766 })();
  2768 // WSP Character Set Assignments
  2769 // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
  2770 // @see http://www.iana.org/assignments/character-sets
  2771 this.WSP_WELL_KNOWN_CHARSETS = (function() {
  2772   let charsets = {};
  2774   function add(name, number, converter) {
  2775     let entry = {
  2776       name: name,
  2777       number: number,
  2778       converter: converter,
  2779     };
  2781     charsets[name] = charsets[number] = entry;
  2784   add("us-ascii",           3, null);
  2785   add("iso-8859-1",         4, "ISO-8859-1");
  2786   add("iso-8859-2",         5, "ISO-8859-2");
  2787   add("iso-8859-3",         6, "ISO-8859-3");
  2788   add("iso-8859-4",         7, "ISO-8859-4");
  2789   add("iso-8859-5",         8, "ISO-8859-5");
  2790   add("iso-8859-6",         9, "ISO-8859-6");
  2791   add("iso-8859-7",        10, "ISO-8859-7");
  2792   add("iso-8859-8",        11, "ISO-8859-8");
  2793   add("iso-8859-9",        12, "ISO-8859-9");
  2794   add("iso-8859-10",       13, "ISO-8859-10");
  2795   add("shift_jis",         17, "Shift_JIS");
  2796   add("euc-jp",            18, "EUC-JP");
  2797   add("iso-2022-kr",       37, "ISO-2022-KR");
  2798   add("euc-kr",            38, "EUC-KR");
  2799   add("iso-2022-jp",       39, "ISO-2022-JP");
  2800   add("iso-2022-jp-2",     40, "iso-2022-jp-2");
  2801   add("iso-8859-6-e",      81, "ISO-8859-6-E");
  2802   add("iso-8859-6-i",      82, "ISO-8859-6-I");
  2803   add("iso-8859-8-e",      84, "ISO-8859-8-E");
  2804   add("iso-8859-8-i",      85, "ISO-8859-8-I");
  2805   add("utf-8",            106, "UTF-8");
  2806   add("iso-10646-ucs-2", 1000, "iso-10646-ucs-2");
  2807   add("utf-16",          1015, "UTF-16");
  2808   add("gb2312",          2025, "GB2312");
  2809   add("big5",            2026, "Big5");
  2810   add("koi8-r",          2084, "KOI8-R");
  2811   add("windows-1252",    2252, "windows-1252");
  2813   return charsets;
  2814 })();
  2816 // OMNA PUSH Application ID
  2817 // @see http://www.openmobilealliance.org/tech/omna/omna-push-app-id.aspx
  2818 this.OMNA_PUSH_APPLICATION_IDS = (function() {
  2819   let ids = {};
  2821   function add(urn, number) {
  2822     let entry = {
  2823       urn: urn,
  2824       number: number,
  2825     };
  2827     ids[urn] = ids[number] = entry;
  2830   add("x-wap-application:wml.ua", 0x02);
  2831   add("x-wap-application:mms.ua", 0x04);
  2833   return ids;
  2834 })();
  2836 let debug;
  2837 if (DEBUG) {
  2838   debug = function(s) {
  2839     dump("-@- WspPduHelper: " + s + "\n");
  2840   };
  2841 } else {
  2842   debug = function(s) {};
  2845 this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
  2846   // Constant values
  2847   "WSP_HEADER_FIELDS",
  2848   "WSP_WELL_KNOWN_CONTENT_TYPES",
  2849   "WSP_WELL_KNOWN_PARAMS",
  2850   "WSP_WELL_KNOWN_CHARSETS",
  2851   "OMNA_PUSH_APPLICATION_IDS",
  2853   // Error classes
  2854   "CodeError",
  2855   "FatalCodeError",
  2856   "NotWellKnownEncodingError",
  2858   // Utility functions
  2859   "ensureHeader",
  2860   "skipValue",
  2861   "decodeAlternatives",
  2862   "encodeAlternatives",
  2864   // Decoders
  2865   "Octet",
  2866   "Text",
  2867   "NullTerminatedTexts",
  2868   "Token",
  2869   "URIC",
  2870   "TextString",
  2871   "TokenText",
  2872   "QuotedString",
  2873   "ShortInteger",
  2874   "LongInteger",
  2875   "UintVar",
  2876   "ConstrainedEncoding",
  2877   "ValueLength",
  2878   "NoValue",
  2879   "TextValue",
  2880   "IntegerValue",
  2881   "DateValue",
  2882   "DeltaSecondsValue",
  2883   "QValue",
  2884   "VersionValue",
  2885   "UriValue",
  2886   "TypeValue",
  2887   "Parameter",
  2888   "Header",
  2889   "WellKnownHeader",
  2890   "ApplicationHeader",
  2891   "FieldName",
  2892   "AcceptCharsetValue",
  2893   "WellKnownCharset",
  2894   "ContentTypeValue",
  2895   "ApplicationIdValue",
  2897   // Parser
  2898   "PduHelper",
  2899 ]);

mercurial