1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/wappush/src/gonk/WbxmlPduHelper.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,504 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.11 + 1.12 +let WSP = {}; 1.13 +Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP); 1.14 + 1.15 +/** 1.16 + * Token flags 1.17 + * 1.18 + * @see WAP-192-WBXML-20010725-A, clause 5.8.2 1.19 + */ 1.20 +const TAG_TOKEN_ATTR_MASK = 0x80; 1.21 +const TAG_TOKEN_CONTENT_MASK = 0x40; 1.22 +const TAG_TOKEN_VALUE_MASK = 0x3F; 1.23 + 1.24 +/** 1.25 + * Global tokens 1.26 + * 1.27 + * @see WAP-192-WBXML-20010725-A, clause 7.1 1.28 + */ 1.29 +const CODE_PAGE_SWITCH_TOKEN = 0x00; 1.30 +const TAG_END_TOKEN = 0x01; 1.31 +const INLINE_STRING_TOKEN = 0x03; 1.32 +const STRING_TABLE_TOKEN = 0x83; 1.33 +const OPAQUE_TOKEN = 0xC3; 1.34 + 1.35 +// Set to true to enable debug message on all WBXML decoders. 1.36 +this.DEBUG_ALL = false; 1.37 + 1.38 +// Enable debug message for WBXML decoder core. 1.39 +this.DEBUG = DEBUG_ALL | false; 1.40 + 1.41 +/** 1.42 + * Handle WBXML code page switch. 1.43 + * 1.44 + * @param data 1.45 + * A wrapped object containing raw PDU data. 1.46 + * @param decodeInfo 1.47 + * Internal information for decode process. 1.48 + * 1.49 + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.7.2 and 5.8.1 1.50 + */ 1.51 +this.WbxmlCodePageSwitch = { 1.52 + decode: function decode_wbxml_code_page_switch(data, decodeInfo) { 1.53 + let codePage = WSP.Octet.decode(data); 1.54 + 1.55 + if (decodeInfo.currentState === "tag") { 1.56 + decodeInfo.currentTokenList.tag = decodeInfo.tokenList.tag[codePage]; 1.57 + 1.58 + if (!decodeInfo.currentTokenList.tag) { 1.59 + throw new Error("Invalid tag code page: " + codePage + "."); 1.60 + } 1.61 + 1.62 + return ""; 1.63 + } 1.64 + 1.65 + if (decodeInfo.currentState === "attr") { 1.66 + decodeInfo.currentTokenList.attr = decodeInfo.tokenList.attr[codePage]; 1.67 + decodeInfo.currentTokenList.value = decodeInfo.tokenList.value[codePage]; 1.68 + 1.69 + if (!decodeInfo.currentTokenList.attr || 1.70 + !decodeInfo.currentTokenList.value) { 1.71 + throw new Error("Invalid attr code page: " + codePage + "."); 1.72 + } 1.73 + 1.74 + return ""; 1.75 + } 1.76 + 1.77 + throw new Error("Invalid decoder state: " + decodeInfo.currentState + "."); 1.78 + }, 1.79 +}; 1.80 + 1.81 +/** 1.82 + * Parse end WBXML encoded message. 1.83 + * 1.84 + * @param data 1.85 + * A wrapped object containing raw PDU data. 1.86 + * @param decodeInfo 1.87 + * Internal information for decode process. 1.88 + * 1.89 + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.7.1 1.90 + * 1.91 + */ 1.92 +this.WbxmlEnd = { 1.93 + decode: function decode_wbxml_end(data, decodeInfo) { 1.94 + let tagInfo = decodeInfo.tagStack.pop(); 1.95 + return "</" + tagInfo.name + ">"; 1.96 + }, 1.97 +}; 1.98 + 1.99 +/** 1.100 + * Escape XML reserved characters &, <, >, " and ' which may appear in the 1.101 + * WBXML-encoded strings in their original form. 1.102 + * 1.103 + * @param str 1.104 + * A string with potentially unescaped characters 1.105 + * 1.106 + * @return A string with the &, <, >, " and ' characters turned into XML 1.107 + * character entitites 1.108 + * 1.109 + * @see WAP-192-WBXML-20010725-A, clause 6.1 1.110 + */ 1.111 +this.escapeReservedCharacters = function escape_reserved_characters(str) { 1.112 + let dst = ""; 1.113 + 1.114 + for (var i = 0; i < str.length; i++) { 1.115 + switch (str[i]) { 1.116 + case '&' : dst += "&" ; break; 1.117 + case '<' : dst += "<" ; break; 1.118 + case '>' : dst += ">" ; break; 1.119 + case '"' : dst += """; break; 1.120 + case '\'': dst += "'"; break; 1.121 + default : dst += str[i]; 1.122 + } 1.123 + } 1.124 + 1.125 + return dst; 1.126 +} 1.127 + 1.128 +/** 1.129 + * Handle string table in WBXML message. 1.130 + * 1.131 + * @see WAP-192-WBXML-20010725-A, clause 5.7 1.132 + */ 1.133 +this.readStringTable = function decode_wbxml_read_string_table(start, stringTable, charset) { 1.134 + let end = start; 1.135 + 1.136 + // Find end of string 1.137 + let stringTableLength = stringTable.length; 1.138 + while (end < stringTableLength) { 1.139 + if (stringTable[end] === 0) { 1.140 + break; 1.141 + } 1.142 + end++; 1.143 + } 1.144 + 1.145 + // Read string table 1.146 + return WSP.PduHelper.decodeStringContent(stringTable.subarray(start, end), 1.147 + charset); 1.148 +}; 1.149 + 1.150 +this.WbxmlStringTable = { 1.151 + decode: function decode_wbxml_string_table(data, decodeInfo) { 1.152 + let start = WSP.Octet.decode(data); 1.153 + let str = readStringTable(start, decodeInfo.stringTable, decodeInfo.charset); 1.154 + 1.155 + return escapeReservedCharacters(str); 1.156 + } 1.157 +}; 1.158 + 1.159 +/** 1.160 + * Parse inline string in WBXML encoded message. 1.161 + * 1.162 + * @param data 1.163 + * A wrapped object containing raw PDU data. 1.164 + * @param decodeInfo 1.165 + * Internal information for decode process. 1.166 + * 1.167 + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.1 1.168 + * 1.169 + */ 1.170 +this.WbxmlInlineString = { 1.171 + decode: function decode_wbxml_inline_string(data, decodeInfo) { 1.172 + let charCode = WSP.Octet.decode(data); 1.173 + let stringData = []; 1.174 + while (charCode) { 1.175 + stringData.push(charCode); 1.176 + charCode = WSP.Octet.decode(data); 1.177 + } 1.178 + 1.179 + let str = WSP.PduHelper.decodeStringContent(stringData, decodeInfo.charset); 1.180 + 1.181 + return escapeReservedCharacters(str); 1.182 + }, 1.183 +}; 1.184 + 1.185 +/** 1.186 + * Parse inline Opaque data in WBXML encoded message. 1.187 + * 1.188 + * @param data 1.189 + * A wrapped object containing raw PDU data. 1.190 + * @param decodeInfo 1.191 + * Internal information for decode process. 1.192 + * 1.193 + * @see WAP-192-WBXML-20010725-A, clause 5.8.4.6 1.194 + * 1.195 + */ 1.196 +this.WbxmlOpaque = { 1.197 + decode: function decode_wbxml_inline_opaque(data) { 1.198 + // Inline OPAQUE must be decoded based on application definition, 1.199 + // so it's illegal to run into OPAQUE type in general decoder. 1.200 + throw new Error("OPQAUE decoder is not defined."); 1.201 + }, 1.202 +}; 1.203 + 1.204 +this.PduHelper = { 1.205 + 1.206 + /** 1.207 + * Parse WBXML encoded message into plain text. 1.208 + * 1.209 + * @param data 1.210 + * A wrapped object containing raw PDU data. 1.211 + * @param decodeInfo 1.212 + * Information for decoding, now requires charset and string table. 1.213 + * @param appToken 1.214 + * Application-specific token difinition. 1.215 + * 1.216 + * @return Decoded WBXML message string. 1.217 + */ 1.218 + parseWbxml: function parseWbxml_wbxml(data, decodeInfo, appToken) { 1.219 + 1.220 + // Parse token definition to my structure. 1.221 + decodeInfo.tokenList = { 1.222 + tag: appToken.tagTokenList, 1.223 + attr: appToken.attrTokenList, 1.224 + value: appToken.valueTokenList 1.225 + }; 1.226 + decodeInfo.tagStack = []; // tag decode stack 1.227 + decodeInfo.currentTokenList = { 1.228 + tag: decodeInfo.tokenList.tag[0], 1.229 + attr: decodeInfo.tokenList.attr[0], 1.230 + value: decodeInfo.tokenList.value[0] 1.231 + }; 1.232 + decodeInfo.currentState = "tag"; // Current decoding state, "tag" or "attr" 1.233 + // Used to read corresponding code page 1.234 + // initial state : "tag" 1.235 + 1.236 + // Merge global tag tokens into single list, so we don't have 1.237 + // to search two lists every time. 1.238 + let globalTagTokenList = Object.create(WBXML_GLOBAL_TOKENS); 1.239 + if (appToken.globalTokenOverride) { 1.240 + let globalTokenOverrideList = appToken.globalTokenOverride; 1.241 + for (let token in globalTokenOverrideList) { 1.242 + globalTagTokenList[token] = globalTokenOverrideList[token]; 1.243 + } 1.244 + } 1.245 + 1.246 + let content = ""; 1.247 + while (data.offset < data.array.length) { 1.248 + // Decode content, might be a new tag token, an end of tag token, or an 1.249 + // inline string. 1.250 + 1.251 + // Switch to tag domain 1.252 + decodeInfo.currentState = "tag"; 1.253 + 1.254 + let tagToken = WSP.Octet.decode(data); 1.255 + let tagTokenValue = tagToken & TAG_TOKEN_VALUE_MASK; 1.256 + 1.257 + // Try global tag first, tagToken of string table is 0x83, and will be 0x03 1.258 + // in tagTokenValue, which is collision with inline string. 1.259 + // So tagToken need to be searched before tagTokenValue. 1.260 + let tagInfo = globalTagTokenList[tagToken] || 1.261 + globalTagTokenList[tagTokenValue]; 1.262 + if (tagInfo) { 1.263 + content += tagInfo.coder.decode(data, decodeInfo); 1.264 + continue; 1.265 + } 1.266 + 1.267 + // Check if application tag token is valid 1.268 + tagInfo = decodeInfo.currentTokenList.tag[tagTokenValue]; 1.269 + if (!tagInfo) { 1.270 + throw new Error("Unsupported WBXML token: " + tagTokenValue + "."); 1.271 + } 1.272 + 1.273 + content += "<" + tagInfo.name; 1.274 + 1.275 + if (tagToken & TAG_TOKEN_ATTR_MASK) { 1.276 + // Decode attributes, might be a new attribute token, a value token, 1.277 + // or an inline string 1.278 + 1.279 + // Switch to attr/value domain 1.280 + decodeInfo.currentState = "attr"; 1.281 + 1.282 + let attrSeperator = ""; 1.283 + while (data.offset < data.array.length) { 1.284 + let attrToken = WSP.Octet.decode(data); 1.285 + if (attrToken === TAG_END_TOKEN) { 1.286 + break; 1.287 + } 1.288 + 1.289 + let attrInfo = globalTagTokenList[attrToken]; 1.290 + if (attrInfo) { 1.291 + content += attrInfo.coder.decode(data, decodeInfo); 1.292 + continue; 1.293 + } 1.294 + 1.295 + // Check if attribute token is valid 1.296 + attrInfo = decodeInfo.currentTokenList.attr[attrToken]; 1.297 + if (attrInfo) { 1.298 + content += attrSeperator + " " + attrInfo.name + "=\"" + attrInfo.value; 1.299 + attrSeperator = "\""; 1.300 + continue; 1.301 + } 1.302 + 1.303 + attrInfo = decodeInfo.currentTokenList.value[attrToken]; 1.304 + if (attrInfo) { 1.305 + content += attrInfo.value; 1.306 + continue; 1.307 + } 1.308 + 1.309 + throw new Error("Unsupported WBXML token: " + attrToken + "."); 1.310 + } 1.311 + 1.312 + content += attrSeperator; 1.313 + } 1.314 + 1.315 + if (tagToken & TAG_TOKEN_CONTENT_MASK) { 1.316 + content += ">"; 1.317 + decodeInfo.tagStack.push(tagInfo); 1.318 + continue; 1.319 + } 1.320 + 1.321 + content += "/>"; 1.322 + } 1.323 + 1.324 + return content; 1.325 + }, 1.326 + 1.327 + /** 1.328 + * @param data 1.329 + * A wrapped object containing raw PDU data. 1.330 + * @param appToken 1.331 + * contains application-specific token info, including 1.332 + * { 1.333 + * publicId : Public identifier of application. 1.334 + * tagToken : Ojbect defines application tag tokens. 1.335 + * In form of 1.336 + * Object[TAG_NAME] = Object[TOKEN_NUMBER] = 1.337 + * { 1.338 + * name: "TOKEN_NAME", 1.339 + * number: TOKEN_NUMBER 1.340 + * } 1.341 + * attrToken : Object defines application attribute tokens. 1.342 + * Object[ATTR_NAME] = Object[TOKEN_NUMBER] = 1.343 + * { 1.344 + * name: "ATTR_NAME", 1.345 + * value: "ATTR_VALUE", 1.346 + * number: TOKEN_NUMBER 1.347 + * } 1.348 + * For attribute value tokens, assign name as "" 1.349 + * globalTokenOverride : Object overrides decoding behavior of global tokens. 1.350 + * In form of 1.351 + * Object[GLOBAL_TOKEN_NUMBER] = 1.352 + * { 1.353 + * decode: function(data), 1.354 + * encode: function(data) 1.355 + * } 1.356 + * decode() returns decoded text, encode() returns 1.357 + * encoded raw data. 1.358 + * } 1.359 + * 1.360 + * @return A WBXML message object or null in case of errors found. 1.361 + */ 1.362 + parse: function parse_wbxml(data, appToken) { 1.363 + let msg = {}; 1.364 + 1.365 + /** 1.366 + * Read WBXML header. 1.367 + * 1.368 + * @see WAP-192-WBXML-20010725-A, Clause 5.3 1.369 + */ 1.370 + let headerRaw = {}; 1.371 + headerRaw.version = WSP.Octet.decode(data); 1.372 + headerRaw.publicId = WSP.UintVar.decode(data); 1.373 + if (headerRaw.publicId === 0) { 1.374 + headerRaw.publicIdStr = WSP.UintVar.decode(data); 1.375 + } 1.376 + headerRaw.charset = WSP.UintVar.decode(data); 1.377 + 1.378 + let stringTableLen = WSP.UintVar.decode(data); 1.379 + msg.stringTable = 1.380 + WSP.Octet.decodeMultiple(data, data.offset + stringTableLen); 1.381 + 1.382 + // Transform raw header into user-friendly form 1.383 + let entry = WSP.WSP_WELL_KNOWN_CHARSETS[headerRaw.charset]; 1.384 + if (!entry) { 1.385 + throw new Error("Charset is not supported."); 1.386 + } 1.387 + msg.charset = entry.name; 1.388 + 1.389 + if (headerRaw.publicId !== 0) { 1.390 + msg.publicId = WBXML_PUBLIC_ID[headerRaw.publicId]; 1.391 + } else { 1.392 + msg.publicId = readStringTable(headerRaw.publicIdStr, msg.stringTable, 1.393 + WSP.WSP_WELL_KNOWN_CHARSETS[msg.charset].converter); 1.394 + } 1.395 + if (msg.publicId != appToken.publicId) { 1.396 + throw new Error("Public ID does not match."); 1.397 + } 1.398 + 1.399 + msg.version = ((headerRaw.version >> 4) + 1) + "." + (headerRaw.version & 0x0F); 1.400 + 1.401 + let decodeInfo = { 1.402 + charset: WSP.WSP_WELL_KNOWN_CHARSETS[msg.charset].converter, // document character set 1.403 + stringTable: msg.stringTable // document string table 1.404 + }; 1.405 + msg.content = this.parseWbxml(data, decodeInfo, appToken); 1.406 + 1.407 + return msg; 1.408 + } 1.409 +}; 1.410 + 1.411 +/** 1.412 + * Global Tokens 1.413 + * 1.414 + * @see WAP-192-WBXML-20010725-A, clause 7.1 1.415 + */ 1.416 +const WBXML_GLOBAL_TOKENS = (function () { 1.417 + let names = {}; 1.418 + function add(number, coder) { 1.419 + let entry = { 1.420 + number: number, 1.421 + coder: coder, 1.422 + }; 1.423 + names[number] = entry; 1.424 + } 1.425 + 1.426 + add(CODE_PAGE_SWITCH_TOKEN, WbxmlCodePageSwitch); 1.427 + add(TAG_END_TOKEN, WbxmlEnd); 1.428 + add(INLINE_STRING_TOKEN, WbxmlInlineString); 1.429 + add(STRING_TABLE_TOKEN, WbxmlStringTable); 1.430 + add(OPAQUE_TOKEN, WbxmlOpaque); 1.431 + 1.432 + return names; 1.433 +})(); 1.434 + 1.435 +/** 1.436 + * Pre-defined public IDs 1.437 + * 1.438 + * @see http://technical.openmobilealliance.org/tech/omna/omna-wbxml-public-docid.aspx 1.439 + */ 1.440 +const WBXML_PUBLIC_ID = (function () { 1.441 + let ids = {}; 1.442 + function add(id, text) { 1.443 + ids[id] = text; 1.444 + } 1.445 + 1.446 + // Well Known Values 1.447 + add(0x01, "UNKNOWN"); 1.448 + add(0x02, "-//WAPFORUM//DTD WML 1.0//EN"); 1.449 + add(0x03, "-//WAPFORUM//DTD WTA 1.0//EN"); 1.450 + add(0x04, "-//WAPFORUM//DTD WML 1.1//EN"); 1.451 + add(0x05, "-//WAPFORUM//DTD SI 1.0//EN"); 1.452 + add(0x06, "-//WAPFORUM//DTD SL 1.0//EN"); 1.453 + add(0x07, "-//WAPFORUM//DTD CO 1.0//EN"); 1.454 + add(0x08, "-//WAPFORUM//DTD CHANNEL 1.1//EN"); 1.455 + add(0x09, "-//WAPFORUM//DTD WML 1.2//EN"); 1.456 + add(0x0A, "-//WAPFORUM//DTD WML 1.3//EN"); 1.457 + add(0x0B, "-//WAPFORUM//DTD PROV 1.0//EN"); 1.458 + add(0x0C, "-//WAPFORUM//DTD WTA-WML 1.2//EN"); 1.459 + add(0x0D, "-//WAPFORUM//DTD EMN 1.0//EN"); 1.460 + add(0x0E, "-//OMA//DTD DRMREL 1.0//EN"); 1.461 + add(0x0F, "-//WIRELESSVILLAGE//DTD CSP 1.0//EN"); 1.462 + add(0x10, "-//WIRELESSVILLAGE//DTD CSP 1.1//EN"); 1.463 + add(0x11, "-//OMA//DTD WV-CSP 1.2//EN"); 1.464 + add(0x12, "-//OMA//DTD IMPS-CSP 1.3//EN"); 1.465 + add(0x13, "-//OMA//DRM 2.1//EN"); 1.466 + add(0x14, "-//OMA//SRM 1.0//EN"); 1.467 + add(0x15, "-//OMA//DCD 1.0//EN"); 1.468 + add(0x16, "-//OMA//DTD DS-DataObjectEmail 1.2//EN"); 1.469 + add(0x17, "-//OMA//DTD DS-DataObjectFolder 1.2//EN"); 1.470 + add(0x18, "-//OMA//DTD DS-DataObjectFile 1.2//EN"); 1.471 + 1.472 + // Registered Values 1.473 + add(0x0FD1, "-//SYNCML//DTD SyncML 1.0//EN"); 1.474 + add(0x0FD2, "-//SYNCML//DTD DevInf 1.0//EN"); 1.475 + add(0x0FD3, "-//SYNCML//DTD SyncML 1.1//EN"); 1.476 + add(0x0FD4, "-//SYNCML//DTD DevInf 1.1//EN"); 1.477 + add(0x1100, "-//PHONE.COM//DTD ALERT 1.0//EN"); 1.478 + add(0x1101, "-//PHONE.COM//DTD CACHE-OPERATION 1.0//EN"); 1.479 + add(0x1102, "-//PHONE.COM//DTD SIGNAL 1.0//EN"); 1.480 + add(0x1103, "-//PHONE.COM//DTD LIST 1.0//EN"); 1.481 + add(0x1104, "-//PHONE.COM//DTD LISTCMD 1.0//EN"); 1.482 + add(0x1105, "-//PHONE.COM//DTD CHANNEL 1.0//EN"); 1.483 + add(0x1106, "-//PHONE.COM//DTD MMC 1.0//EN"); 1.484 + add(0x1107, "-//PHONE.COM//DTD BEARER-CHOICE 1.0//EN"); 1.485 + add(0x1108, "-//PHONE.COM//DTD WML 1.1//EN"); 1.486 + add(0x1109, "-//PHONE.COM//DTD CHANNEL 1.1//EN"); 1.487 + add(0x110A, "-//PHONE.COM//DTD LIST 1.1//EN"); 1.488 + add(0x110B, "-//PHONE.COM//DTD LISTCMD 1.1//EN"); 1.489 + add(0x110C, "-//PHONE.COM//DTD MMC 1.1//EN"); 1.490 + add(0x110D, "-//PHONE.COM//DTD WML 1.3//EN"); 1.491 + add(0x110E, "-//PHONE.COM//DTD MMC 2.0//EN"); 1.492 + add(0x1200, "-//3GPP2.COM//DTD IOTA 1.0//EN"); 1.493 + add(0x1201, "-//SYNCML//DTD SyncML 1.2//EN"); 1.494 + add(0x1202, "-//SYNCML//DTD MetaInf 1.2//EN"); 1.495 + add(0x1203, "-//SYNCML//DTD DevInf 1.2//EN"); 1.496 + add(0x1204, "-//NOKIA//DTD LANDMARKS 1.0//EN"); 1.497 + add(0x1205, "-//SyncML//Schema SyncML 2.0//EN"); 1.498 + add(0x1206, "-//SyncML//Schema DevInf 2.0//EN"); 1.499 + add(0x1207, "-//OMA//DTD DRMREL 1.0//EN"); 1.500 + 1.501 + return ids; 1.502 +})(); 1.503 + 1.504 +this.EXPORTED_SYMBOLS = [ 1.505 + // Parser 1.506 + "PduHelper", 1.507 +];