dom/wappush/src/gonk/WbxmlPduHelper.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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 let WSP = {};
    10 Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP);
    12 /**
    13  * Token flags
    14  *
    15  * @see WAP-192-WBXML-20010725-A, clause 5.8.2
    16  */
    17 const TAG_TOKEN_ATTR_MASK     = 0x80;
    18 const TAG_TOKEN_CONTENT_MASK  = 0x40;
    19 const TAG_TOKEN_VALUE_MASK    = 0x3F;
    21 /**
    22  * Global tokens
    23  *
    24  * @see WAP-192-WBXML-20010725-A, clause 7.1
    25  */
    26 const CODE_PAGE_SWITCH_TOKEN  = 0x00;
    27 const TAG_END_TOKEN           = 0x01;
    28 const INLINE_STRING_TOKEN     = 0x03;
    29 const STRING_TABLE_TOKEN      = 0x83;
    30 const OPAQUE_TOKEN            = 0xC3;
    32 // Set to true to enable debug message on all WBXML decoders.
    33 this.DEBUG_ALL = false;
    35 // Enable debug message for WBXML decoder core.
    36 this.DEBUG = DEBUG_ALL | false;
    38 /**
    39  * Handle WBXML code page switch.
    40  *
    41  * @param data
    42  *        A wrapped object containing raw PDU data.
    43  * @param decodeInfo
    44  *        Internal information for decode process.
    45  *
    46  * @see WAP-192-WBXML-20010725-A, clause 5.8.4.7.2 and 5.8.1
    47  */
    48 this.WbxmlCodePageSwitch = {
    49   decode: function decode_wbxml_code_page_switch(data, decodeInfo) {
    50     let codePage = WSP.Octet.decode(data);
    52     if (decodeInfo.currentState === "tag") {
    53       decodeInfo.currentTokenList.tag = decodeInfo.tokenList.tag[codePage];
    55       if (!decodeInfo.currentTokenList.tag) {
    56         throw new Error("Invalid tag code page: " + codePage + ".");
    57       }
    59       return "";
    60     }
    62     if (decodeInfo.currentState === "attr") {
    63       decodeInfo.currentTokenList.attr = decodeInfo.tokenList.attr[codePage];
    64       decodeInfo.currentTokenList.value = decodeInfo.tokenList.value[codePage];
    66       if (!decodeInfo.currentTokenList.attr ||
    67           !decodeInfo.currentTokenList.value) {
    68         throw new Error("Invalid attr code page: " + codePage + ".");
    69       }
    71       return "";
    72     }
    74     throw new Error("Invalid decoder state: " + decodeInfo.currentState + ".");
    75   },
    76 };
    78 /**
    79  * Parse end WBXML encoded message.
    80  *
    81  * @param data
    82  *        A wrapped object containing raw PDU data.
    83  * @param decodeInfo
    84  *        Internal information for decode process.
    85  *
    86  * @see WAP-192-WBXML-20010725-A, clause 5.8.4.7.1
    87  *
    88  */
    89 this.WbxmlEnd = {
    90   decode: function decode_wbxml_end(data, decodeInfo) {
    91     let tagInfo = decodeInfo.tagStack.pop();
    92     return "</" + tagInfo.name + ">";
    93   },
    94 };
    96 /**
    97  * Escape XML reserved characters &, <, >, " and ' which may appear in the
    98  * WBXML-encoded strings in their original form.
    99  *
   100  * @param str
   101  *        A string with potentially unescaped characters
   102  *
   103  * @return A string with the &, <, >, " and ' characters turned into XML
   104  *         character entitites
   105  *
   106  * @see WAP-192-WBXML-20010725-A, clause 6.1
   107  */
   108 this.escapeReservedCharacters = function escape_reserved_characters(str) {
   109   let dst = "";
   111   for (var i = 0; i < str.length; i++) {
   112     switch (str[i]) {
   113       case '&' : dst += "&amp;" ; break;
   114       case '<' : dst += "&lt;"  ; break;
   115       case '>' : dst += "&gt;"  ; break;
   116       case '"' : dst += "&quot;"; break;
   117       case '\'': dst += "&apos;"; break;
   118       default  : dst += str[i];
   119     }
   120   }
   122   return dst;
   123 }
   125 /**
   126  * Handle string table in WBXML message.
   127  *
   128  * @see WAP-192-WBXML-20010725-A, clause 5.7
   129  */
   130 this.readStringTable = function decode_wbxml_read_string_table(start, stringTable, charset) {
   131   let end = start;
   133   // Find end of string
   134   let stringTableLength = stringTable.length;
   135   while (end < stringTableLength) {
   136     if (stringTable[end] === 0) {
   137       break;
   138     }
   139     end++;
   140   }
   142   // Read string table
   143   return WSP.PduHelper.decodeStringContent(stringTable.subarray(start, end),
   144                                            charset);
   145 };
   147 this.WbxmlStringTable = {
   148   decode: function decode_wbxml_string_table(data, decodeInfo) {
   149     let start = WSP.Octet.decode(data);
   150     let str = readStringTable(start, decodeInfo.stringTable, decodeInfo.charset);
   152     return escapeReservedCharacters(str);
   153   }
   154 };
   156 /**
   157  * Parse inline string in WBXML encoded message.
   158  *
   159  * @param data
   160  *        A wrapped object containing raw PDU data.
   161  * @param decodeInfo
   162  *        Internal information for decode process.
   163  *
   164  * @see WAP-192-WBXML-20010725-A, clause 5.8.4.1
   165  *
   166  */
   167 this.WbxmlInlineString = {
   168   decode: function decode_wbxml_inline_string(data, decodeInfo) {
   169     let charCode = WSP.Octet.decode(data);
   170     let stringData = [];
   171     while (charCode) {
   172       stringData.push(charCode);
   173       charCode = WSP.Octet.decode(data);
   174     }
   176     let str = WSP.PduHelper.decodeStringContent(stringData, decodeInfo.charset);
   178     return escapeReservedCharacters(str);
   179   },
   180 };
   182 /**
   183  * Parse inline Opaque data in WBXML encoded message.
   184  *
   185  * @param data
   186  *        A wrapped object containing raw PDU data.
   187  * @param decodeInfo
   188  *        Internal information for decode process.
   189  *
   190  * @see WAP-192-WBXML-20010725-A, clause 5.8.4.6
   191  *
   192  */
   193 this.WbxmlOpaque = {
   194   decode: function decode_wbxml_inline_opaque(data) {
   195     // Inline OPAQUE must be decoded based on application definition,
   196     // so it's illegal to run into OPAQUE type in general decoder.
   197     throw new Error("OPQAUE decoder is not defined.");
   198   },
   199 };
   201 this.PduHelper = {
   203   /**
   204    * Parse WBXML encoded message into plain text.
   205    *
   206    * @param data
   207    *        A wrapped object containing raw PDU data.
   208    * @param decodeInfo
   209    *        Information for decoding, now requires charset and string table.
   210    * @param appToken
   211    *        Application-specific token difinition.
   212    *
   213    * @return Decoded WBXML message string.
   214    */
   215   parseWbxml: function parseWbxml_wbxml(data, decodeInfo, appToken) {
   217     // Parse token definition to my structure.
   218     decodeInfo.tokenList = {
   219       tag: appToken.tagTokenList,
   220       attr: appToken.attrTokenList,
   221       value: appToken.valueTokenList
   222     };
   223     decodeInfo.tagStack = [];   // tag decode stack
   224     decodeInfo.currentTokenList = {
   225       tag: decodeInfo.tokenList.tag[0],
   226       attr: decodeInfo.tokenList.attr[0],
   227       value: decodeInfo.tokenList.value[0]
   228     };
   229     decodeInfo.currentState = "tag";  // Current decoding state, "tag" or "attr"
   230                                       // Used to read corresponding code page
   231                                       // initial state : "tag"
   233     // Merge global tag tokens into single list, so we don't have
   234     // to search two lists every time.
   235     let globalTagTokenList = Object.create(WBXML_GLOBAL_TOKENS);
   236     if (appToken.globalTokenOverride) {
   237       let globalTokenOverrideList = appToken.globalTokenOverride;
   238       for (let token in globalTokenOverrideList) {
   239         globalTagTokenList[token] = globalTokenOverrideList[token];
   240       }
   241     }
   243     let content = "";
   244     while (data.offset < data.array.length) {
   245       // Decode content, might be a new tag token, an end of tag token, or an
   246       // inline string.
   248       // Switch to tag domain
   249       decodeInfo.currentState = "tag";
   251       let tagToken = WSP.Octet.decode(data);
   252       let tagTokenValue = tagToken & TAG_TOKEN_VALUE_MASK;
   254       // Try global tag first, tagToken of string table is 0x83, and will be 0x03
   255       // in tagTokenValue, which is collision with inline string.
   256       // So tagToken need to be searched before tagTokenValue.
   257       let tagInfo = globalTagTokenList[tagToken] ||
   258                     globalTagTokenList[tagTokenValue];
   259       if (tagInfo) {
   260         content += tagInfo.coder.decode(data, decodeInfo);
   261         continue;
   262       }
   264       // Check if application tag token is valid
   265       tagInfo = decodeInfo.currentTokenList.tag[tagTokenValue];
   266       if (!tagInfo) {
   267         throw new Error("Unsupported WBXML token: " + tagTokenValue + ".");
   268       }
   270       content += "<" + tagInfo.name;
   272       if (tagToken & TAG_TOKEN_ATTR_MASK) {
   273         // Decode attributes, might be a new attribute token, a value token,
   274         // or an inline string
   276         // Switch to attr/value domain
   277         decodeInfo.currentState = "attr";
   279         let attrSeperator = "";
   280         while (data.offset < data.array.length) {
   281           let attrToken = WSP.Octet.decode(data);
   282           if (attrToken === TAG_END_TOKEN) {
   283             break;
   284           }
   286           let attrInfo = globalTagTokenList[attrToken];
   287           if (attrInfo) {
   288             content += attrInfo.coder.decode(data, decodeInfo);
   289             continue;
   290           }
   292           // Check if attribute token is valid
   293           attrInfo = decodeInfo.currentTokenList.attr[attrToken];
   294           if (attrInfo) {
   295             content += attrSeperator + " " + attrInfo.name + "=\"" + attrInfo.value;
   296             attrSeperator = "\"";
   297             continue;
   298           }
   300           attrInfo = decodeInfo.currentTokenList.value[attrToken];
   301           if (attrInfo) {
   302             content += attrInfo.value;
   303             continue;
   304           }
   306           throw new Error("Unsupported WBXML token: " + attrToken + ".");
   307         }
   309         content += attrSeperator;
   310       }
   312       if (tagToken & TAG_TOKEN_CONTENT_MASK) {
   313         content += ">";
   314         decodeInfo.tagStack.push(tagInfo);
   315         continue;
   316       }
   318       content += "/>";
   319     }
   321     return content;
   322   },
   324   /**
   325    * @param data
   326    *        A wrapped object containing raw PDU data.
   327    * @param appToken
   328    *        contains application-specific token info, including
   329    *        {
   330    *          publicId              : Public identifier of application.
   331    *          tagToken              : Ojbect defines application tag tokens.
   332    *                                  In form of
   333    *                                  Object[TAG_NAME] = Object[TOKEN_NUMBER] =
   334    *                                  {
   335    *                                    name: "TOKEN_NAME",
   336    *                                    number: TOKEN_NUMBER
   337    *                                  }
   338    *          attrToken             : Object defines application attribute tokens.
   339    *                                  Object[ATTR_NAME] = Object[TOKEN_NUMBER] =
   340    *                                  {
   341    *                                    name: "ATTR_NAME",
   342    *                                    value: "ATTR_VALUE",
   343    *                                    number: TOKEN_NUMBER
   344    *                                  }
   345    *                                  For attribute value tokens, assign name as ""
   346    *          globalTokenOverride   : Object overrides decoding behavior of global tokens.
   347    *                                  In form of
   348    *                                  Object[GLOBAL_TOKEN_NUMBER] =
   349    *                                  {
   350    *                                    decode: function(data),
   351    *                                    encode: function(data)
   352    *                                  }
   353    *                                  decode() returns decoded text, encode() returns
   354    *                                  encoded raw data.
   355    *        }
   356    *
   357    * @return A WBXML message object or null in case of errors found.
   358    */
   359   parse: function parse_wbxml(data, appToken) {
   360     let msg = {};
   362     /**
   363      * Read WBXML header.
   364      *
   365      * @see WAP-192-WBXML-20010725-A, Clause 5.3
   366      */
   367     let headerRaw = {};
   368     headerRaw.version = WSP.Octet.decode(data);
   369     headerRaw.publicId = WSP.UintVar.decode(data);
   370     if (headerRaw.publicId === 0) {
   371       headerRaw.publicIdStr = WSP.UintVar.decode(data);
   372     }
   373     headerRaw.charset = WSP.UintVar.decode(data);
   375     let stringTableLen = WSP.UintVar.decode(data);
   376     msg.stringTable =
   377         WSP.Octet.decodeMultiple(data, data.offset + stringTableLen);
   379     // Transform raw header into user-friendly form
   380     let entry = WSP.WSP_WELL_KNOWN_CHARSETS[headerRaw.charset];
   381     if (!entry) {
   382       throw new Error("Charset is not supported.");
   383     }
   384     msg.charset = entry.name;
   386     if (headerRaw.publicId !== 0) {
   387       msg.publicId = WBXML_PUBLIC_ID[headerRaw.publicId];
   388     } else {
   389       msg.publicId = readStringTable(headerRaw.publicIdStr, msg.stringTable,
   390                                      WSP.WSP_WELL_KNOWN_CHARSETS[msg.charset].converter);
   391     }
   392     if (msg.publicId != appToken.publicId) {
   393       throw new Error("Public ID does not match.");
   394     }
   396     msg.version = ((headerRaw.version >> 4) + 1) + "." + (headerRaw.version & 0x0F);
   398     let decodeInfo = {
   399       charset: WSP.WSP_WELL_KNOWN_CHARSETS[msg.charset].converter,  // document character set
   400       stringTable: msg.stringTable                                  // document string table
   401     };
   402     msg.content = this.parseWbxml(data, decodeInfo, appToken);
   404     return msg;
   405   }
   406 };
   408 /**
   409  * Global Tokens
   410  *
   411  * @see WAP-192-WBXML-20010725-A, clause 7.1
   412  */
   413 const WBXML_GLOBAL_TOKENS = (function () {
   414   let names = {};
   415   function add(number, coder) {
   416     let entry = {
   417       number: number,
   418       coder: coder,
   419     };
   420     names[number] = entry;
   421   }
   423   add(CODE_PAGE_SWITCH_TOKEN, WbxmlCodePageSwitch);
   424   add(TAG_END_TOKEN,          WbxmlEnd);
   425   add(INLINE_STRING_TOKEN,    WbxmlInlineString);
   426   add(STRING_TABLE_TOKEN,     WbxmlStringTable);
   427   add(OPAQUE_TOKEN,           WbxmlOpaque);
   429   return names;
   430 })();
   432 /**
   433  *  Pre-defined public IDs
   434  *
   435  * @see http://technical.openmobilealliance.org/tech/omna/omna-wbxml-public-docid.aspx
   436  */
   437 const WBXML_PUBLIC_ID = (function () {
   438   let ids = {};
   439   function add(id, text) {
   440     ids[id] = text;
   441   }
   443   // Well Known Values
   444   add(0x01,     "UNKNOWN");
   445   add(0x02,     "-//WAPFORUM//DTD WML 1.0//EN");
   446   add(0x03,     "-//WAPFORUM//DTD WTA 1.0//EN");
   447   add(0x04,     "-//WAPFORUM//DTD WML 1.1//EN");
   448   add(0x05,     "-//WAPFORUM//DTD SI 1.0//EN");
   449   add(0x06,     "-//WAPFORUM//DTD SL 1.0//EN");
   450   add(0x07,     "-//WAPFORUM//DTD CO 1.0//EN");
   451   add(0x08,     "-//WAPFORUM//DTD CHANNEL 1.1//EN");
   452   add(0x09,     "-//WAPFORUM//DTD WML 1.2//EN");
   453   add(0x0A,     "-//WAPFORUM//DTD WML 1.3//EN");
   454   add(0x0B,     "-//WAPFORUM//DTD PROV 1.0//EN");
   455   add(0x0C,     "-//WAPFORUM//DTD WTA-WML 1.2//EN");
   456   add(0x0D,     "-//WAPFORUM//DTD EMN 1.0//EN");
   457   add(0x0E,     "-//OMA//DTD DRMREL 1.0//EN");
   458   add(0x0F,     "-//WIRELESSVILLAGE//DTD CSP 1.0//EN");
   459   add(0x10,     "-//WIRELESSVILLAGE//DTD CSP 1.1//EN");
   460   add(0x11,     "-//OMA//DTD WV-CSP 1.2//EN");
   461   add(0x12,     "-//OMA//DTD IMPS-CSP 1.3//EN");
   462   add(0x13,     "-//OMA//DRM 2.1//EN");
   463   add(0x14,     "-//OMA//SRM 1.0//EN");
   464   add(0x15,     "-//OMA//DCD 1.0//EN");
   465   add(0x16,     "-//OMA//DTD DS-DataObjectEmail 1.2//EN");
   466   add(0x17,     "-//OMA//DTD DS-DataObjectFolder 1.2//EN");
   467   add(0x18,     "-//OMA//DTD DS-DataObjectFile 1.2//EN");
   469   // Registered Values
   470   add(0x0FD1,   "-//SYNCML//DTD SyncML 1.0//EN");
   471   add(0x0FD2,   "-//SYNCML//DTD DevInf 1.0//EN");
   472   add(0x0FD3,   "-//SYNCML//DTD SyncML 1.1//EN");
   473   add(0x0FD4,   "-//SYNCML//DTD DevInf 1.1//EN");
   474   add(0x1100,   "-//PHONE.COM//DTD ALERT 1.0//EN");
   475   add(0x1101,   "-//PHONE.COM//DTD CACHE-OPERATION 1.0//EN");
   476   add(0x1102,   "-//PHONE.COM//DTD SIGNAL 1.0//EN");
   477   add(0x1103,   "-//PHONE.COM//DTD LIST 1.0//EN");
   478   add(0x1104,   "-//PHONE.COM//DTD LISTCMD 1.0//EN");
   479   add(0x1105,   "-//PHONE.COM//DTD CHANNEL 1.0//EN");
   480   add(0x1106,   "-//PHONE.COM//DTD MMC 1.0//EN");
   481   add(0x1107,   "-//PHONE.COM//DTD BEARER-CHOICE 1.0//EN");
   482   add(0x1108,   "-//PHONE.COM//DTD WML 1.1//EN");
   483   add(0x1109,   "-//PHONE.COM//DTD CHANNEL 1.1//EN");
   484   add(0x110A,   "-//PHONE.COM//DTD LIST 1.1//EN");
   485   add(0x110B,   "-//PHONE.COM//DTD LISTCMD 1.1//EN");
   486   add(0x110C,   "-//PHONE.COM//DTD MMC 1.1//EN");
   487   add(0x110D,   "-//PHONE.COM//DTD WML 1.3//EN");
   488   add(0x110E,   "-//PHONE.COM//DTD MMC 2.0//EN");
   489   add(0x1200,   "-//3GPP2.COM//DTD IOTA 1.0//EN");
   490   add(0x1201,   "-//SYNCML//DTD SyncML 1.2//EN");
   491   add(0x1202,   "-//SYNCML//DTD MetaInf 1.2//EN");
   492   add(0x1203,   "-//SYNCML//DTD DevInf 1.2//EN");
   493   add(0x1204,   "-//NOKIA//DTD LANDMARKS 1.0//EN");
   494   add(0x1205,   "-//SyncML//Schema SyncML 2.0//EN");
   495   add(0x1206,   "-//SyncML//Schema DevInf 2.0//EN");
   496   add(0x1207,   "-//OMA//DTD DRMREL 1.0//EN");
   498   return ids;
   499 })();
   501 this.EXPORTED_SYMBOLS = [
   502   // Parser
   503   "PduHelper",
   504 ];

mercurial