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