|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
|
8 |
|
9 let WSP = {}; |
|
10 Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP); |
|
11 |
|
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; |
|
20 |
|
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; |
|
31 |
|
32 // Set to true to enable debug message on all WBXML decoders. |
|
33 this.DEBUG_ALL = false; |
|
34 |
|
35 // Enable debug message for WBXML decoder core. |
|
36 this.DEBUG = DEBUG_ALL | false; |
|
37 |
|
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); |
|
51 |
|
52 if (decodeInfo.currentState === "tag") { |
|
53 decodeInfo.currentTokenList.tag = decodeInfo.tokenList.tag[codePage]; |
|
54 |
|
55 if (!decodeInfo.currentTokenList.tag) { |
|
56 throw new Error("Invalid tag code page: " + codePage + "."); |
|
57 } |
|
58 |
|
59 return ""; |
|
60 } |
|
61 |
|
62 if (decodeInfo.currentState === "attr") { |
|
63 decodeInfo.currentTokenList.attr = decodeInfo.tokenList.attr[codePage]; |
|
64 decodeInfo.currentTokenList.value = decodeInfo.tokenList.value[codePage]; |
|
65 |
|
66 if (!decodeInfo.currentTokenList.attr || |
|
67 !decodeInfo.currentTokenList.value) { |
|
68 throw new Error("Invalid attr code page: " + codePage + "."); |
|
69 } |
|
70 |
|
71 return ""; |
|
72 } |
|
73 |
|
74 throw new Error("Invalid decoder state: " + decodeInfo.currentState + "."); |
|
75 }, |
|
76 }; |
|
77 |
|
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 }; |
|
95 |
|
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 = ""; |
|
110 |
|
111 for (var i = 0; i < str.length; i++) { |
|
112 switch (str[i]) { |
|
113 case '&' : dst += "&" ; break; |
|
114 case '<' : dst += "<" ; break; |
|
115 case '>' : dst += ">" ; break; |
|
116 case '"' : dst += """; break; |
|
117 case '\'': dst += "'"; break; |
|
118 default : dst += str[i]; |
|
119 } |
|
120 } |
|
121 |
|
122 return dst; |
|
123 } |
|
124 |
|
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; |
|
132 |
|
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 } |
|
141 |
|
142 // Read string table |
|
143 return WSP.PduHelper.decodeStringContent(stringTable.subarray(start, end), |
|
144 charset); |
|
145 }; |
|
146 |
|
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); |
|
151 |
|
152 return escapeReservedCharacters(str); |
|
153 } |
|
154 }; |
|
155 |
|
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 } |
|
175 |
|
176 let str = WSP.PduHelper.decodeStringContent(stringData, decodeInfo.charset); |
|
177 |
|
178 return escapeReservedCharacters(str); |
|
179 }, |
|
180 }; |
|
181 |
|
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 }; |
|
200 |
|
201 this.PduHelper = { |
|
202 |
|
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) { |
|
216 |
|
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" |
|
232 |
|
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 } |
|
242 |
|
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. |
|
247 |
|
248 // Switch to tag domain |
|
249 decodeInfo.currentState = "tag"; |
|
250 |
|
251 let tagToken = WSP.Octet.decode(data); |
|
252 let tagTokenValue = tagToken & TAG_TOKEN_VALUE_MASK; |
|
253 |
|
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 } |
|
263 |
|
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 } |
|
269 |
|
270 content += "<" + tagInfo.name; |
|
271 |
|
272 if (tagToken & TAG_TOKEN_ATTR_MASK) { |
|
273 // Decode attributes, might be a new attribute token, a value token, |
|
274 // or an inline string |
|
275 |
|
276 // Switch to attr/value domain |
|
277 decodeInfo.currentState = "attr"; |
|
278 |
|
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 } |
|
285 |
|
286 let attrInfo = globalTagTokenList[attrToken]; |
|
287 if (attrInfo) { |
|
288 content += attrInfo.coder.decode(data, decodeInfo); |
|
289 continue; |
|
290 } |
|
291 |
|
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 } |
|
299 |
|
300 attrInfo = decodeInfo.currentTokenList.value[attrToken]; |
|
301 if (attrInfo) { |
|
302 content += attrInfo.value; |
|
303 continue; |
|
304 } |
|
305 |
|
306 throw new Error("Unsupported WBXML token: " + attrToken + "."); |
|
307 } |
|
308 |
|
309 content += attrSeperator; |
|
310 } |
|
311 |
|
312 if (tagToken & TAG_TOKEN_CONTENT_MASK) { |
|
313 content += ">"; |
|
314 decodeInfo.tagStack.push(tagInfo); |
|
315 continue; |
|
316 } |
|
317 |
|
318 content += "/>"; |
|
319 } |
|
320 |
|
321 return content; |
|
322 }, |
|
323 |
|
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 = {}; |
|
361 |
|
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); |
|
374 |
|
375 let stringTableLen = WSP.UintVar.decode(data); |
|
376 msg.stringTable = |
|
377 WSP.Octet.decodeMultiple(data, data.offset + stringTableLen); |
|
378 |
|
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; |
|
385 |
|
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 } |
|
395 |
|
396 msg.version = ((headerRaw.version >> 4) + 1) + "." + (headerRaw.version & 0x0F); |
|
397 |
|
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); |
|
403 |
|
404 return msg; |
|
405 } |
|
406 }; |
|
407 |
|
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 } |
|
422 |
|
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); |
|
428 |
|
429 return names; |
|
430 })(); |
|
431 |
|
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 } |
|
442 |
|
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"); |
|
468 |
|
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"); |
|
497 |
|
498 return ids; |
|
499 })(); |
|
500 |
|
501 this.EXPORTED_SYMBOLS = [ |
|
502 // Parser |
|
503 "PduHelper", |
|
504 ]; |