Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / dom / wappush / gonk / WbxmlPduHelper.jsm
blob69e154af415963ff6d3cdac0d100022fec219f9a
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   },
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   },
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
103  * @return A string with the &, <, >, " and ' characters turned into XML
104  *         character entitites
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;
126  * Handle string table in WBXML message.
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);
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   }
157  * Parse inline string in WBXML encoded message.
159  * @param data
160  *        A wrapped object containing raw PDU data.
161  * @param decodeInfo
162  *        Internal information for decode process.
164  * @see WAP-192-WBXML-20010725-A, clause 5.8.4.1
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   },
183  * Parse inline Opaque data in WBXML encoded message.
185  * @param data
186  *        A wrapped object containing raw PDU data.
187  * @param decodeInfo
188  *        Internal information for decode process.
190  * @see WAP-192-WBXML-20010725-A, clause 5.8.4.6
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   },
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   }
409  * Global Tokens
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 })();
433  *  Pre-defined public IDs
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",