diff -r 000000000000 -r 6474c204b198 dom/wappush/src/gonk/WbxmlPduHelper.jsm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/wappush/src/gonk/WbxmlPduHelper.jsm Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,504 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +let WSP = {}; +Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP); + +/** + * Token flags + * + * @see WAP-192-WBXML-20010725-A, clause 5.8.2 + */ +const TAG_TOKEN_ATTR_MASK = 0x80; +const TAG_TOKEN_CONTENT_MASK = 0x40; +const TAG_TOKEN_VALUE_MASK = 0x3F; + +/** + * Global tokens + * + * @see WAP-192-WBXML-20010725-A, clause 7.1 + */ +const CODE_PAGE_SWITCH_TOKEN = 0x00; +const TAG_END_TOKEN = 0x01; +const INLINE_STRING_TOKEN = 0x03; +const STRING_TABLE_TOKEN = 0x83; +const OPAQUE_TOKEN = 0xC3; + +// Set to true to enable debug message on all WBXML decoders. +this.DEBUG_ALL = false; + +// Enable debug message for WBXML decoder core. +this.DEBUG = DEBUG_ALL | false; + +/** + * Handle WBXML code page switch. + * + * @param data + * A wrapped object containing raw PDU data. + * @param decodeInfo + * Internal information for decode process. + * + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.7.2 and 5.8.1 + */ +this.WbxmlCodePageSwitch = { + decode: function decode_wbxml_code_page_switch(data, decodeInfo) { + let codePage = WSP.Octet.decode(data); + + if (decodeInfo.currentState === "tag") { + decodeInfo.currentTokenList.tag = decodeInfo.tokenList.tag[codePage]; + + if (!decodeInfo.currentTokenList.tag) { + throw new Error("Invalid tag code page: " + codePage + "."); + } + + return ""; + } + + if (decodeInfo.currentState === "attr") { + decodeInfo.currentTokenList.attr = decodeInfo.tokenList.attr[codePage]; + decodeInfo.currentTokenList.value = decodeInfo.tokenList.value[codePage]; + + if (!decodeInfo.currentTokenList.attr || + !decodeInfo.currentTokenList.value) { + throw new Error("Invalid attr code page: " + codePage + "."); + } + + return ""; + } + + throw new Error("Invalid decoder state: " + decodeInfo.currentState + "."); + }, +}; + +/** + * Parse end WBXML encoded message. + * + * @param data + * A wrapped object containing raw PDU data. + * @param decodeInfo + * Internal information for decode process. + * + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.7.1 + * + */ +this.WbxmlEnd = { + decode: function decode_wbxml_end(data, decodeInfo) { + let tagInfo = decodeInfo.tagStack.pop(); + return ""; + }, +}; + +/** + * Escape XML reserved characters &, <, >, " and ' which may appear in the + * WBXML-encoded strings in their original form. + * + * @param str + * A string with potentially unescaped characters + * + * @return A string with the &, <, >, " and ' characters turned into XML + * character entitites + * + * @see WAP-192-WBXML-20010725-A, clause 6.1 + */ +this.escapeReservedCharacters = function escape_reserved_characters(str) { + let dst = ""; + + for (var i = 0; i < str.length; i++) { + switch (str[i]) { + case '&' : dst += "&" ; break; + case '<' : dst += "<" ; break; + case '>' : dst += ">" ; break; + case '"' : dst += """; break; + case '\'': dst += "'"; break; + default : dst += str[i]; + } + } + + return dst; +} + +/** + * Handle string table in WBXML message. + * + * @see WAP-192-WBXML-20010725-A, clause 5.7 + */ +this.readStringTable = function decode_wbxml_read_string_table(start, stringTable, charset) { + let end = start; + + // Find end of string + let stringTableLength = stringTable.length; + while (end < stringTableLength) { + if (stringTable[end] === 0) { + break; + } + end++; + } + + // Read string table + return WSP.PduHelper.decodeStringContent(stringTable.subarray(start, end), + charset); +}; + +this.WbxmlStringTable = { + decode: function decode_wbxml_string_table(data, decodeInfo) { + let start = WSP.Octet.decode(data); + let str = readStringTable(start, decodeInfo.stringTable, decodeInfo.charset); + + return escapeReservedCharacters(str); + } +}; + +/** + * Parse inline string in WBXML encoded message. + * + * @param data + * A wrapped object containing raw PDU data. + * @param decodeInfo + * Internal information for decode process. + * + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.1 + * + */ +this.WbxmlInlineString = { + decode: function decode_wbxml_inline_string(data, decodeInfo) { + let charCode = WSP.Octet.decode(data); + let stringData = []; + while (charCode) { + stringData.push(charCode); + charCode = WSP.Octet.decode(data); + } + + let str = WSP.PduHelper.decodeStringContent(stringData, decodeInfo.charset); + + return escapeReservedCharacters(str); + }, +}; + +/** + * Parse inline Opaque data in WBXML encoded message. + * + * @param data + * A wrapped object containing raw PDU data. + * @param decodeInfo + * Internal information for decode process. + * + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.6 + * + */ +this.WbxmlOpaque = { + decode: function decode_wbxml_inline_opaque(data) { + // Inline OPAQUE must be decoded based on application definition, + // so it's illegal to run into OPAQUE type in general decoder. + throw new Error("OPQAUE decoder is not defined."); + }, +}; + +this.PduHelper = { + + /** + * Parse WBXML encoded message into plain text. + * + * @param data + * A wrapped object containing raw PDU data. + * @param decodeInfo + * Information for decoding, now requires charset and string table. + * @param appToken + * Application-specific token difinition. + * + * @return Decoded WBXML message string. + */ + parseWbxml: function parseWbxml_wbxml(data, decodeInfo, appToken) { + + // Parse token definition to my structure. + decodeInfo.tokenList = { + tag: appToken.tagTokenList, + attr: appToken.attrTokenList, + value: appToken.valueTokenList + }; + decodeInfo.tagStack = []; // tag decode stack + decodeInfo.currentTokenList = { + tag: decodeInfo.tokenList.tag[0], + attr: decodeInfo.tokenList.attr[0], + value: decodeInfo.tokenList.value[0] + }; + decodeInfo.currentState = "tag"; // Current decoding state, "tag" or "attr" + // Used to read corresponding code page + // initial state : "tag" + + // Merge global tag tokens into single list, so we don't have + // to search two lists every time. + let globalTagTokenList = Object.create(WBXML_GLOBAL_TOKENS); + if (appToken.globalTokenOverride) { + let globalTokenOverrideList = appToken.globalTokenOverride; + for (let token in globalTokenOverrideList) { + globalTagTokenList[token] = globalTokenOverrideList[token]; + } + } + + let content = ""; + while (data.offset < data.array.length) { + // Decode content, might be a new tag token, an end of tag token, or an + // inline string. + + // Switch to tag domain + decodeInfo.currentState = "tag"; + + let tagToken = WSP.Octet.decode(data); + let tagTokenValue = tagToken & TAG_TOKEN_VALUE_MASK; + + // Try global tag first, tagToken of string table is 0x83, and will be 0x03 + // in tagTokenValue, which is collision with inline string. + // So tagToken need to be searched before tagTokenValue. + let tagInfo = globalTagTokenList[tagToken] || + globalTagTokenList[tagTokenValue]; + if (tagInfo) { + content += tagInfo.coder.decode(data, decodeInfo); + continue; + } + + // Check if application tag token is valid + tagInfo = decodeInfo.currentTokenList.tag[tagTokenValue]; + if (!tagInfo) { + throw new Error("Unsupported WBXML token: " + tagTokenValue + "."); + } + + content += "<" + tagInfo.name; + + if (tagToken & TAG_TOKEN_ATTR_MASK) { + // Decode attributes, might be a new attribute token, a value token, + // or an inline string + + // Switch to attr/value domain + decodeInfo.currentState = "attr"; + + let attrSeperator = ""; + while (data.offset < data.array.length) { + let attrToken = WSP.Octet.decode(data); + if (attrToken === TAG_END_TOKEN) { + break; + } + + let attrInfo = globalTagTokenList[attrToken]; + if (attrInfo) { + content += attrInfo.coder.decode(data, decodeInfo); + continue; + } + + // Check if attribute token is valid + attrInfo = decodeInfo.currentTokenList.attr[attrToken]; + if (attrInfo) { + content += attrSeperator + " " + attrInfo.name + "=\"" + attrInfo.value; + attrSeperator = "\""; + continue; + } + + attrInfo = decodeInfo.currentTokenList.value[attrToken]; + if (attrInfo) { + content += attrInfo.value; + continue; + } + + throw new Error("Unsupported WBXML token: " + attrToken + "."); + } + + content += attrSeperator; + } + + if (tagToken & TAG_TOKEN_CONTENT_MASK) { + content += ">"; + decodeInfo.tagStack.push(tagInfo); + continue; + } + + content += "/>"; + } + + return content; + }, + + /** + * @param data + * A wrapped object containing raw PDU data. + * @param appToken + * contains application-specific token info, including + * { + * publicId : Public identifier of application. + * tagToken : Ojbect defines application tag tokens. + * In form of + * Object[TAG_NAME] = Object[TOKEN_NUMBER] = + * { + * name: "TOKEN_NAME", + * number: TOKEN_NUMBER + * } + * attrToken : Object defines application attribute tokens. + * Object[ATTR_NAME] = Object[TOKEN_NUMBER] = + * { + * name: "ATTR_NAME", + * value: "ATTR_VALUE", + * number: TOKEN_NUMBER + * } + * For attribute value tokens, assign name as "" + * globalTokenOverride : Object overrides decoding behavior of global tokens. + * In form of + * Object[GLOBAL_TOKEN_NUMBER] = + * { + * decode: function(data), + * encode: function(data) + * } + * decode() returns decoded text, encode() returns + * encoded raw data. + * } + * + * @return A WBXML message object or null in case of errors found. + */ + parse: function parse_wbxml(data, appToken) { + let msg = {}; + + /** + * Read WBXML header. + * + * @see WAP-192-WBXML-20010725-A, Clause 5.3 + */ + let headerRaw = {}; + headerRaw.version = WSP.Octet.decode(data); + headerRaw.publicId = WSP.UintVar.decode(data); + if (headerRaw.publicId === 0) { + headerRaw.publicIdStr = WSP.UintVar.decode(data); + } + headerRaw.charset = WSP.UintVar.decode(data); + + let stringTableLen = WSP.UintVar.decode(data); + msg.stringTable = + WSP.Octet.decodeMultiple(data, data.offset + stringTableLen); + + // Transform raw header into user-friendly form + let entry = WSP.WSP_WELL_KNOWN_CHARSETS[headerRaw.charset]; + if (!entry) { + throw new Error("Charset is not supported."); + } + msg.charset = entry.name; + + if (headerRaw.publicId !== 0) { + msg.publicId = WBXML_PUBLIC_ID[headerRaw.publicId]; + } else { + msg.publicId = readStringTable(headerRaw.publicIdStr, msg.stringTable, + WSP.WSP_WELL_KNOWN_CHARSETS[msg.charset].converter); + } + if (msg.publicId != appToken.publicId) { + throw new Error("Public ID does not match."); + } + + msg.version = ((headerRaw.version >> 4) + 1) + "." + (headerRaw.version & 0x0F); + + let decodeInfo = { + charset: WSP.WSP_WELL_KNOWN_CHARSETS[msg.charset].converter, // document character set + stringTable: msg.stringTable // document string table + }; + msg.content = this.parseWbxml(data, decodeInfo, appToken); + + return msg; + } +}; + +/** + * Global Tokens + * + * @see WAP-192-WBXML-20010725-A, clause 7.1 + */ +const WBXML_GLOBAL_TOKENS = (function () { + let names = {}; + function add(number, coder) { + let entry = { + number: number, + coder: coder, + }; + names[number] = entry; + } + + add(CODE_PAGE_SWITCH_TOKEN, WbxmlCodePageSwitch); + add(TAG_END_TOKEN, WbxmlEnd); + add(INLINE_STRING_TOKEN, WbxmlInlineString); + add(STRING_TABLE_TOKEN, WbxmlStringTable); + add(OPAQUE_TOKEN, WbxmlOpaque); + + return names; +})(); + +/** + * Pre-defined public IDs + * + * @see http://technical.openmobilealliance.org/tech/omna/omna-wbxml-public-docid.aspx + */ +const WBXML_PUBLIC_ID = (function () { + let ids = {}; + function add(id, text) { + ids[id] = text; + } + + // Well Known Values + add(0x01, "UNKNOWN"); + add(0x02, "-//WAPFORUM//DTD WML 1.0//EN"); + add(0x03, "-//WAPFORUM//DTD WTA 1.0//EN"); + add(0x04, "-//WAPFORUM//DTD WML 1.1//EN"); + add(0x05, "-//WAPFORUM//DTD SI 1.0//EN"); + add(0x06, "-//WAPFORUM//DTD SL 1.0//EN"); + add(0x07, "-//WAPFORUM//DTD CO 1.0//EN"); + add(0x08, "-//WAPFORUM//DTD CHANNEL 1.1//EN"); + add(0x09, "-//WAPFORUM//DTD WML 1.2//EN"); + add(0x0A, "-//WAPFORUM//DTD WML 1.3//EN"); + add(0x0B, "-//WAPFORUM//DTD PROV 1.0//EN"); + add(0x0C, "-//WAPFORUM//DTD WTA-WML 1.2//EN"); + add(0x0D, "-//WAPFORUM//DTD EMN 1.0//EN"); + add(0x0E, "-//OMA//DTD DRMREL 1.0//EN"); + add(0x0F, "-//WIRELESSVILLAGE//DTD CSP 1.0//EN"); + add(0x10, "-//WIRELESSVILLAGE//DTD CSP 1.1//EN"); + add(0x11, "-//OMA//DTD WV-CSP 1.2//EN"); + add(0x12, "-//OMA//DTD IMPS-CSP 1.3//EN"); + add(0x13, "-//OMA//DRM 2.1//EN"); + add(0x14, "-//OMA//SRM 1.0//EN"); + add(0x15, "-//OMA//DCD 1.0//EN"); + add(0x16, "-//OMA//DTD DS-DataObjectEmail 1.2//EN"); + add(0x17, "-//OMA//DTD DS-DataObjectFolder 1.2//EN"); + add(0x18, "-//OMA//DTD DS-DataObjectFile 1.2//EN"); + + // Registered Values + add(0x0FD1, "-//SYNCML//DTD SyncML 1.0//EN"); + add(0x0FD2, "-//SYNCML//DTD DevInf 1.0//EN"); + add(0x0FD3, "-//SYNCML//DTD SyncML 1.1//EN"); + add(0x0FD4, "-//SYNCML//DTD DevInf 1.1//EN"); + add(0x1100, "-//PHONE.COM//DTD ALERT 1.0//EN"); + add(0x1101, "-//PHONE.COM//DTD CACHE-OPERATION 1.0//EN"); + add(0x1102, "-//PHONE.COM//DTD SIGNAL 1.0//EN"); + add(0x1103, "-//PHONE.COM//DTD LIST 1.0//EN"); + add(0x1104, "-//PHONE.COM//DTD LISTCMD 1.0//EN"); + add(0x1105, "-//PHONE.COM//DTD CHANNEL 1.0//EN"); + add(0x1106, "-//PHONE.COM//DTD MMC 1.0//EN"); + add(0x1107, "-//PHONE.COM//DTD BEARER-CHOICE 1.0//EN"); + add(0x1108, "-//PHONE.COM//DTD WML 1.1//EN"); + add(0x1109, "-//PHONE.COM//DTD CHANNEL 1.1//EN"); + add(0x110A, "-//PHONE.COM//DTD LIST 1.1//EN"); + add(0x110B, "-//PHONE.COM//DTD LISTCMD 1.1//EN"); + add(0x110C, "-//PHONE.COM//DTD MMC 1.1//EN"); + add(0x110D, "-//PHONE.COM//DTD WML 1.3//EN"); + add(0x110E, "-//PHONE.COM//DTD MMC 2.0//EN"); + add(0x1200, "-//3GPP2.COM//DTD IOTA 1.0//EN"); + add(0x1201, "-//SYNCML//DTD SyncML 1.2//EN"); + add(0x1202, "-//SYNCML//DTD MetaInf 1.2//EN"); + add(0x1203, "-//SYNCML//DTD DevInf 1.2//EN"); + add(0x1204, "-//NOKIA//DTD LANDMARKS 1.0//EN"); + add(0x1205, "-//SyncML//Schema SyncML 2.0//EN"); + add(0x1206, "-//SyncML//Schema DevInf 2.0//EN"); + add(0x1207, "-//OMA//DTD DRMREL 1.0//EN"); + + return ids; +})(); + +this.EXPORTED_SYMBOLS = [ + // Parser + "PduHelper", +];