|
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 Cu.import("resource://gre/modules/wap_consts.js", this); |
|
10 |
|
11 let DEBUG; // set to true to see debug messages |
|
12 |
|
13 // Special ASCII characters |
|
14 const NUL = 0; |
|
15 const CR = 13; |
|
16 const LF = 10; |
|
17 const SP = 32; |
|
18 const HT = 9; |
|
19 const DQUOTE = 34; |
|
20 const DEL = 127; |
|
21 |
|
22 // Special ASCII character ranges |
|
23 const CTLS = 32; |
|
24 const ASCIIS = 128; |
|
25 |
|
26 /** |
|
27 * Error class for generic encoding/decoding failures. |
|
28 */ |
|
29 this.CodeError = function CodeError(message) { |
|
30 this.name = "CodeError"; |
|
31 this.message = message || "Invalid format"; |
|
32 } |
|
33 CodeError.prototype = new Error(); |
|
34 CodeError.prototype.constructor = CodeError; |
|
35 |
|
36 /** |
|
37 * Error class for unexpected NUL char at decoding text elements. |
|
38 * |
|
39 * @param message [optional] |
|
40 * A short description for the error. |
|
41 */ |
|
42 function NullCharError(message) { |
|
43 this.name = "NullCharError"; |
|
44 this.message = message || "Null character found"; |
|
45 } |
|
46 NullCharError.prototype = new CodeError(); |
|
47 NullCharError.prototype.constructor = NullCharError; |
|
48 |
|
49 /** |
|
50 * Error class for fatal encoding/decoding failures. |
|
51 * |
|
52 * This error is only raised when expected format isn't met and the parser |
|
53 * context can't do anything more to either skip it or hand over to other |
|
54 * alternative encoding/decoding steps. |
|
55 * |
|
56 * @param message [optional] |
|
57 * A short description for the error. |
|
58 */ |
|
59 this.FatalCodeError = function FatalCodeError(message) { |
|
60 this.name = "FatalCodeError"; |
|
61 this.message = message || "Decoding fails"; |
|
62 } |
|
63 FatalCodeError.prototype = new Error(); |
|
64 FatalCodeError.prototype.constructor = FatalCodeError; |
|
65 |
|
66 /** |
|
67 * Error class for undefined well known encoding. |
|
68 * |
|
69 * When a encoded header field/parameter has unknown/unsupported value, we may |
|
70 * never know how to decode the next value. For example, a parameter of |
|
71 * undefined well known encoding may be followed by a Q-value, which is |
|
72 * basically a uintvar. However, there is no way you can distiguish an Q-value |
|
73 * 0.64, encoded as 0x41, from a string begins with 'A', which is also 0x41. |
|
74 * The `skipValue` will try the latter one, which is not expected. |
|
75 * |
|
76 * @param message [optional] |
|
77 * A short description for the error. |
|
78 */ |
|
79 this.NotWellKnownEncodingError = function NotWellKnownEncodingError(message) { |
|
80 this.name = "NotWellKnownEncodingError"; |
|
81 this.message = message || "Not well known encoding"; |
|
82 } |
|
83 NotWellKnownEncodingError.prototype = new FatalCodeError(); |
|
84 NotWellKnownEncodingError.prototype.constructor = NotWellKnownEncodingError; |
|
85 |
|
86 /** |
|
87 * Internal helper function to retrieve the value of a property with its name |
|
88 * specified by `name` inside the object `headers`. |
|
89 * |
|
90 * @param headers |
|
91 * An object that contains parsed header fields. |
|
92 * @param name |
|
93 * Header name string to be checked. |
|
94 * |
|
95 * @return Value of specified header field. |
|
96 * |
|
97 * @throws FatalCodeError if headers[name] is undefined. |
|
98 */ |
|
99 this.ensureHeader = function ensureHeader(headers, name) { |
|
100 let value = headers[name]; |
|
101 // Header field might have a null value as NoValue |
|
102 if (value === undefined) { |
|
103 throw new FatalCodeError("ensureHeader: header " + name + " not defined"); |
|
104 } |
|
105 return value; |
|
106 } |
|
107 |
|
108 /** |
|
109 * Skip field value. |
|
110 * |
|
111 * The WSP field values are encoded so that the length of the field value can |
|
112 * always be determined, even if the detailed format of a specific field value |
|
113 * is not known. This makes it possible to skip over individual header fields |
|
114 * without interpreting their content. ... the first octet in all the field |
|
115 * values can be interpreted as follows: |
|
116 * |
|
117 * 0 - 30 | This octet is followed by the indicated number (0 - 30) of data |
|
118 * octets. |
|
119 * 31 | This octet is followed by a unitvar, which indicates the number |
|
120 * of data octets after it. |
|
121 * 32 - 127 | The value is a text string, terminated by a zero octet (NUL |
|
122 * character). |
|
123 * 128 - 255 | It is an encoded 7-bit value; this header has no more data. |
|
124 * |
|
125 * @param data |
|
126 * A wrapped object containing raw PDU data. |
|
127 * |
|
128 * @return Skipped value of several possible types like string, integer, or |
|
129 * an array of octets. |
|
130 * |
|
131 * @see WAP-230-WSP-20010705-a clause 8.4.1.2 |
|
132 */ |
|
133 this.skipValue = function skipValue(data) { |
|
134 let begin = data.offset; |
|
135 let value = Octet.decode(data); |
|
136 if (value <= 31) { |
|
137 if (value == 31) { |
|
138 value = UintVar.decode(data); |
|
139 } |
|
140 |
|
141 if (value) { |
|
142 // `value` can be larger than 30, max length of a multi-octet integer |
|
143 // here. So we must decode it as an array instead. |
|
144 value = Octet.decodeMultiple(data, data.offset + value); |
|
145 } else { |
|
146 value = null; |
|
147 } |
|
148 } else if (value <= 127) { |
|
149 data.offset = begin; |
|
150 value = NullTerminatedTexts.decode(data); |
|
151 } else { |
|
152 value &= 0x7F; |
|
153 } |
|
154 |
|
155 return value; |
|
156 } |
|
157 |
|
158 /** |
|
159 * Helper function for decoding multiple alternative forms. |
|
160 * |
|
161 * @param data |
|
162 * A wrapped object containing raw PDU data. |
|
163 * @param options |
|
164 * Extra context for decoding. |
|
165 * |
|
166 * @return Decoded value. |
|
167 */ |
|
168 this.decodeAlternatives = function decodeAlternatives(data, options) { |
|
169 let begin = data.offset; |
|
170 for (let i = 2; i < arguments.length; i++) { |
|
171 try { |
|
172 return arguments[i].decode(data, options); |
|
173 } catch (e) { |
|
174 // Throw the last exception we get |
|
175 if (i == (arguments.length - 1)) { |
|
176 throw e; |
|
177 } |
|
178 |
|
179 data.offset = begin; |
|
180 } |
|
181 } |
|
182 } |
|
183 |
|
184 /** |
|
185 * Helper function for encoding multiple alternative forms. |
|
186 * |
|
187 * @param data |
|
188 * A wrapped object to store encoded raw data. |
|
189 * @param value |
|
190 * Object value of arbitrary type to be encoded. |
|
191 * @param options |
|
192 * Extra context for encoding. |
|
193 */ |
|
194 this.encodeAlternatives = function encodeAlternatives(data, value, options) { |
|
195 let begin = data.offset; |
|
196 for (let i = 3; i < arguments.length; i++) { |
|
197 try { |
|
198 arguments[i].encode(data, value, options); |
|
199 return; |
|
200 } catch (e) { |
|
201 // Throw the last exception we get |
|
202 if (i == (arguments.length - 1)) { |
|
203 throw e; |
|
204 } |
|
205 |
|
206 data.offset = begin; |
|
207 } |
|
208 } |
|
209 } |
|
210 |
|
211 this.Octet = { |
|
212 /** |
|
213 * @param data |
|
214 * A wrapped object containing raw PDU data. |
|
215 * |
|
216 * @throws RangeError if no more data is available. |
|
217 */ |
|
218 decode: function(data) { |
|
219 if (data.offset >= data.array.length) { |
|
220 throw new RangeError(); |
|
221 } |
|
222 |
|
223 return data.array[data.offset++]; |
|
224 }, |
|
225 |
|
226 /** |
|
227 * @param data |
|
228 * A wrapped object containing raw PDU data. |
|
229 * @param end |
|
230 * An ending offset indicating the end of octet array to read. |
|
231 * |
|
232 * @return A decoded array object. |
|
233 * |
|
234 * @throws RangeError if no enough data to read. |
|
235 * @throws TypeError if `data` has neither subarray() nor slice() method. |
|
236 */ |
|
237 decodeMultiple: function(data, end) { |
|
238 if ((end < data.offset) || (end > data.array.length)) { |
|
239 throw new RangeError(); |
|
240 } |
|
241 if (end == data.offset) { |
|
242 return []; |
|
243 } |
|
244 |
|
245 let result; |
|
246 if (data.array.subarray) { |
|
247 result = data.array.subarray(data.offset, end); |
|
248 } else if (data.array.slice) { |
|
249 result = data.array.slice(data.offset, end); |
|
250 } else { |
|
251 throw new TypeError(); |
|
252 } |
|
253 |
|
254 data.offset = end; |
|
255 return result; |
|
256 }, |
|
257 |
|
258 /** |
|
259 * Internal octet decoding for specific value. |
|
260 * |
|
261 * @param data |
|
262 * A wrapped object containing raw PDU data. |
|
263 * @param expected |
|
264 * Expected octet value. |
|
265 * |
|
266 * @return Expected octet value. |
|
267 * |
|
268 * @throws CodeError if read octet is not equal to expected one. |
|
269 */ |
|
270 decodeEqualTo: function(data, expected) { |
|
271 if (this.decode(data) != expected) { |
|
272 throw new CodeError("Octet - decodeEqualTo: doesn't match " + expected); |
|
273 } |
|
274 |
|
275 return expected; |
|
276 }, |
|
277 |
|
278 /** |
|
279 * @param data |
|
280 * A wrapped object to store encoded raw data. |
|
281 * @param octet |
|
282 * Octet value to be encoded. |
|
283 */ |
|
284 encode: function(data, octet) { |
|
285 if (data.offset >= data.array.length) { |
|
286 data.array.push(octet); |
|
287 data.offset++; |
|
288 } else { |
|
289 data.array[data.offset++] = octet; |
|
290 } |
|
291 }, |
|
292 |
|
293 /** |
|
294 * @param data |
|
295 * A wrapped object to store encoded raw data. |
|
296 * @param octet |
|
297 * An octet array object. |
|
298 */ |
|
299 encodeMultiple: function(data, array) { |
|
300 for (let i = 0; i < array.length; i++) { |
|
301 this.encode(data, array[i]); |
|
302 } |
|
303 }, |
|
304 }; |
|
305 |
|
306 /** |
|
307 * TEXT = <any OCTET except CTLs, but including LWS> |
|
308 * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> |
|
309 * LWS = [CRLF] 1*(SP|HT) |
|
310 * CRLF = CR LF |
|
311 * CR = <US-ASCII CR, carriage return (13)> |
|
312 * LF = <US-ASCII LF, linefeed (10)> |
|
313 * SP = <US-ASCII SP, space (32)> |
|
314 * HT = <US-ASCII HT, horizontal-tab(9)> |
|
315 * |
|
316 * @see RFC 2616 clause 2.2 Basic Rules |
|
317 */ |
|
318 this.Text = { |
|
319 /** |
|
320 * @param data |
|
321 * A wrapped object containing raw PDU data. |
|
322 * |
|
323 * @return Decoded character. |
|
324 * |
|
325 * @throws NullCharError if a NUL character read. |
|
326 * @throws CodeError if a control character read. |
|
327 */ |
|
328 decode: function(data) { |
|
329 let code = Octet.decode(data); |
|
330 if ((code >= CTLS) && (code != DEL)) { |
|
331 return String.fromCharCode(code); |
|
332 } |
|
333 |
|
334 if (code == NUL) { |
|
335 throw new NullCharError(); |
|
336 } |
|
337 |
|
338 if (code != CR) { |
|
339 throw new CodeError("Text: invalid char code " + code); |
|
340 } |
|
341 |
|
342 // "A CRLF is allowed in the definition of TEXT only as part of a header |
|
343 // field continuation. It is expected that the folding LWS will be |
|
344 // replaced with a single SP before interpretation of the TEXT value." |
|
345 // ~ RFC 2616 clause 2.2 |
|
346 |
|
347 let extra; |
|
348 |
|
349 // Rethrow everything as CodeError. We had already a successful read above. |
|
350 try { |
|
351 extra = Octet.decode(data); |
|
352 if (extra != LF) { |
|
353 throw new CodeError("Text: doesn't match LWS sequence"); |
|
354 } |
|
355 |
|
356 extra = Octet.decode(data); |
|
357 if ((extra != SP) && (extra != HT)) { |
|
358 throw new CodeError("Text: doesn't match LWS sequence"); |
|
359 } |
|
360 } catch (e if e instanceof CodeError) { |
|
361 throw e; |
|
362 } catch (e) { |
|
363 throw new CodeError("Text: doesn't match LWS sequence"); |
|
364 } |
|
365 |
|
366 // Let's eat as many SP|HT as possible. |
|
367 let begin; |
|
368 |
|
369 // Do not throw anything here. We had already matched (SP | HT). |
|
370 try { |
|
371 do { |
|
372 begin = data.offset; |
|
373 extra = Octet.decode(data); |
|
374 } while ((extra == SP) || (extra == HT)); |
|
375 } catch (e) {} |
|
376 |
|
377 data.offset = begin; |
|
378 return " "; |
|
379 }, |
|
380 |
|
381 /** |
|
382 * @param data |
|
383 * A wrapped object to store encoded raw data. |
|
384 * @param text |
|
385 * String text of one character to be encoded. |
|
386 * @param asciiOnly |
|
387 * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). |
|
388 * |
|
389 * @throws CodeError if a control character got. |
|
390 */ |
|
391 encode: function(data, text, asciiOnly) { |
|
392 if (!text) { |
|
393 throw new CodeError("Text: empty string"); |
|
394 } |
|
395 |
|
396 let code = text.charCodeAt(0); |
|
397 if ((code < CTLS) || (code == DEL) || (code > 255) || |
|
398 (code >= 128 && asciiOnly)) { |
|
399 throw new CodeError("Text: invalid char code " + code); |
|
400 } |
|
401 Octet.encode(data, code); |
|
402 }, |
|
403 }; |
|
404 |
|
405 this.NullTerminatedTexts = { |
|
406 /** |
|
407 * Decode internal referenced null terminated text string. |
|
408 * |
|
409 * @param data |
|
410 * A wrapped object containing raw PDU data. |
|
411 * |
|
412 * @return Decoded string. |
|
413 */ |
|
414 decode: function(data) { |
|
415 let str = ""; |
|
416 try { |
|
417 // A End-of-string is also a CTL, which should cause a error. |
|
418 while (true) { |
|
419 str += Text.decode(data); |
|
420 } |
|
421 } catch (e if e instanceof NullCharError) { |
|
422 return str; |
|
423 } |
|
424 }, |
|
425 |
|
426 /** |
|
427 * @param data |
|
428 * A wrapped object to store encoded raw data. |
|
429 * @param str |
|
430 * A String to be encoded. |
|
431 * @param asciiOnly |
|
432 * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). |
|
433 */ |
|
434 encode: function(data, str, asciiOnly) { |
|
435 if (str) { |
|
436 for (let i = 0; i < str.length; i++) { |
|
437 Text.encode(data, str.charAt(i), asciiOnly); |
|
438 } |
|
439 } |
|
440 Octet.encode(data, 0); |
|
441 }, |
|
442 }; |
|
443 |
|
444 /** |
|
445 * TOKEN = 1*<any CHAR except CTLs or separators> |
|
446 * CHAR = <any US-ASCII character (octets 0 - 127)> |
|
447 * SEPARATORS = ()<>@,;:\"/[]?={} SP HT |
|
448 * |
|
449 * @see RFC 2616 clause 2.2 Basic Rules |
|
450 */ |
|
451 this.Token = { |
|
452 /** |
|
453 * @param data |
|
454 * A wrapped object containing raw PDU data. |
|
455 * |
|
456 * @return Decoded character. |
|
457 * |
|
458 * @throws NullCharError if a NUL character read. |
|
459 * @throws CodeError if an invalid character read. |
|
460 */ |
|
461 decode: function(data) { |
|
462 let code = Octet.decode(data); |
|
463 if ((code < ASCIIS) && (code >= CTLS)) { |
|
464 if ((code == HT) || (code == SP) |
|
465 || (code == 34) || (code == 40) || (code == 41) // ASCII "() |
|
466 || (code == 44) || (code == 47) // ASCII ,/ |
|
467 || ((code >= 58) && (code <= 64)) // ASCII :;<=>?@ |
|
468 || ((code >= 91) && (code <= 93)) // ASCII [\] |
|
469 || (code == 123) || (code == 125)) { // ASCII {} |
|
470 throw new CodeError("Token: invalid char code " + code); |
|
471 } |
|
472 |
|
473 return String.fromCharCode(code); |
|
474 } |
|
475 |
|
476 if (code == NUL) { |
|
477 throw new NullCharError(); |
|
478 } |
|
479 |
|
480 throw new CodeError("Token: invalid char code " + code); |
|
481 }, |
|
482 |
|
483 /** |
|
484 * @param data |
|
485 * A wrapped object to store encoded raw data. |
|
486 * @param token |
|
487 * String text of one character to be encoded. |
|
488 * |
|
489 * @throws CodeError if an invalid character got. |
|
490 */ |
|
491 encode: function(data, token) { |
|
492 if (!token) { |
|
493 throw new CodeError("Token: empty string"); |
|
494 } |
|
495 |
|
496 let code = token.charCodeAt(0); |
|
497 if ((code < ASCIIS) && (code >= CTLS)) { |
|
498 if ((code == HT) || (code == SP) |
|
499 || (code == 34) || (code == 40) || (code == 41) // ASCII "() |
|
500 || (code == 44) || (code == 47) // ASCII ,/ |
|
501 || ((code >= 58) && (code <= 64)) // ASCII :;<=>?@ |
|
502 || ((code >= 91) && (code <= 93)) // ASCII [\] |
|
503 || (code == 123) || (code == 125)) { // ASCII {} |
|
504 // Fallback to throw CodeError |
|
505 } else { |
|
506 Octet.encode(data, token.charCodeAt(0)); |
|
507 return; |
|
508 } |
|
509 } |
|
510 |
|
511 throw new CodeError("Token: invalid char code " + code); |
|
512 }, |
|
513 }; |
|
514 |
|
515 /** |
|
516 * uric = reserved | unreserved | escaped |
|
517 * reserved = ;/?:@&=+$, |
|
518 * unreserved = alphanum | mark |
|
519 * mark = -_.!~*'() |
|
520 * escaped = % hex hex |
|
521 * excluded but used = #% |
|
522 * |
|
523 * Or, in decimal, they are: 33,35-59,61,63-90,95,97-122,126 |
|
524 * |
|
525 * @see RFC 2396 Uniform Resource Indentifiers (URI) |
|
526 */ |
|
527 this.URIC = { |
|
528 /** |
|
529 * @param data |
|
530 * A wrapped object containing raw PDU data. |
|
531 * |
|
532 * @return Decoded character. |
|
533 * |
|
534 * @throws NullCharError if a NUL character read. |
|
535 * @throws CodeError if an invalid character read. |
|
536 */ |
|
537 decode: function(data) { |
|
538 let code = Octet.decode(data); |
|
539 if (code == NUL) { |
|
540 throw new NullCharError(); |
|
541 } |
|
542 |
|
543 if ((code <= CTLS) || (code >= ASCIIS) || (code == 34) || (code == 60) |
|
544 || (code == 62) || ((code >= 91) && (code <= 94)) || (code == 96) |
|
545 || ((code >= 123) && (code <= 125)) || (code == 127)) { |
|
546 throw new CodeError("URIC: invalid char code " + code); |
|
547 } |
|
548 |
|
549 return String.fromCharCode(code); |
|
550 }, |
|
551 }; |
|
552 |
|
553 /** |
|
554 * If the first character in the TEXT is in the range of 128-255, a Quote |
|
555 * character must precede it. Otherwise the Quote character must be omitted. |
|
556 * The Quote is not part of the contents. |
|
557 * |
|
558 * Text-string = [Quote] *TEXT End-of-string |
|
559 * Quote = <Octet 127> |
|
560 * |
|
561 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
562 */ |
|
563 this.TextString = { |
|
564 /** |
|
565 * @param data |
|
566 * A wrapped object containing raw PDU data. |
|
567 * |
|
568 * @return Decoded string. |
|
569 */ |
|
570 decode: function(data) { |
|
571 let begin = data.offset; |
|
572 let firstCode = Octet.decode(data); |
|
573 if (firstCode == 127) { |
|
574 // Quote found, check if first char code is larger-equal than 128. |
|
575 begin = data.offset; |
|
576 try { |
|
577 if (Octet.decode(data) < 128) { |
|
578 throw new CodeError("Text-string: illegal quote found."); |
|
579 } |
|
580 } catch (e if e instanceof CodeError) { |
|
581 throw e; |
|
582 } catch (e) { |
|
583 throw new CodeError("Text-string: unexpected error."); |
|
584 } |
|
585 } else if (firstCode >= 128) { |
|
586 throw new CodeError("Text-string: invalid char code " + firstCode); |
|
587 } |
|
588 |
|
589 data.offset = begin; |
|
590 return NullTerminatedTexts.decode(data); |
|
591 }, |
|
592 |
|
593 /** |
|
594 * @param data |
|
595 * A wrapped object to store encoded raw data. |
|
596 * @param str |
|
597 * A String to be encoded. |
|
598 * @param asciiOnly |
|
599 * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). |
|
600 */ |
|
601 encode: function(data, str, asciiOnly) { |
|
602 if (!str) { |
|
603 Octet.encode(data, 0); |
|
604 return; |
|
605 } |
|
606 |
|
607 let firstCharCode = str.charCodeAt(0); |
|
608 if (firstCharCode >= 128) { |
|
609 if (asciiOnly) { |
|
610 throw new CodeError("Text: invalid char code " + code); |
|
611 } |
|
612 |
|
613 Octet.encode(data, 127); |
|
614 } |
|
615 |
|
616 NullTerminatedTexts.encode(data, str, asciiOnly); |
|
617 }, |
|
618 }; |
|
619 |
|
620 /** |
|
621 * Token-text = Token End-of-string |
|
622 * |
|
623 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
624 */ |
|
625 this.TokenText = { |
|
626 /** |
|
627 * @param data |
|
628 * A wrapped object containing raw PDU data. |
|
629 * |
|
630 * @return Decoded string. |
|
631 */ |
|
632 decode: function(data) { |
|
633 let str = ""; |
|
634 try { |
|
635 // A End-of-string is also a CTL, which should cause a error. |
|
636 while (true) { |
|
637 str += Token.decode(data); |
|
638 } |
|
639 } catch (e if e instanceof NullCharError) { |
|
640 return str; |
|
641 } |
|
642 }, |
|
643 |
|
644 /** |
|
645 * @param data |
|
646 * A wrapped object to store encoded raw data. |
|
647 * @param str |
|
648 * A String to be encoded. |
|
649 */ |
|
650 encode: function(data, str) { |
|
651 if (str) { |
|
652 for (let i = 0; i < str.length; i++) { |
|
653 Token.encode(data, str.charAt(i)); |
|
654 } |
|
655 } |
|
656 Octet.encode(data, 0); |
|
657 }, |
|
658 }; |
|
659 |
|
660 /** |
|
661 * The TEXT encodes an RFC2616 Quoted-string with the enclosing |
|
662 * quotation-marks <"> removed. |
|
663 * |
|
664 * Quoted-string = <Octet 34> *TEXT End-of-string |
|
665 * |
|
666 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
667 */ |
|
668 this.QuotedString = { |
|
669 /** |
|
670 * @param data |
|
671 * A wrapped object containing raw PDU data. |
|
672 * |
|
673 * @return Decoded string. |
|
674 * |
|
675 * @throws CodeError if first octet read is not 0x34. |
|
676 */ |
|
677 decode: function(data) { |
|
678 let value = Octet.decode(data); |
|
679 if (value != 34) { |
|
680 throw new CodeError("Quoted-string: not quote " + value); |
|
681 } |
|
682 |
|
683 return NullTerminatedTexts.decode(data); |
|
684 }, |
|
685 |
|
686 /** |
|
687 * @param data |
|
688 * A wrapped object to store encoded raw data. |
|
689 * @param str |
|
690 * A String to be encoded. |
|
691 */ |
|
692 encode: function(data, str) { |
|
693 Octet.encode(data, 34); |
|
694 NullTerminatedTexts.encode(data, str); |
|
695 }, |
|
696 }; |
|
697 |
|
698 /** |
|
699 * Integers in range 0-127 shall be encoded as a one octet value with the |
|
700 * most significant bit set to one (1xxx xxxx) and with the value in the |
|
701 * remaining least significant bits. |
|
702 * |
|
703 * Short-integer = OCTET |
|
704 * |
|
705 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
706 */ |
|
707 this.ShortInteger = { |
|
708 /** |
|
709 * @param data |
|
710 * A wrapped object containing raw PDU data. |
|
711 * |
|
712 * @return Decoded integer value. |
|
713 * |
|
714 * @throws CodeError if the octet read is less than 0x80. |
|
715 */ |
|
716 decode: function(data) { |
|
717 let value = Octet.decode(data); |
|
718 if (!(value & 0x80)) { |
|
719 throw new CodeError("Short-integer: invalid value " + value); |
|
720 } |
|
721 |
|
722 return (value & 0x7F); |
|
723 }, |
|
724 |
|
725 /** |
|
726 * @param data |
|
727 * A wrapped object to store encoded raw data. |
|
728 * @param value |
|
729 * A numeric value to be encoded. |
|
730 * |
|
731 * @throws CodeError if the octet read is larger-equal than 0x80. |
|
732 */ |
|
733 encode: function(data, value) { |
|
734 if (value >= 0x80) { |
|
735 throw new CodeError("Short-integer: invalid value " + value); |
|
736 } |
|
737 |
|
738 Octet.encode(data, value | 0x80); |
|
739 }, |
|
740 }; |
|
741 |
|
742 /** |
|
743 * The content octets shall be an unsigned integer value with the most |
|
744 * significant octet encoded first (big-endian representation). The minimum |
|
745 * number of octets must be used to encode the value. |
|
746 * |
|
747 * Long-integer = Short-length Multi-octet-integer |
|
748 * Short-length = <Any octet 0-30> |
|
749 * Multi-octet-integer = 1*30 OCTET |
|
750 * |
|
751 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
752 */ |
|
753 this.LongInteger = { |
|
754 /** |
|
755 * @param data |
|
756 * A wrapped object containing raw PDU data. |
|
757 * @param length |
|
758 * Number of octets to read. |
|
759 * |
|
760 * @return A decoded integer value or an octets array of max 30 elements. |
|
761 */ |
|
762 decodeMultiOctetInteger: function(data, length) { |
|
763 if (length < 7) { |
|
764 // Return a integer instead of an array as possible. For a multi-octet |
|
765 // integer, there are only maximum 53 bits for integer in javascript. We |
|
766 // will get an inaccurate one beyond that. We can't neither use bitwise |
|
767 // operation here, for it will be limited in 32 bits. |
|
768 let value = 0; |
|
769 while (length--) { |
|
770 value = value * 256 + Octet.decode(data); |
|
771 } |
|
772 return value; |
|
773 } |
|
774 |
|
775 return Octet.decodeMultiple(data, data.offset + length); |
|
776 }, |
|
777 |
|
778 /** |
|
779 * @param data |
|
780 * A wrapped object containing raw PDU data. |
|
781 * |
|
782 * @return A decoded integer value or an octets array of max 30 elements. |
|
783 * |
|
784 * @throws CodeError if the length read is not in 1..30. |
|
785 */ |
|
786 decode: function(data) { |
|
787 let length = Octet.decode(data); |
|
788 if ((length < 1) || (length > 30)) { |
|
789 throw new CodeError("Long-integer: invalid length " + length); |
|
790 } |
|
791 |
|
792 return this.decodeMultiOctetInteger(data, length); |
|
793 }, |
|
794 |
|
795 /** |
|
796 * @param data |
|
797 * A wrapped object to store encoded raw data. |
|
798 * @param numOrArray |
|
799 * An octet array of less-equal than 30 elements or an integer |
|
800 * greater-equal than 128. |
|
801 */ |
|
802 encode: function(data, numOrArray) { |
|
803 if (typeof numOrArray === "number") { |
|
804 let num = numOrArray; |
|
805 if (num >= 0x1000000000000) { |
|
806 throw new CodeError("Long-integer: number too large " + num); |
|
807 } |
|
808 |
|
809 let stack = []; |
|
810 do { |
|
811 stack.push(Math.floor(num % 256)); |
|
812 num = Math.floor(num / 256); |
|
813 } while (num); |
|
814 |
|
815 Octet.encode(data, stack.length); |
|
816 while (stack.length) { |
|
817 Octet.encode(data, stack.pop()); |
|
818 } |
|
819 return; |
|
820 } |
|
821 |
|
822 let array = numOrArray; |
|
823 if ((array.length < 1) || (array.length > 30)) { |
|
824 throw new CodeError("Long-integer: invalid length " + array.length); |
|
825 } |
|
826 |
|
827 Octet.encode(data, array.length); |
|
828 Octet.encodeMultiple(data, array); |
|
829 }, |
|
830 }; |
|
831 |
|
832 /** |
|
833 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
834 */ |
|
835 this.UintVar = { |
|
836 /** |
|
837 * @param data |
|
838 * A wrapped object containing raw PDU data. |
|
839 * |
|
840 * @return Decoded integer value. |
|
841 */ |
|
842 decode: function(data) { |
|
843 let value = Octet.decode(data); |
|
844 let result = value & 0x7F; |
|
845 while (value & 0x80) { |
|
846 value = Octet.decode(data); |
|
847 result = result * 128 + (value & 0x7F); |
|
848 } |
|
849 |
|
850 return result; |
|
851 }, |
|
852 |
|
853 /** |
|
854 * @param data |
|
855 * A wrapped object to store encoded raw data. |
|
856 * @param value |
|
857 * An integer value. |
|
858 */ |
|
859 encode: function(data, value) { |
|
860 if (value < 0) { |
|
861 throw new CodeError("UintVar: invalid value " + value); |
|
862 } |
|
863 |
|
864 let stack = []; |
|
865 while (value >= 128) { |
|
866 stack.push(Math.floor(value % 128)); |
|
867 value = Math.floor(value / 128); |
|
868 } |
|
869 |
|
870 while (stack.length) { |
|
871 Octet.encode(data, value | 0x80); |
|
872 value = stack.pop(); |
|
873 } |
|
874 Octet.encode(data, value); |
|
875 }, |
|
876 }; |
|
877 |
|
878 /** |
|
879 * This encoding is used for token values, which have no well-known binary |
|
880 * encoding, or when the assigned number of the well-known encoding is small |
|
881 * enough to fit into Short-Integer. We change Extension-Media from |
|
882 * NullTerminatedTexts to TextString because of Bug 823816. |
|
883 * |
|
884 * Constrained-encoding = Extension-Media | Short-integer |
|
885 * Extension-Media = TextString |
|
886 * |
|
887 * @see WAP-230-WSP-20010705-a clause 8.4.2.1 |
|
888 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=823816 |
|
889 */ |
|
890 this.ConstrainedEncoding = { |
|
891 /** |
|
892 * @param data |
|
893 * A wrapped object containing raw PDU data. |
|
894 * |
|
895 * @return Decode integer value or string. |
|
896 */ |
|
897 decode: function(data) { |
|
898 return decodeAlternatives(data, null, TextString, ShortInteger); |
|
899 }, |
|
900 |
|
901 /** |
|
902 * @param data |
|
903 * A wrapped object to store encoded raw data. |
|
904 * @param value |
|
905 * An integer or a string value. |
|
906 */ |
|
907 encode: function(data, value) { |
|
908 if (typeof value == "number") { |
|
909 ShortInteger.encode(data, value); |
|
910 } else { |
|
911 TextString.encode(data, value); |
|
912 } |
|
913 }, |
|
914 }; |
|
915 |
|
916 /** |
|
917 * Value-length = Short-length | (Length-quote Length) |
|
918 * Short-length = <Any octet 0-30> |
|
919 * Length-quote = <Octet 31> |
|
920 * Length = Uintvar-integer |
|
921 * |
|
922 * @see WAP-230-WSP-20010705-a clause 8.4.2.2 |
|
923 */ |
|
924 this.ValueLength = { |
|
925 /** |
|
926 * @param data |
|
927 * A wrapped object containing raw PDU data. |
|
928 * |
|
929 * @return Decoded integer value. |
|
930 * |
|
931 * @throws CodeError if the first octet read is larger than 31. |
|
932 */ |
|
933 decode: function(data) { |
|
934 let value = Octet.decode(data); |
|
935 if (value <= 30) { |
|
936 return value; |
|
937 } |
|
938 |
|
939 if (value == 31) { |
|
940 return UintVar.decode(data); |
|
941 } |
|
942 |
|
943 throw new CodeError("Value-length: invalid value " + value); |
|
944 }, |
|
945 |
|
946 /** |
|
947 * @param data |
|
948 * A wrapped object to store encoded raw data. |
|
949 * @param value |
|
950 */ |
|
951 encode: function(data, value) { |
|
952 if (value <= 30) { |
|
953 Octet.encode(data, value); |
|
954 } else { |
|
955 Octet.encode(data, 31); |
|
956 UintVar.encode(data, value); |
|
957 } |
|
958 }, |
|
959 }; |
|
960 |
|
961 /** |
|
962 * No-value = <Octet 0> |
|
963 * |
|
964 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
965 */ |
|
966 this.NoValue = { |
|
967 /** |
|
968 * @param data |
|
969 * A wrapped object containing raw PDU data. |
|
970 * |
|
971 * @return Always returns null. |
|
972 */ |
|
973 decode: function(data) { |
|
974 Octet.decodeEqualTo(data, 0); |
|
975 return null; |
|
976 }, |
|
977 |
|
978 /** |
|
979 * @param data |
|
980 * A wrapped object to store encoded raw data. |
|
981 * @param value |
|
982 * A null or undefined value. |
|
983 */ |
|
984 encode: function(data, value) { |
|
985 if (value != null) { |
|
986 throw new CodeError("No-value: invalid value " + value); |
|
987 } |
|
988 Octet.encode(data, 0); |
|
989 }, |
|
990 }; |
|
991 |
|
992 /** |
|
993 * Text-value = No-value | Token-text | Quoted-string |
|
994 * |
|
995 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
996 */ |
|
997 this.TextValue = { |
|
998 /** |
|
999 * @param data |
|
1000 * A wrapped object containing raw PDU data. |
|
1001 * |
|
1002 * @return Decoded string or null for No-value. |
|
1003 */ |
|
1004 decode: function(data) { |
|
1005 return decodeAlternatives(data, null, NoValue, TokenText, QuotedString); |
|
1006 }, |
|
1007 |
|
1008 /** |
|
1009 * @param data |
|
1010 * A wrapped object to store encoded raw data. |
|
1011 * @param text |
|
1012 * A null or undefined or text string. |
|
1013 */ |
|
1014 encode: function(data, text) { |
|
1015 encodeAlternatives(data, text, null, NoValue, TokenText, QuotedString); |
|
1016 }, |
|
1017 }; |
|
1018 |
|
1019 /** |
|
1020 * Integer-Value = Short-integer | Long-integer |
|
1021 * |
|
1022 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
1023 */ |
|
1024 this.IntegerValue = { |
|
1025 /** |
|
1026 * @param data |
|
1027 * A wrapped object containing raw PDU data. |
|
1028 * |
|
1029 * @return Decoded integer value or array of octets. |
|
1030 */ |
|
1031 decode: function(data) { |
|
1032 return decodeAlternatives(data, null, ShortInteger, LongInteger); |
|
1033 }, |
|
1034 |
|
1035 /** |
|
1036 * @param data |
|
1037 * A wrapped object to store encoded raw data. |
|
1038 * @param value |
|
1039 * An integer value or an octet array of less-equal than 31 elements. |
|
1040 */ |
|
1041 encode: function(data, value) { |
|
1042 if (typeof value === "number") { |
|
1043 encodeAlternatives(data, value, null, ShortInteger, LongInteger); |
|
1044 } else if (Array.isArray(value) || (value instanceof Uint8Array)) { |
|
1045 LongInteger.encode(data, value); |
|
1046 } else { |
|
1047 throw new CodeError("Integer-Value: invalid value type"); |
|
1048 } |
|
1049 }, |
|
1050 }; |
|
1051 |
|
1052 /** |
|
1053 * The encoding of dates shall be done in number of seconds from |
|
1054 * 1970-01-01, 00:00:00 GMT. |
|
1055 * |
|
1056 * Date-value = Long-integer |
|
1057 * |
|
1058 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
1059 */ |
|
1060 this.DateValue = { |
|
1061 /** |
|
1062 * @param data |
|
1063 * A wrapped object containing raw PDU data. |
|
1064 * |
|
1065 * @return A Date object. |
|
1066 */ |
|
1067 decode: function(data) { |
|
1068 let numOrArray = LongInteger.decode(data); |
|
1069 let seconds; |
|
1070 if (typeof numOrArray == "number") { |
|
1071 seconds = numOrArray; |
|
1072 } else { |
|
1073 seconds = 0; |
|
1074 for (let i = 0; i < numOrArray.length; i++) { |
|
1075 seconds = seconds * 256 + numOrArray[i]; |
|
1076 } |
|
1077 } |
|
1078 |
|
1079 return new Date(seconds * 1000); |
|
1080 }, |
|
1081 |
|
1082 /** |
|
1083 * @param data |
|
1084 * A wrapped object to store encoded raw data. |
|
1085 * @param date |
|
1086 * A Date object. |
|
1087 */ |
|
1088 encode: function(data, date) { |
|
1089 let seconds = date.getTime() / 1000; |
|
1090 if (seconds < 0) { |
|
1091 throw new CodeError("Date-value: negative seconds " + seconds); |
|
1092 } |
|
1093 |
|
1094 LongInteger.encode(data, seconds); |
|
1095 }, |
|
1096 }; |
|
1097 |
|
1098 /** |
|
1099 * Delta-seconds-value = Integer-value |
|
1100 * |
|
1101 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
1102 */ |
|
1103 this.DeltaSecondsValue = IntegerValue; |
|
1104 |
|
1105 /** |
|
1106 * Quality factor 0 and quality factors with one or two decimal digits are |
|
1107 * encoded into 1-100; three digits ones into 101-1099. |
|
1108 * |
|
1109 * Q-value = 1*2 OCTET |
|
1110 * |
|
1111 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
1112 */ |
|
1113 this.QValue = { |
|
1114 /** |
|
1115 * @param data |
|
1116 * A wrapped object containing raw PDU data. |
|
1117 * |
|
1118 * @return Decoded integer value of 1..1099. |
|
1119 * |
|
1120 * @throws CodeError if decoded UintVar is not in range 1..1099. |
|
1121 */ |
|
1122 decode: function(data) { |
|
1123 let value = UintVar.decode(data); |
|
1124 if (value > 0) { |
|
1125 if (value <= 100) { |
|
1126 return (value - 1) / 100.0; |
|
1127 } |
|
1128 if (value <= 1099) { |
|
1129 return (value - 100) / 1000.0; |
|
1130 } |
|
1131 } |
|
1132 |
|
1133 throw new CodeError("Q-value: invalid value " + value); |
|
1134 }, |
|
1135 |
|
1136 /** |
|
1137 * @param data |
|
1138 * A wrapped object to store encoded raw data. |
|
1139 * @param value |
|
1140 * An integer within the range 1..1099. |
|
1141 */ |
|
1142 encode: function(data, value) { |
|
1143 if ((value < 0) || (value >= 1)) { |
|
1144 throw new CodeError("Q-value: invalid value " + value); |
|
1145 } |
|
1146 |
|
1147 value *= 1000; |
|
1148 if ((value % 10) == 0) { |
|
1149 // Two digits only. |
|
1150 UintVar.encode(data, Math.floor(value / 10 + 1)); |
|
1151 } else { |
|
1152 // Three digits. |
|
1153 UintVar.encode(data, Math.floor(value + 100)); |
|
1154 } |
|
1155 }, |
|
1156 }; |
|
1157 |
|
1158 /** |
|
1159 * The three most significant bits of the Short-integer value are interpreted |
|
1160 * to encode a major version number in the range 1-7, and the four least |
|
1161 * significant bits contain a minor version number in the range 0-14. If |
|
1162 * there is only a major version number, this is encoded by placing the value |
|
1163 * 15 in the four least significant bits. |
|
1164 * |
|
1165 * Version-value = Short-integer | Text-string |
|
1166 * |
|
1167 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
1168 */ |
|
1169 this.VersionValue = { |
|
1170 /** |
|
1171 * @param data |
|
1172 * A wrapped object containing raw PDU data. |
|
1173 * |
|
1174 * @return Binary encoded version number. |
|
1175 */ |
|
1176 decode: function(data) { |
|
1177 let begin = data.offset; |
|
1178 let value; |
|
1179 try { |
|
1180 value = ShortInteger.decode(data); |
|
1181 if ((value >= 0x10) && (value < 0x80)) { |
|
1182 return value; |
|
1183 } |
|
1184 |
|
1185 throw new CodeError("Version-value: invalid value " + value); |
|
1186 } catch (e) {} |
|
1187 |
|
1188 data.offset = begin; |
|
1189 |
|
1190 let str = TextString.decode(data); |
|
1191 if (!str.match(/^[1-7](\.1?\d)?$/)) { |
|
1192 throw new CodeError("Version-value: invalid value " + str); |
|
1193 } |
|
1194 |
|
1195 let major = str.charCodeAt(0) - 0x30; |
|
1196 let minor = 0x0F; |
|
1197 if (str.length > 1) { |
|
1198 minor = str.charCodeAt(2) - 0x30; |
|
1199 if (str.length > 3) { |
|
1200 minor = 10 + (str.charCodeAt(3) - 0x30); |
|
1201 if (minor > 14) { |
|
1202 throw new CodeError("Version-value: invalid minor " + minor); |
|
1203 } |
|
1204 } |
|
1205 } |
|
1206 |
|
1207 return major << 4 | minor; |
|
1208 }, |
|
1209 |
|
1210 /** |
|
1211 * @param data |
|
1212 * A wrapped object to store encoded raw data. |
|
1213 * @param version |
|
1214 * A binary encoded version number. |
|
1215 */ |
|
1216 encode: function(data, version) { |
|
1217 if ((version < 0x10) || (version >= 0x80)) { |
|
1218 throw new CodeError("Version-value: invalid version " + version); |
|
1219 } |
|
1220 |
|
1221 ShortInteger.encode(data, version); |
|
1222 }, |
|
1223 }; |
|
1224 |
|
1225 /** |
|
1226 * URI value should be encoded per [RFC2616], but service user may use a |
|
1227 * different format. |
|
1228 * |
|
1229 * Uri-value = Text-string |
|
1230 * |
|
1231 * @see WAP-230-WSP-20010705-a clause 8.4.2.3 |
|
1232 * @see RFC 2616 clause 2.2 Basic Rules |
|
1233 */ |
|
1234 this.UriValue = { |
|
1235 /** |
|
1236 * @param data |
|
1237 * A wrapped object containing raw PDU data. |
|
1238 * |
|
1239 * @return Decoded uri string. |
|
1240 */ |
|
1241 decode: function(data) { |
|
1242 let str = ""; |
|
1243 try { |
|
1244 // A End-of-string is also a CTL, which should cause a error. |
|
1245 while (true) { |
|
1246 str += URIC.decode(data); |
|
1247 } |
|
1248 } catch (e if e instanceof NullCharError) { |
|
1249 return str; |
|
1250 } |
|
1251 }, |
|
1252 }; |
|
1253 |
|
1254 /** |
|
1255 * Internal coder for "type" parameter. |
|
1256 * |
|
1257 * Type-value = Constrained-encoding |
|
1258 * |
|
1259 * @see WAP-230-WSP-20010705-a table 38 |
|
1260 */ |
|
1261 this.TypeValue = { |
|
1262 /** |
|
1263 * @param data |
|
1264 * A wrapped object containing raw PDU data. |
|
1265 * |
|
1266 * @return Decoded content type string. |
|
1267 */ |
|
1268 decode: function(data) { |
|
1269 let numOrStr = ConstrainedEncoding.decode(data); |
|
1270 if (typeof numOrStr == "string") { |
|
1271 return numOrStr.toLowerCase(); |
|
1272 } |
|
1273 |
|
1274 let number = numOrStr; |
|
1275 let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; |
|
1276 if (!entry) { |
|
1277 throw new NotWellKnownEncodingError( |
|
1278 "Constrained-media: not well known media " + number); |
|
1279 } |
|
1280 |
|
1281 return entry.type; |
|
1282 }, |
|
1283 |
|
1284 /** |
|
1285 * @param data |
|
1286 * A wrapped object to store encoded raw data. |
|
1287 * @param type |
|
1288 * A content type string. |
|
1289 */ |
|
1290 encode: function(data, type) { |
|
1291 let entry = WSP_WELL_KNOWN_CONTENT_TYPES[type.toLowerCase()]; |
|
1292 if (entry) { |
|
1293 ConstrainedEncoding.encode(data, entry.number); |
|
1294 } else { |
|
1295 ConstrainedEncoding.encode(data, type); |
|
1296 } |
|
1297 }, |
|
1298 }; |
|
1299 |
|
1300 /** |
|
1301 * Parameter = Typed-parameter | Untyped-parameter |
|
1302 * |
|
1303 * For Typed-parameters, the actual expected type of the value is implied by |
|
1304 * the well-known parameter. In addition to the expected type, there may be no |
|
1305 * value. If the value cannot be encoded using expected type, it shall be |
|
1306 * encoded as text. |
|
1307 * |
|
1308 * Typed-parameter = Well-known-parameter-token Typed-value |
|
1309 * Well-known-parameter-token = Integer-value |
|
1310 * Typed-value = Compact-value | Text-value |
|
1311 * Compact-value = Integer-value | Date-value | Delta-seconds-value | Q-value |
|
1312 * | Version-value | Uri-value |
|
1313 * |
|
1314 * For Untyped-parameters, the type of the value is unknown, but is shall be |
|
1315 * encoded as an integer, if that is possible. |
|
1316 * |
|
1317 * Untyped-parameter = Token-text Untyped-value |
|
1318 * Untyped-value = Integer-value | Text-value |
|
1319 * |
|
1320 * @see WAP-230-WSP-20010705-a clause 8.4.2.4 |
|
1321 */ |
|
1322 this.Parameter = { |
|
1323 /** |
|
1324 * @param data |
|
1325 * A wrapped object containing raw PDU data. |
|
1326 * |
|
1327 * @return A decoded object containing `name` and `value` properties or null |
|
1328 * if something wrong. The `name` property must be a string, but the |
|
1329 * `value` property can be many different types depending on `name`. |
|
1330 * |
|
1331 * @throws CodeError if decoded IntegerValue is an array. |
|
1332 * @throws NotWellKnownEncodingError if decoded well-known parameter number |
|
1333 * is not registered or supported. |
|
1334 */ |
|
1335 decodeTypedParameter: function(data) { |
|
1336 let numOrArray = IntegerValue.decode(data); |
|
1337 // `decodeIntegerValue` can return a array, which doesn't apply here. |
|
1338 if (typeof numOrArray != "number") { |
|
1339 throw new CodeError("Typed-parameter: invalid integer type"); |
|
1340 } |
|
1341 |
|
1342 let number = numOrArray; |
|
1343 let param = WSP_WELL_KNOWN_PARAMS[number]; |
|
1344 if (!param) { |
|
1345 throw new NotWellKnownEncodingError( |
|
1346 "Typed-parameter: not well known parameter " + number); |
|
1347 } |
|
1348 |
|
1349 let begin = data.offset, value; |
|
1350 try { |
|
1351 // Althought Text-string is not included in BNF of Compact-value, but |
|
1352 // some service provider might still pass a less-strict text form and |
|
1353 // cause a unexpected CodeError raised. For example, the `start` |
|
1354 // parameter expects its value of Text-value, but service provider might |
|
1355 // gives "<smil>", which contains illegal characters "<" and ">". |
|
1356 value = decodeAlternatives(data, null, |
|
1357 param.coder, TextValue, TextString); |
|
1358 } catch (e) { |
|
1359 data.offset = begin; |
|
1360 |
|
1361 // Skip current parameter. |
|
1362 value = skipValue(data); |
|
1363 debug("Skip malformed typed parameter: " |
|
1364 + JSON.stringify({name: param.name, value: value})); |
|
1365 |
|
1366 return null; |
|
1367 } |
|
1368 |
|
1369 return { |
|
1370 name: param.name, |
|
1371 value: value, |
|
1372 }; |
|
1373 }, |
|
1374 |
|
1375 /** |
|
1376 * @param data |
|
1377 * A wrapped object containing raw PDU data. |
|
1378 * |
|
1379 * @return A decoded object containing `name` and `value` properties or null |
|
1380 * if something wrong. The `name` property must be a string, but the |
|
1381 * `value` property can be many different types depending on `name`. |
|
1382 */ |
|
1383 decodeUntypedParameter: function(data) { |
|
1384 let name = TokenText.decode(data); |
|
1385 |
|
1386 let begin = data.offset, value; |
|
1387 try { |
|
1388 value = decodeAlternatives(data, null, IntegerValue, TextValue); |
|
1389 } catch (e) { |
|
1390 data.offset = begin; |
|
1391 |
|
1392 // Skip current parameter. |
|
1393 value = skipValue(data); |
|
1394 debug("Skip malformed untyped parameter: " |
|
1395 + JSON.stringify({name: name, value: value})); |
|
1396 |
|
1397 return null; |
|
1398 } |
|
1399 |
|
1400 return { |
|
1401 name: name.toLowerCase(), |
|
1402 value: value, |
|
1403 }; |
|
1404 }, |
|
1405 |
|
1406 /** |
|
1407 * @param data |
|
1408 * A wrapped object containing raw PDU data. |
|
1409 * |
|
1410 * @return A decoded object containing `name` and `value` properties or null |
|
1411 * if something wrong. The `name` property must be a string, but the |
|
1412 * `value` property can be many different types depending on `name`. |
|
1413 */ |
|
1414 decode: function(data) { |
|
1415 let begin = data.offset; |
|
1416 try { |
|
1417 return this.decodeTypedParameter(data); |
|
1418 } catch (e) { |
|
1419 data.offset = begin; |
|
1420 return this.decodeUntypedParameter(data); |
|
1421 } |
|
1422 }, |
|
1423 |
|
1424 /** |
|
1425 * @param data |
|
1426 * A wrapped object containing raw PDU data. |
|
1427 * @param end |
|
1428 * Ending offset of following parameters. |
|
1429 * |
|
1430 * @return An array of decoded objects. |
|
1431 */ |
|
1432 decodeMultiple: function(data, end) { |
|
1433 let params = null, param; |
|
1434 |
|
1435 while (data.offset < end) { |
|
1436 try { |
|
1437 param = this.decode(data); |
|
1438 } catch (e) { |
|
1439 break; |
|
1440 } |
|
1441 if (param) { |
|
1442 if (!params) { |
|
1443 params = {}; |
|
1444 } |
|
1445 params[param.name] = param.value; |
|
1446 } |
|
1447 } |
|
1448 |
|
1449 return params; |
|
1450 }, |
|
1451 |
|
1452 /** |
|
1453 * @param data |
|
1454 * A wrapped object to store encoded raw data. |
|
1455 * @param param |
|
1456 * An object containing `name` and `value` properties. |
|
1457 */ |
|
1458 encodeTypedParameter: function(data, param) { |
|
1459 let entry = WSP_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; |
|
1460 if (!entry) { |
|
1461 throw new NotWellKnownEncodingError( |
|
1462 "Typed-parameter: not well known parameter " + param.name); |
|
1463 } |
|
1464 |
|
1465 IntegerValue.encode(data, entry.number); |
|
1466 encodeAlternatives(data, param.value, null, |
|
1467 entry.coder, TextValue, TextString); |
|
1468 }, |
|
1469 |
|
1470 /** |
|
1471 * @param data |
|
1472 * A wrapped object to store encoded raw data. |
|
1473 * @param param |
|
1474 * An object containing `name` and `value` properties. |
|
1475 */ |
|
1476 encodeUntypedParameter: function(data, param) { |
|
1477 TokenText.encode(data, param.name); |
|
1478 encodeAlternatives(data, param.value, null, IntegerValue, TextValue); |
|
1479 }, |
|
1480 |
|
1481 /** |
|
1482 * @param data |
|
1483 * A wrapped object to store encoded raw data. |
|
1484 * @param param |
|
1485 * An array of parameter objects. |
|
1486 */ |
|
1487 encodeMultiple: function(data, params) { |
|
1488 for (let name in params) { |
|
1489 this.encode(data, {name: name, value: params[name]}); |
|
1490 } |
|
1491 }, |
|
1492 |
|
1493 /** |
|
1494 * @param data |
|
1495 * A wrapped object to store encoded raw data. |
|
1496 * @param param |
|
1497 * An object containing `name` and `value` properties. |
|
1498 */ |
|
1499 encode: function(data, param) { |
|
1500 let begin = data.offset; |
|
1501 try { |
|
1502 this.encodeTypedParameter(data, param); |
|
1503 } catch (e) { |
|
1504 data.offset = begin; |
|
1505 this.encodeUntypedParameter(data, param); |
|
1506 } |
|
1507 }, |
|
1508 }; |
|
1509 |
|
1510 /** |
|
1511 * Header = Message-header | Shift-sequence |
|
1512 * Message-header = Well-known-header | Application-header |
|
1513 * |
|
1514 * @see WAP-230-WSP-20010705-a clause 8.4.2.6 |
|
1515 */ |
|
1516 this.Header = { |
|
1517 /** |
|
1518 * @param data |
|
1519 * A wrapped object containing raw PDU data. |
|
1520 * |
|
1521 * @return A decoded object containing `name` and `value` properties or null |
|
1522 * in case of a failed parsing. The `name` property must be a string, |
|
1523 * but the `value` property can be many different types depending on |
|
1524 * `name`. |
|
1525 */ |
|
1526 decodeMessageHeader: function(data) { |
|
1527 return decodeAlternatives(data, null, WellKnownHeader, ApplicationHeader); |
|
1528 }, |
|
1529 |
|
1530 /** |
|
1531 * @param data |
|
1532 * A wrapped object containing raw PDU data. |
|
1533 * |
|
1534 * @return A decoded object containing `name` and `value` properties or null |
|
1535 * in case of a failed parsing. The `name` property must be a string, |
|
1536 * but the `value` property can be many different types depending on |
|
1537 * `name`. |
|
1538 */ |
|
1539 decode: function(data) { |
|
1540 // TODO: support header code page shift-sequence |
|
1541 return this.decodeMessageHeader(data); |
|
1542 }, |
|
1543 |
|
1544 encodeMessageHeader: function(data, header) { |
|
1545 encodeAlternatives(data, header, null, WellKnownHeader, ApplicationHeader); |
|
1546 }, |
|
1547 |
|
1548 /** |
|
1549 * @param data |
|
1550 * A wrapped object to store encoded raw data. |
|
1551 * @param header |
|
1552 * An object containing two attributes: a string-typed `name` and a |
|
1553 * `value` of arbitrary type. |
|
1554 */ |
|
1555 encode: function(data, header) { |
|
1556 // TODO: support header code page shift-sequence |
|
1557 this.encodeMessageHeader(data, header); |
|
1558 }, |
|
1559 }; |
|
1560 |
|
1561 /** |
|
1562 * Well-known-header = Well-known-field-name Wap-value |
|
1563 * Well-known-field-name = Short-integer |
|
1564 * |
|
1565 * @see WAP-230-WSP-20010705-a clause 8.4.2.6 |
|
1566 */ |
|
1567 this.WellKnownHeader = { |
|
1568 /** |
|
1569 * @param data |
|
1570 * A wrapped object containing raw PDU data. |
|
1571 * |
|
1572 * @return A decoded object containing `name` and `value` properties or null |
|
1573 * in case of a failed parsing. The `name` property must be a string, |
|
1574 * but the `value` property can be many different types depending on |
|
1575 * `name`. |
|
1576 * |
|
1577 * @throws NotWellKnownEncodingError if decoded well-known header field |
|
1578 * number is not registered or supported. |
|
1579 */ |
|
1580 decode: function(data) { |
|
1581 let index = ShortInteger.decode(data); |
|
1582 |
|
1583 let entry = WSP_HEADER_FIELDS[index]; |
|
1584 if (!entry) { |
|
1585 throw new NotWellKnownEncodingError( |
|
1586 "Well-known-header: not well known header " + index); |
|
1587 } |
|
1588 |
|
1589 let begin = data.offset, value; |
|
1590 try { |
|
1591 value = decodeAlternatives(data, null, entry.coder, TextValue); |
|
1592 } catch (e) { |
|
1593 data.offset = begin; |
|
1594 |
|
1595 value = skipValue(data); |
|
1596 debug("Skip malformed well known header(" + index + "): " |
|
1597 + JSON.stringify({name: entry.name, value: value})); |
|
1598 |
|
1599 return null; |
|
1600 } |
|
1601 |
|
1602 return { |
|
1603 name: entry.name, |
|
1604 value: value, |
|
1605 }; |
|
1606 }, |
|
1607 |
|
1608 /** |
|
1609 * @param data |
|
1610 * A wrapped object to store encoded raw data. |
|
1611 * @param header |
|
1612 * An object containing two attributes: a string-typed `name` and a |
|
1613 * `value` of arbitrary type. |
|
1614 */ |
|
1615 encode: function(data, header) { |
|
1616 let entry = WSP_HEADER_FIELDS[header.name.toLowerCase()]; |
|
1617 if (!entry) { |
|
1618 throw new NotWellKnownEncodingError( |
|
1619 "Well-known-header: not well known header " + header.name); |
|
1620 } |
|
1621 |
|
1622 ShortInteger.encode(data, entry.number); |
|
1623 encodeAlternatives(data, header.value, null, entry.coder, TextValue); |
|
1624 }, |
|
1625 }; |
|
1626 |
|
1627 /** |
|
1628 * Application-header = Token-text Application-specific-value |
|
1629 * Application-specific-value = Text-string |
|
1630 * |
|
1631 * @see WAP-230-WSP-20010705-a clause 8.4.2.6 |
|
1632 */ |
|
1633 this.ApplicationHeader = { |
|
1634 /** |
|
1635 * @param data |
|
1636 * A wrapped object containing raw PDU data. |
|
1637 * |
|
1638 * @return A decoded object containing `name` and `value` properties or null |
|
1639 * in case of a failed parsing. The `name` property must be a string, |
|
1640 * but the `value` property can be many different types depending on |
|
1641 * `name`. |
|
1642 */ |
|
1643 decode: function(data) { |
|
1644 let name = TokenText.decode(data); |
|
1645 |
|
1646 let begin = data.offset, value; |
|
1647 try { |
|
1648 value = TextString.decode(data); |
|
1649 } catch (e) { |
|
1650 data.offset = begin; |
|
1651 |
|
1652 value = skipValue(data); |
|
1653 debug("Skip malformed application header: " |
|
1654 + JSON.stringify({name: name, value: value})); |
|
1655 |
|
1656 return null; |
|
1657 } |
|
1658 |
|
1659 return { |
|
1660 name: name.toLowerCase(), |
|
1661 value: value, |
|
1662 }; |
|
1663 }, |
|
1664 |
|
1665 /** |
|
1666 * @param data |
|
1667 * A wrapped object to store encoded raw data. |
|
1668 * @param header |
|
1669 * An object containing two attributes: a string-typed `name` and a |
|
1670 * `value` of arbitrary type. |
|
1671 * |
|
1672 * @throws CodeError if got an empty header name. |
|
1673 */ |
|
1674 encode: function(data, header) { |
|
1675 if (!header.name) { |
|
1676 throw new CodeError("Application-header: empty header name"); |
|
1677 } |
|
1678 |
|
1679 TokenText.encode(data, header.name); |
|
1680 TextString.encode(data, header.value); |
|
1681 }, |
|
1682 }; |
|
1683 |
|
1684 /** |
|
1685 * Field-name = Token-text | Well-known-field-name |
|
1686 * Well-known-field-name = Short-integer |
|
1687 * |
|
1688 * @see WAP-230-WSP-20010705-a clause 8.4.2.6 |
|
1689 */ |
|
1690 this.FieldName = { |
|
1691 /** |
|
1692 * @param data |
|
1693 * A wrapped object containing raw PDU data. |
|
1694 * |
|
1695 * @return A field name string. |
|
1696 * |
|
1697 * @throws NotWellKnownEncodingError if decoded well-known header field |
|
1698 * number is not registered or supported. |
|
1699 */ |
|
1700 decode: function(data) { |
|
1701 let begin = data.offset; |
|
1702 try { |
|
1703 return TokenText.decode(data).toLowerCase(); |
|
1704 } catch (e) {} |
|
1705 |
|
1706 data.offset = begin; |
|
1707 |
|
1708 let number = ShortInteger.decode(data); |
|
1709 let entry = WSP_HEADER_FIELDS[number]; |
|
1710 if (!entry) { |
|
1711 throw new NotWellKnownEncodingError( |
|
1712 "Field-name: not well known encoding " + number); |
|
1713 } |
|
1714 |
|
1715 return entry.name; |
|
1716 }, |
|
1717 |
|
1718 /** |
|
1719 * @param data |
|
1720 * A wrapped object to store encoded raw data. |
|
1721 * @param name |
|
1722 * A field name string. |
|
1723 */ |
|
1724 encode: function(data, name) { |
|
1725 let entry = WSP_HEADER_FIELDS[name.toLowerCase()]; |
|
1726 if (entry) { |
|
1727 ShortInteger.encode(data, entry.number); |
|
1728 } else { |
|
1729 TokenText.encode(data, name); |
|
1730 } |
|
1731 }, |
|
1732 }; |
|
1733 |
|
1734 /** |
|
1735 * Accept-charset-value = Constrained-charset | Accept-charset-general-form |
|
1736 * Constrained-charset = Any-charset | Constrained-encoding |
|
1737 * Any-charset = <Octet 128> |
|
1738 * Accept-charset-general-form = Value-length (Well-known-charset | Token-text) [Q-value] |
|
1739 * |
|
1740 * @see WAP-230-WSP-20010705-a clause 8.4.2.8 |
|
1741 */ |
|
1742 this.AcceptCharsetValue = { |
|
1743 /** |
|
1744 * @param data |
|
1745 * A wrapped object containing raw PDU data. |
|
1746 * |
|
1747 * @return A object with a property `charset` of string "*". |
|
1748 */ |
|
1749 decodeAnyCharset: function(data) { |
|
1750 Octet.decodeEqualTo(data, 128); |
|
1751 return {charset: "*"}; |
|
1752 }, |
|
1753 |
|
1754 /** |
|
1755 * @param data |
|
1756 * A wrapped object containing raw PDU data. |
|
1757 * |
|
1758 * @return A object with a string property `charset` and a optional integer |
|
1759 * property `q`. |
|
1760 * |
|
1761 * @throws NotWellKnownEncodingError if decoded well-known charset number is |
|
1762 * not registered or supported. |
|
1763 */ |
|
1764 decodeConstrainedCharset: function(data) { |
|
1765 let begin = data.offset; |
|
1766 try { |
|
1767 return this.decodeAnyCharset(data); |
|
1768 } catch (e) {} |
|
1769 |
|
1770 data.offset = begin; |
|
1771 |
|
1772 let numOrStr = ConstrainedEncoding.decode(data); |
|
1773 if (typeof numOrStr == "string") { |
|
1774 return {charset: numOrStr}; |
|
1775 } |
|
1776 |
|
1777 let charset = numOrStr; |
|
1778 let entry = WSP_WELL_KNOWN_CHARSETS[charset]; |
|
1779 if (!entry) { |
|
1780 throw new NotWellKnownEncodingError( |
|
1781 "Constrained-charset: not well known charset: " + charset); |
|
1782 } |
|
1783 |
|
1784 return {charset: entry.name}; |
|
1785 }, |
|
1786 |
|
1787 /** |
|
1788 * @param data |
|
1789 * A wrapped object containing raw PDU data. |
|
1790 * |
|
1791 * @return A object with a string property `charset` and a optional integer |
|
1792 * property `q`. |
|
1793 */ |
|
1794 decodeAcceptCharsetGeneralForm: function(data) { |
|
1795 let length = ValueLength.decode(data); |
|
1796 |
|
1797 let begin = data.offset; |
|
1798 let end = begin + length; |
|
1799 |
|
1800 let result; |
|
1801 try { |
|
1802 result = WellKnownCharset.decode(data); |
|
1803 } catch (e) { |
|
1804 data.offset = begin; |
|
1805 |
|
1806 result = {charset: TokenText.decode(data)}; |
|
1807 if (data.offset < end) { |
|
1808 result.q = QValue.decode(data); |
|
1809 } |
|
1810 } |
|
1811 |
|
1812 if (data.offset != end) { |
|
1813 data.offset = end; |
|
1814 } |
|
1815 |
|
1816 return result; |
|
1817 }, |
|
1818 |
|
1819 /** |
|
1820 * @param data |
|
1821 * A wrapped object containing raw PDU data. |
|
1822 * |
|
1823 * @return A object with a string property `charset` and a optional integer |
|
1824 * property `q`. |
|
1825 */ |
|
1826 decode: function(data) { |
|
1827 let begin = data.offset; |
|
1828 try { |
|
1829 return this.decodeConstrainedCharset(data); |
|
1830 } catch (e) { |
|
1831 data.offset = begin; |
|
1832 return this.decodeAcceptCharsetGeneralForm(data); |
|
1833 } |
|
1834 }, |
|
1835 |
|
1836 /** |
|
1837 * @param data |
|
1838 * A wrapped object to store encoded raw data. |
|
1839 * @param value |
|
1840 * An object with a string property `charset`. |
|
1841 */ |
|
1842 encodeAnyCharset: function(data, value) { |
|
1843 if (!value || !value.charset || (value.charset === "*")) { |
|
1844 Octet.encode(data, 128); |
|
1845 return; |
|
1846 } |
|
1847 |
|
1848 throw new CodeError("Any-charset: invalid value " + value); |
|
1849 }, |
|
1850 }; |
|
1851 |
|
1852 /** |
|
1853 * Well-known-charset = Any-charset | Integer-value |
|
1854 * |
|
1855 * @see WAP-230-WSP-20010705-a clause 8.4.2.8 |
|
1856 */ |
|
1857 this.WellKnownCharset = { |
|
1858 /** |
|
1859 * @param data |
|
1860 * A wrapped object containing raw PDU data. |
|
1861 * |
|
1862 * @return A object with a string property `charset`. |
|
1863 * |
|
1864 * @throws CodeError if decoded charset number is an array. |
|
1865 * @throws NotWellKnownEncodingError if decoded well-known charset number |
|
1866 * is not registered or supported. |
|
1867 */ |
|
1868 decode: function(data) { |
|
1869 let begin = data.offset; |
|
1870 |
|
1871 try { |
|
1872 return AcceptCharsetValue.decodeAnyCharset(data); |
|
1873 } catch (e) {} |
|
1874 |
|
1875 data.offset = begin; |
|
1876 |
|
1877 // `IntegerValue.decode` can return a array, which doesn't apply here. |
|
1878 let numOrArray = IntegerValue.decode(data); |
|
1879 if (typeof numOrArray != "number") { |
|
1880 throw new CodeError("Well-known-charset: invalid integer type"); |
|
1881 } |
|
1882 |
|
1883 let charset = numOrArray; |
|
1884 let entry = WSP_WELL_KNOWN_CHARSETS[charset]; |
|
1885 if (!entry) { |
|
1886 throw new NotWellKnownEncodingError( |
|
1887 "Well-known-charset: not well known charset " + charset); |
|
1888 } |
|
1889 |
|
1890 return {charset: entry.name}; |
|
1891 }, |
|
1892 |
|
1893 /** |
|
1894 * @param data |
|
1895 * A wrapped object to store encoded raw data. |
|
1896 * @param value |
|
1897 */ |
|
1898 encode: function(data, value) { |
|
1899 let begin = data.offset; |
|
1900 try { |
|
1901 AcceptCharsetValue.encodeAnyCharset(data, value); |
|
1902 return; |
|
1903 } catch (e) {} |
|
1904 |
|
1905 data.offset = begin; |
|
1906 let entry = WSP_WELL_KNOWN_CHARSETS[value.charset.toLowerCase()]; |
|
1907 if (!entry) { |
|
1908 throw new NotWellKnownEncodingError( |
|
1909 "Well-known-charset: not well known charset " + value.charset); |
|
1910 } |
|
1911 |
|
1912 IntegerValue.encode(data, entry.number); |
|
1913 }, |
|
1914 }; |
|
1915 |
|
1916 /** |
|
1917 * The short form of the Content-type-value MUST only be used when the |
|
1918 * well-known media is in the range of 0-127 or a text string. In all other |
|
1919 * cases the general form MUST be used. |
|
1920 * |
|
1921 * Content-type-value = Constrained-media | Content-general-form |
|
1922 * Constrained-media = Constrained-encoding |
|
1923 * Content-general-form = Value-length Media-type |
|
1924 * Media-type = Media *(Parameter) |
|
1925 * Media = Well-known-media | Extension-Media |
|
1926 * Well-known-media = Integer-value |
|
1927 * Extension-Media = *TEXT End-of-string |
|
1928 * |
|
1929 * @see WAP-230-WSP-20010705-a clause 8.4.2.24 |
|
1930 */ |
|
1931 this.ContentTypeValue = { |
|
1932 /** |
|
1933 * @param data |
|
1934 * A wrapped object containing raw PDU data. |
|
1935 * |
|
1936 * @return A decoded object containing `media` and `params` properties or |
|
1937 * null in case of a failed parsing. The `media` property must be a |
|
1938 * string, and the `params` property is always null. |
|
1939 * |
|
1940 * @throws NotWellKnownEncodingError if decoded well-known content type number |
|
1941 * is not registered or supported. |
|
1942 */ |
|
1943 decodeConstrainedMedia: function(data) { |
|
1944 return { |
|
1945 media: TypeValue.decode(data), |
|
1946 params: null, |
|
1947 }; |
|
1948 }, |
|
1949 |
|
1950 /** |
|
1951 * @param data |
|
1952 * A wrapped object containing raw PDU data. |
|
1953 * |
|
1954 * @return Decode string. |
|
1955 * |
|
1956 * @throws CodeError if decoded content type number is an array. |
|
1957 * @throws NotWellKnownEncodingError if decoded well-known content type |
|
1958 * number is not registered or supported. |
|
1959 */ |
|
1960 decodeMedia: function(data) { |
|
1961 let begin = data.offset, number; |
|
1962 try { |
|
1963 number = IntegerValue.decode(data); |
|
1964 } catch (e) { |
|
1965 data.offset = begin; |
|
1966 return NullTerminatedTexts.decode(data).toLowerCase(); |
|
1967 } |
|
1968 |
|
1969 // `decodeIntegerValue` can return a array, which doesn't apply here. |
|
1970 if (typeof number != "number") { |
|
1971 throw new CodeError("Media: invalid integer type"); |
|
1972 } |
|
1973 |
|
1974 let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; |
|
1975 if (!entry) { |
|
1976 throw new NotWellKnownEncodingError("Media: not well known media " + number); |
|
1977 } |
|
1978 |
|
1979 return entry.type; |
|
1980 }, |
|
1981 |
|
1982 /** |
|
1983 * @param data |
|
1984 * A wrapped object containing raw PDU data. |
|
1985 * @param end |
|
1986 * Ending offset of the Media-type value. |
|
1987 * |
|
1988 * @return A decoded object containing `media` and `params` properties or |
|
1989 * null in case of a failed parsing. The `media` property must be a |
|
1990 * string, and the `params` property is a hash map from a string to |
|
1991 * an value of unspecified type. |
|
1992 */ |
|
1993 decodeMediaType: function(data, end) { |
|
1994 let media = this.decodeMedia(data); |
|
1995 let params = Parameter.decodeMultiple(data, end); |
|
1996 |
|
1997 return { |
|
1998 media: media, |
|
1999 params: params, |
|
2000 }; |
|
2001 }, |
|
2002 |
|
2003 /** |
|
2004 * @param data |
|
2005 * A wrapped object containing raw PDU data. |
|
2006 * |
|
2007 * @return A decoded object containing `media` and `params` properties or |
|
2008 * null in case of a failed parsing. The `media` property must be a |
|
2009 * string, and the `params` property is null or a hash map from a |
|
2010 * string to an value of unspecified type. |
|
2011 */ |
|
2012 decodeContentGeneralForm: function(data) { |
|
2013 let length = ValueLength.decode(data); |
|
2014 let end = data.offset + length; |
|
2015 |
|
2016 let value = this.decodeMediaType(data, end); |
|
2017 |
|
2018 if (data.offset != end) { |
|
2019 data.offset = end; |
|
2020 } |
|
2021 |
|
2022 return value; |
|
2023 }, |
|
2024 |
|
2025 /** |
|
2026 * @param data |
|
2027 * A wrapped object containing raw PDU data. |
|
2028 * |
|
2029 * @return A decoded object containing `media` and `params` properties or |
|
2030 * null in case of a failed parsing. The `media` property must be a |
|
2031 * string, and the `params` property is null or a hash map from a |
|
2032 * string to an value of unspecified type. |
|
2033 */ |
|
2034 decode: function(data) { |
|
2035 let begin = data.offset; |
|
2036 |
|
2037 try { |
|
2038 return this.decodeConstrainedMedia(data); |
|
2039 } catch (e) { |
|
2040 data.offset = begin; |
|
2041 return this.decodeContentGeneralForm(data); |
|
2042 } |
|
2043 }, |
|
2044 |
|
2045 /** |
|
2046 * @param data |
|
2047 * A wrapped object to store encoded raw data. |
|
2048 * @param value |
|
2049 * An object containing `media` and `params` properties. |
|
2050 */ |
|
2051 encodeConstrainedMedia: function(data, value) { |
|
2052 if (value.params) { |
|
2053 throw new CodeError("Constrained-media: should use general form instead"); |
|
2054 } |
|
2055 |
|
2056 TypeValue.encode(data, value.media); |
|
2057 }, |
|
2058 |
|
2059 /** |
|
2060 * @param data |
|
2061 * A wrapped object to store encoded raw data. |
|
2062 * @param value |
|
2063 * An object containing `media` and `params` properties. |
|
2064 */ |
|
2065 encodeMediaType: function(data, value) { |
|
2066 let entry = WSP_WELL_KNOWN_CONTENT_TYPES[value.media.toLowerCase()]; |
|
2067 if (entry) { |
|
2068 IntegerValue.encode(data, entry.number); |
|
2069 } else { |
|
2070 NullTerminatedTexts.encode(data, value.media); |
|
2071 } |
|
2072 |
|
2073 Parameter.encodeMultiple(data, value.params); |
|
2074 }, |
|
2075 |
|
2076 /** |
|
2077 * @param data |
|
2078 * A wrapped object to store encoded raw data. |
|
2079 * @param value |
|
2080 * An object containing `media` and `params` properties. |
|
2081 */ |
|
2082 encodeContentGeneralForm: function(data, value) { |
|
2083 let begin = data.offset; |
|
2084 this.encodeMediaType(data, value); |
|
2085 |
|
2086 // Calculate how much octets will be written and seek back. |
|
2087 // TODO: use memmove, see bug 730873 |
|
2088 let len = data.offset - begin; |
|
2089 data.offset = begin; |
|
2090 |
|
2091 ValueLength.encode(data, len); |
|
2092 this.encodeMediaType(data, value); |
|
2093 }, |
|
2094 |
|
2095 /** |
|
2096 * @param data |
|
2097 * A wrapped object to store encoded raw data. |
|
2098 * @param value |
|
2099 * An object containing `media` and `params` properties. |
|
2100 */ |
|
2101 encode: function(data, value) { |
|
2102 let begin = data.offset; |
|
2103 |
|
2104 try { |
|
2105 this.encodeConstrainedMedia(data, value); |
|
2106 } catch (e) { |
|
2107 data.offset = begin; |
|
2108 this.encodeContentGeneralForm(data, value); |
|
2109 } |
|
2110 }, |
|
2111 }; |
|
2112 |
|
2113 /** |
|
2114 * Application-id-value = Uri-value | App-assigned-code |
|
2115 * App-assigned-code = Integer-value |
|
2116 * |
|
2117 * @see WAP-230-WSP-20010705-a clause 8.4.2.54 |
|
2118 */ |
|
2119 this.ApplicationIdValue = { |
|
2120 /** |
|
2121 * @param data |
|
2122 * A wrapped object containing raw PDU data. |
|
2123 * |
|
2124 * @return Decoded string value. |
|
2125 * |
|
2126 * @throws CodeError if decoded application id number is an array. |
|
2127 * @throws NotWellKnownEncodingError if decoded well-known application id |
|
2128 * number is not registered or supported. |
|
2129 */ |
|
2130 decode: function(data) { |
|
2131 let begin = data.offset; |
|
2132 try { |
|
2133 return UriValue.decode(data); |
|
2134 } catch (e) {} |
|
2135 |
|
2136 data.offset = begin; |
|
2137 |
|
2138 // `decodeIntegerValue` can return a array, which doesn't apply here. |
|
2139 let numOrArray = IntegerValue.decode(data); |
|
2140 if (typeof numOrArray != "number") { |
|
2141 throw new CodeError("Application-id-value: invalid integer type"); |
|
2142 } |
|
2143 |
|
2144 let id = numOrArray; |
|
2145 let entry = OMNA_PUSH_APPLICATION_IDS[id]; |
|
2146 if (!entry) { |
|
2147 throw new NotWellKnownEncodingError( |
|
2148 "Application-id-value: not well known id: " + id); |
|
2149 } |
|
2150 |
|
2151 return entry.urn; |
|
2152 }, |
|
2153 }; |
|
2154 |
|
2155 this.PduHelper = { |
|
2156 /** |
|
2157 * @param data |
|
2158 * A UInt8Array of data for decode. |
|
2159 * @param charset |
|
2160 * charset for decode |
|
2161 * |
|
2162 * @return Decoded string. |
|
2163 */ |
|
2164 decodeStringContent: function(data, charset) { |
|
2165 let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
|
2166 .createInstance(Ci.nsIScriptableUnicodeConverter); |
|
2167 |
|
2168 let entry; |
|
2169 if (charset) { |
|
2170 entry = WSP_WELL_KNOWN_CHARSETS[charset]; |
|
2171 } |
|
2172 // Set converter to default one if (entry && entry.converter) is null. |
|
2173 // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9 |
|
2174 conv.charset = (entry && entry.converter) || "UTF-8"; |
|
2175 try { |
|
2176 return conv.convertFromByteArray(data, data.length); |
|
2177 } catch (e) { |
|
2178 } |
|
2179 return null; |
|
2180 }, |
|
2181 |
|
2182 /** |
|
2183 * @param strContent |
|
2184 * Decoded string content. |
|
2185 * @param charset |
|
2186 * Charset for encode. |
|
2187 * |
|
2188 * @return An encoded UInt8Array of string content. |
|
2189 */ |
|
2190 encodeStringContent: function(strContent, charset) { |
|
2191 let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
|
2192 .createInstance(Ci.nsIScriptableUnicodeConverter); |
|
2193 |
|
2194 let entry; |
|
2195 if (charset) { |
|
2196 entry = WSP_WELL_KNOWN_CHARSETS[charset]; |
|
2197 } |
|
2198 // Set converter to default one if (entry && entry.converter) is null. |
|
2199 // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9 |
|
2200 conv.charset = (entry && entry.converter) || "UTF-8"; |
|
2201 try { |
|
2202 return conv.convertToByteArray(strContent); |
|
2203 } catch (e) { |
|
2204 } |
|
2205 return null; |
|
2206 }, |
|
2207 |
|
2208 /** |
|
2209 * Parse multiple header fields with end mark. |
|
2210 * |
|
2211 * @param data |
|
2212 * A wrapped object containing raw PDU data. |
|
2213 * @param end |
|
2214 * An ending offset indicating the end of headers. |
|
2215 * @param headers [optional] |
|
2216 * An optional object to store parsed header fields. Created |
|
2217 * automatically if undefined. |
|
2218 * |
|
2219 * @return A object containing decoded header fields as its attributes. |
|
2220 */ |
|
2221 parseHeaders: function(data, end, headers) { |
|
2222 if (!headers) { |
|
2223 headers = {}; |
|
2224 } |
|
2225 |
|
2226 let header; |
|
2227 while (data.offset < end) { |
|
2228 try { |
|
2229 header = Header.decode(data); |
|
2230 } catch (e) { |
|
2231 break; |
|
2232 } |
|
2233 if (header) { |
|
2234 headers[header.name] = header.value; |
|
2235 } |
|
2236 } |
|
2237 |
|
2238 if (data.offset != end) { |
|
2239 debug("Parser expects ending in " + end + ", but in " + data.offset); |
|
2240 // Explicitly seek to end in case of skipped header fields. |
|
2241 data.offset = end; |
|
2242 } |
|
2243 |
|
2244 return headers; |
|
2245 }, |
|
2246 |
|
2247 /** |
|
2248 * @param data |
|
2249 * A wrapped object containing raw PDU data. |
|
2250 * @param msg |
|
2251 * Message object to be populated with decoded header fields. |
|
2252 * |
|
2253 * @see WAP-230-WSP-20010705-a clause 8.2.4 |
|
2254 */ |
|
2255 parsePushHeaders: function(data, msg) { |
|
2256 if (!msg.headers) { |
|
2257 msg.headers = {}; |
|
2258 } |
|
2259 |
|
2260 let headersLen = UintVar.decode(data); |
|
2261 let headersEnd = data.offset + headersLen; |
|
2262 |
|
2263 let contentType = ContentTypeValue.decode(data); |
|
2264 msg.headers["content-type"] = contentType; |
|
2265 |
|
2266 msg.headers = this.parseHeaders(data, headersEnd, msg.headers); |
|
2267 }, |
|
2268 |
|
2269 /** |
|
2270 * @param data |
|
2271 * A wrapped object containing raw PDU data. |
|
2272 * |
|
2273 * @return An array of objects representing multipart entries or null in case |
|
2274 * of errors found. |
|
2275 * |
|
2276 * @see WAP-230-WSP-20010705-a section 8.5 |
|
2277 */ |
|
2278 parseMultiPart: function(data) { |
|
2279 let nEntries = UintVar.decode(data); |
|
2280 if (!nEntries) { |
|
2281 return null; |
|
2282 } |
|
2283 |
|
2284 let parts = new Array(nEntries); |
|
2285 for (let i = 0; i < nEntries; i++) { |
|
2286 // Length of the ContentType and Headers fields combined. |
|
2287 let headersLen = UintVar.decode(data); |
|
2288 // Length of the Data field |
|
2289 let contentLen = UintVar.decode(data); |
|
2290 |
|
2291 let headersEnd = data.offset + headersLen; |
|
2292 let contentEnd = headersEnd + contentLen; |
|
2293 |
|
2294 try { |
|
2295 let headers = {}; |
|
2296 |
|
2297 let contentType = ContentTypeValue.decode(data); |
|
2298 headers["content-type"] = contentType; |
|
2299 headers["content-length"] = contentLen; |
|
2300 |
|
2301 headers = this.parseHeaders(data, headersEnd, headers); |
|
2302 |
|
2303 let octetArray = Octet.decodeMultiple(data, contentEnd); |
|
2304 let content = null; |
|
2305 let charset = headers["content-type"].params && |
|
2306 headers["content-type"].params.charset |
|
2307 ? headers["content-type"].params.charset.charset |
|
2308 : null; |
|
2309 |
|
2310 let mimeType = headers["content-type"].media; |
|
2311 |
|
2312 if (mimeType) { |
|
2313 if (mimeType == "application/smil") { |
|
2314 // If the content is a SMIL type, convert it to a string. |
|
2315 // We hope to save and expose the SMIL content in a string way. |
|
2316 content = this.decodeStringContent(octetArray, charset); |
|
2317 } else if (mimeType.indexOf("text/") == 0 && charset != "utf-8") { |
|
2318 // If the content is a "text/plain" type, we have to make sure |
|
2319 // the encoding of the blob content should always be "utf-8". |
|
2320 let tmpStr = this.decodeStringContent(octetArray, charset); |
|
2321 let encoder = new TextEncoder("UTF-8"); |
|
2322 content = new Blob([encoder.encode(tmpStr)], {type : mimeType}); |
|
2323 |
|
2324 // Make up the missing encoding info. |
|
2325 if (!headers["content-type"].params) { |
|
2326 headers["content-type"].params = {}; |
|
2327 } |
|
2328 if (!headers["content-type"].params.charset) { |
|
2329 headers["content-type"].params.charset = {}; |
|
2330 } |
|
2331 headers["content-type"].params.charset.charset = "utf-8"; |
|
2332 } |
|
2333 } |
|
2334 |
|
2335 if (!content) { |
|
2336 content = new Blob([octetArray], {type : mimeType}); |
|
2337 } |
|
2338 |
|
2339 parts[i] = { |
|
2340 index: i, |
|
2341 headers: headers, |
|
2342 content: content, |
|
2343 }; |
|
2344 } catch (e) { |
|
2345 debug("Failed to parse multipart entry, message: " + e.message); |
|
2346 // Placeholder to keep original index of following entries. |
|
2347 parts[i] = null; |
|
2348 } |
|
2349 |
|
2350 if (data.offset != contentEnd) { |
|
2351 // Seek to entry boundary for next entry. |
|
2352 data.offset = contentEnd; |
|
2353 } |
|
2354 } |
|
2355 |
|
2356 return parts; |
|
2357 }, |
|
2358 |
|
2359 /** |
|
2360 * @param data |
|
2361 * A wrapped object containing raw PDU data. |
|
2362 * @param isSessionless |
|
2363 * Whether or not the PDU contains a session less WSP PDU. |
|
2364 * @param msg [optional] |
|
2365 * Optional pre-defined PDU object. |
|
2366 * |
|
2367 * @return Parsed WSP PDU object or null in case of errors found. |
|
2368 */ |
|
2369 parse: function(data, isSessionless, msg) { |
|
2370 if (!msg) { |
|
2371 msg = { |
|
2372 type: null, |
|
2373 }; |
|
2374 } |
|
2375 |
|
2376 try { |
|
2377 if (isSessionless) { |
|
2378 // "The `transactionId` is used to associate requests with replies in |
|
2379 // the connectionless session service." ~ WAP-230-WSP-20010705-a 8.2.1 |
|
2380 msg.transactionId = Octet.decode(data); |
|
2381 } |
|
2382 |
|
2383 msg.type = Octet.decode(data); |
|
2384 switch (msg.type) { |
|
2385 case WSP_PDU_TYPE_PUSH: |
|
2386 this.parsePushHeaders(data, msg); |
|
2387 break; |
|
2388 } |
|
2389 } catch (e) { |
|
2390 debug("Parse error. Message: " + e.message); |
|
2391 msg = null; |
|
2392 } |
|
2393 |
|
2394 return msg; |
|
2395 }, |
|
2396 |
|
2397 /** |
|
2398 * @param multiStream |
|
2399 * An exsiting nsIMultiplexInputStream. |
|
2400 * @param array |
|
2401 * An octet array. |
|
2402 * @param length |
|
2403 * Max number of octets to be coverted into an input stream. |
|
2404 */ |
|
2405 appendArrayToMultiStream: function(multiStream, array, length) { |
|
2406 let storageStream = Cc["@mozilla.org/storagestream;1"] |
|
2407 .createInstance(Ci.nsIStorageStream); |
|
2408 storageStream.init(4096, length, null); |
|
2409 |
|
2410 let boStream = Cc["@mozilla.org/binaryoutputstream;1"] |
|
2411 .createInstance(Ci.nsIBinaryOutputStream); |
|
2412 boStream.setOutputStream(storageStream.getOutputStream(0)); |
|
2413 boStream.writeByteArray(array, length); |
|
2414 boStream.close(); |
|
2415 |
|
2416 multiStream.appendStream(storageStream.newInputStream(0)); |
|
2417 }, |
|
2418 |
|
2419 /** |
|
2420 * @param multiStream |
|
2421 * An exsiting nsIMultiplexInputStream. |
|
2422 * @param parts |
|
2423 * An array of objects representing multipart entries. |
|
2424 * |
|
2425 * @see WAP-230-WSP-20010705-a section 8.5 |
|
2426 */ |
|
2427 composeMultiPart: function(multiStream, parts) { |
|
2428 // Encode multipart header |
|
2429 { |
|
2430 let data = {array: [], offset: 0}; |
|
2431 UintVar.encode(data, parts.length); |
|
2432 debug("Encoded multipart header: " + JSON.stringify(data.array)); |
|
2433 this.appendArrayToMultiStream(multiStream, data.array, data.offset); |
|
2434 } |
|
2435 |
|
2436 // Encode each part |
|
2437 for (let i = 0; i < parts.length; i++) { |
|
2438 let part = parts[i]; |
|
2439 let data = {array: [], offset: 0}; |
|
2440 |
|
2441 // Encode Content-Type |
|
2442 let contentType = part.headers["content-type"]; |
|
2443 ContentTypeValue.encode(data, contentType); |
|
2444 |
|
2445 // Encode other headers |
|
2446 if (Object.keys(part).length > 1) { |
|
2447 // Remove Content-Type temporarily |
|
2448 delete part.headers["content-type"]; |
|
2449 |
|
2450 for (let name in part.headers) { |
|
2451 Header.encode(data, {name: name, value: part.headers[name]}); |
|
2452 } |
|
2453 |
|
2454 // Restore Content-Type back |
|
2455 part.headers["content-type"] = contentType; |
|
2456 } |
|
2457 |
|
2458 // Encode headersLen, DataLen |
|
2459 let headersLen = data.offset; |
|
2460 let content = part.content; |
|
2461 UintVar.encode(data, headersLen); |
|
2462 if (typeof content === "string") { |
|
2463 let charset; |
|
2464 if (contentType && contentType.params && contentType.params.charset && |
|
2465 contentType.params.charset.charset) { |
|
2466 charset = contentType.params.charset.charset; |
|
2467 } |
|
2468 content = this.encodeStringContent(content, charset); |
|
2469 UintVar.encode(data, content.length); |
|
2470 } else if (part.content instanceof Uint8Array) { |
|
2471 UintVar.encode(data, content.length); |
|
2472 } else { |
|
2473 throw new TypeError(); |
|
2474 } |
|
2475 |
|
2476 // Move them to the beginning of encoded octet array. |
|
2477 let slice1 = data.array.slice(headersLen); |
|
2478 let slice2 = data.array.slice(0, headersLen); |
|
2479 data.array = slice1.concat(slice2); |
|
2480 debug("Encoded per-part header: " + JSON.stringify(data.array)); |
|
2481 |
|
2482 // Append per-part header |
|
2483 this.appendArrayToMultiStream(multiStream, data.array, data.offset); |
|
2484 // Append part content |
|
2485 this.appendArrayToMultiStream(multiStream, content, content.length); |
|
2486 } |
|
2487 }, |
|
2488 }; |
|
2489 |
|
2490 // WSP Header Field Name Assignments |
|
2491 // Note: Items commented out are either deprecated or not implemented. |
|
2492 // Deprecated items should only be supported for backward compatibility |
|
2493 // purpose. |
|
2494 // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. |
|
2495 this.WSP_HEADER_FIELDS = (function() { |
|
2496 let names = {}; |
|
2497 function add(name, number, coder) { |
|
2498 let entry = { |
|
2499 name: name, |
|
2500 number: number, |
|
2501 coder: coder, |
|
2502 }; |
|
2503 names[name] = names[number] = entry; |
|
2504 } |
|
2505 |
|
2506 // Encoding Version: 1.1 |
|
2507 //add("accept", 0x00); |
|
2508 //add("accept-charset", 0x01); Deprecated |
|
2509 //add("accept-encoding", 0x02); Deprecated |
|
2510 //add("accept-language", 0x03); |
|
2511 //add("accept-ranges", 0x04); |
|
2512 add("age", 0x05, DeltaSecondsValue); |
|
2513 //add("allow", 0x06); |
|
2514 //add("authorization", 0x07); |
|
2515 //add("cache-control", 0x08); Deprecated |
|
2516 //add("connection", 0x09); |
|
2517 //add("content-base", 0x0A); Deprecated |
|
2518 //add("content-encoding", 0x0B); |
|
2519 //add("content-language", 0x0C); |
|
2520 add("content-length", 0x0D, IntegerValue); |
|
2521 add("content-location", 0x0E, UriValue); |
|
2522 //add("content-md5", 0x0F); |
|
2523 //add("content-range", 0x10); Deprecated |
|
2524 add("content-type", 0x11, ContentTypeValue); |
|
2525 add("date", 0x12, DateValue); |
|
2526 add("etag", 0x13, TextString); |
|
2527 add("expires", 0x14, DateValue); |
|
2528 add("from", 0x15, TextString); |
|
2529 add("host", 0x16, TextString); |
|
2530 add("if-modified-since", 0x17, DateValue); |
|
2531 add("if-match", 0x18, TextString); |
|
2532 add("if-none-match", 0x19, TextString); |
|
2533 //add("if-range", 0x1A); |
|
2534 add("if-unmodified-since", 0x1B, DateValue); |
|
2535 add("location", 0x1C, UriValue); |
|
2536 add("last-modified", 0x1D, DateValue); |
|
2537 add("max-forwards", 0x1E, IntegerValue); |
|
2538 //add("pragma", 0x1F); |
|
2539 //add("proxy-authenticate", 0x20); |
|
2540 //add("proxy-authentication", 0x21); |
|
2541 //add("public", 0x22); |
|
2542 //add("range", 0x23); |
|
2543 add("referer", 0x24, UriValue); |
|
2544 //add("retry-after", 0x25); |
|
2545 add("server", 0x26, TextString); |
|
2546 //add("transfer-encoding", 0x27); |
|
2547 add("upgrade", 0x28, TextString); |
|
2548 add("user-agent", 0x29, TextString); |
|
2549 //add("vary", 0x2A); |
|
2550 add("via", 0x2B, TextString); |
|
2551 //add("warning", 0x2C); |
|
2552 //add("www-authenticate", 0x2D); |
|
2553 //add("content-disposition", 0x2E); Deprecated |
|
2554 |
|
2555 // Encoding Version: 1.2 |
|
2556 add("x-wap-application-id", 0x2F, ApplicationIdValue); |
|
2557 add("x-wap-content-uri", 0x30, UriValue); |
|
2558 add("x-wap-initiator-uri", 0x31, UriValue); |
|
2559 //add("accept-application", 0x32); |
|
2560 add("bearer-indication", 0x33, IntegerValue); |
|
2561 add("push-flag", 0x34, ShortInteger); |
|
2562 add("profile", 0x35, UriValue); |
|
2563 //add("profile-diff", 0x36); |
|
2564 //add("profile-warning", 0x37); Deprecated |
|
2565 |
|
2566 // Encoding Version: 1.3 |
|
2567 //add("expect", 0x38); |
|
2568 //add("te", 0x39); |
|
2569 //add("trailer", 0x3A); |
|
2570 add("accept-charset", 0x3B, AcceptCharsetValue); |
|
2571 //add("accept-encoding", 0x3C); |
|
2572 //add("cache-control", 0x3D); Deprecated |
|
2573 //add("content-range", 0x3E); |
|
2574 add("x-wap-tod", 0x3F, DateValue); |
|
2575 add("content-id", 0x40, QuotedString); |
|
2576 //add("set-cookie", 0x41); |
|
2577 //add("cookie", 0x42); |
|
2578 //add("encoding-version", 0x43); |
|
2579 |
|
2580 // Encoding Version: 1.4 |
|
2581 //add("profile-warning", 0x44); |
|
2582 //add("content-disposition", 0x45); |
|
2583 //add("x-wap-security", 0x46); |
|
2584 //add("cache-control", 0x47); |
|
2585 |
|
2586 return names; |
|
2587 })(); |
|
2588 |
|
2589 // WSP Content Type Assignments |
|
2590 // @see http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.aspx |
|
2591 this.WSP_WELL_KNOWN_CONTENT_TYPES = (function() { |
|
2592 let types = {}; |
|
2593 |
|
2594 function add(type, number) { |
|
2595 let entry = { |
|
2596 type: type, |
|
2597 number: number, |
|
2598 }; |
|
2599 // For case like "text/x-vCalendar", we need toLoweCase() for generating |
|
2600 // the same index. |
|
2601 types[type.toLowerCase()] = types[number] = entry; |
|
2602 } |
|
2603 |
|
2604 // Well Known Values |
|
2605 // Encoding Version: 1.1 |
|
2606 add("*/*", 0x00); |
|
2607 add("text/*", 0x01); |
|
2608 add("text/html", 0x02); |
|
2609 add("text/plain", 0x03); |
|
2610 add("text/x-hdml", 0x04); |
|
2611 add("text/x-ttml", 0x05); |
|
2612 add("text/x-vCalendar", 0x06); |
|
2613 add("text/x-vCard", 0x07); |
|
2614 add("text/vnd.wap.wml", 0x08); |
|
2615 add("text/vnd.wap.wmlscript", 0x09); |
|
2616 add("text/vnd.wap.wta-event", 0x0A); |
|
2617 add("multipart/*", 0x0B); |
|
2618 add("multipart/mixed", 0x0C); |
|
2619 add("multipart/form-data", 0x0D); |
|
2620 add("multipart/byterantes", 0x0E); |
|
2621 add("multipart/alternative", 0x0F); |
|
2622 add("application/*", 0x10); |
|
2623 add("application/java-vm", 0x11); |
|
2624 add("application/x-www-form-urlencoded", 0x12); |
|
2625 add("application/x-hdmlc", 0x13); |
|
2626 add("application/vnd.wap.wmlc", 0x14); |
|
2627 add("application/vnd.wap.wmlscriptc", 0x15); |
|
2628 add("application/vnd.wap.wta-eventc", 0x16); |
|
2629 add("application/vnd.wap.uaprof", 0x17); |
|
2630 add("application/vnd.wap.wtls-ca-certificate", 0x18); |
|
2631 add("application/vnd.wap.wtls-user-certificate", 0x19); |
|
2632 add("application/x-x509-ca-cert", 0x1A); |
|
2633 add("application/x-x509-user-cert", 0x1B); |
|
2634 add("image/*", 0x1C); |
|
2635 add("image/gif", 0x1D); |
|
2636 add("image/jpeg", 0x1E); |
|
2637 add("image/tiff", 0x1F); |
|
2638 add("image/png", 0x20); |
|
2639 add("image/vnd.wap.wbmp", 0x21); |
|
2640 add("application/vnd.wap.multipart.*", 0x22); |
|
2641 add("application/vnd.wap.multipart.mixed", 0x23); |
|
2642 add("application/vnd.wap.multipart.form-data", 0x24); |
|
2643 add("application/vnd.wap.multipart.byteranges", 0x25); |
|
2644 add("application/vnd.wap.multipart.alternative", 0x26); |
|
2645 add("application/xml", 0x27); |
|
2646 add("text/xml", 0x28); |
|
2647 add("application/vnd.wap.wbxml", 0x29); |
|
2648 add("application/x-x968-cross-cert", 0x2A); |
|
2649 add("application/x-x968-ca-cert", 0x2B); |
|
2650 add("application/x-x968-user-cert", 0x2C); |
|
2651 add("text/vnd.wap.si", 0x2D); |
|
2652 |
|
2653 // Encoding Version: 1.2 |
|
2654 add("application/vnd.wap.sic", 0x2E); |
|
2655 add("text/vnd.wap.sl", 0x2F); |
|
2656 add("application/vnd.wap.slc", 0x30); |
|
2657 add("text/vnd.wap.co", 0x31); |
|
2658 add("application/vnd.wap.coc", 0x32); |
|
2659 add("application/vnd.wap.multipart.related", 0x33); |
|
2660 add("application/vnd.wap.sia", 0x34); |
|
2661 |
|
2662 // Encoding Version: 1.3 |
|
2663 add("text/vnd.wap.connectivity-xml", 0x35); |
|
2664 add("application/vnd.wap.connectivity-wbxml", 0x36); |
|
2665 |
|
2666 // Encoding Version: 1.4 |
|
2667 add("application/pkcs7-mime", 0x37); |
|
2668 add("application/vnd.wap.hashed-certificate", 0x38); |
|
2669 add("application/vnd.wap.signed-certificate", 0x39); |
|
2670 add("application/vnd.wap.cert-response", 0x3A); |
|
2671 add("application/xhtml+xml", 0x3B); |
|
2672 add("application/wml+xml", 0x3C); |
|
2673 add("text/css", 0x3D); |
|
2674 add("application/vnd.wap.mms-message", 0x3E); |
|
2675 add("application/vnd.wap.rollover-certificate", 0x3F); |
|
2676 |
|
2677 // Encoding Version: 1.5 |
|
2678 add("application/vnd.wap.locc+wbxml", 0x40); |
|
2679 add("application/vnd.wap.loc+xml", 0x41); |
|
2680 add("application/vnd.syncml.dm+wbxml", 0x42); |
|
2681 add("application/vnd.syncml.dm+xml", 0x43); |
|
2682 add("application/vnd.syncml.notification", 0x44); |
|
2683 add("application/vnd.wap.xhtml+xml", 0x45); |
|
2684 add("application/vnd.wv.csp.cir", 0x46); |
|
2685 add("application/vnd.oma.dd+xml", 0x47); |
|
2686 add("application/vnd.oma.drm.message", 0x48); |
|
2687 add("application/vnd.oma.drm.content", 0x49); |
|
2688 add("application/vnd.oma.drm.rights+xml", 0x4A); |
|
2689 add("application/vnd.oma.drm.rights+wbxml", 0x4B); |
|
2690 add("application/vnd.wv.csp+xml", 0x4C); |
|
2691 add("application/vnd.wv.csp+wbxml", 0x4D); |
|
2692 add("application/vnd.syncml.ds.notification", 0x4E); |
|
2693 |
|
2694 // Encoding Version: 1.6 |
|
2695 add("audio/*", 0x4F); |
|
2696 add("video/*", 0x50); |
|
2697 |
|
2698 // Encoding Version: TBD |
|
2699 add("application/vnd.oma.dd2+xml", 0x51); |
|
2700 add("application/mikey", 0x52); |
|
2701 add("application/vnd.oma.dcd", 0x53); |
|
2702 add("application/vnd.oma.dcdc", 0x54); |
|
2703 add("text/x-vMessage", 0x55); |
|
2704 add("application/vnd.omads-email+wbxml", 0x56); |
|
2705 add("text/x-vBookmark", 0x57); |
|
2706 add("application/vnd.syncml.dm.notification", 0x58); |
|
2707 add("application/octet-stream", 0x5A); |
|
2708 |
|
2709 return types; |
|
2710 })(); |
|
2711 |
|
2712 // WSP Well-Known Parameter Assignments |
|
2713 // Note: Items commented out are either deprecated or not implemented. |
|
2714 // Deprecated items should not be used. |
|
2715 // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. |
|
2716 this.WSP_WELL_KNOWN_PARAMS = (function() { |
|
2717 let params = {}; |
|
2718 |
|
2719 function add(name, number, coder) { |
|
2720 let entry = { |
|
2721 name: name, |
|
2722 number: number, |
|
2723 coder: coder, |
|
2724 }; |
|
2725 params[name] = params[number] = entry; |
|
2726 } |
|
2727 |
|
2728 // Encoding Version: 1.1 |
|
2729 add("q", 0x00, QValue); |
|
2730 add("charset", 0x01, WellKnownCharset); |
|
2731 add("level", 0x02, VersionValue); |
|
2732 add("type", 0x03, IntegerValue); |
|
2733 add("name", 0x05, TextValue); // Deprecated, but used in some carriers, eg. Hinet. |
|
2734 //add("filename", 0x06); Deprecated |
|
2735 add("differences", 0x07, FieldName); |
|
2736 add("padding", 0x08, ShortInteger); |
|
2737 |
|
2738 // Encoding Version: 1.2 |
|
2739 add("type", 0x09, TypeValue); |
|
2740 add("start", 0x0A, TextValue); // Deprecated, but used in some carriers, eg. T-Mobile. |
|
2741 //add("start-info", 0x0B); Deprecated |
|
2742 |
|
2743 // Encoding Version: 1.3 |
|
2744 //add("comment", 0x0C); Deprecated |
|
2745 //add("domain", 0x0D); Deprecated |
|
2746 add("max-age", 0x0E, DeltaSecondsValue); |
|
2747 //add("path", 0x0F); Deprecated |
|
2748 add("secure", 0x10, NoValue); |
|
2749 |
|
2750 // Encoding Version: 1.4 |
|
2751 add("sec", 0x11, ShortInteger); |
|
2752 add("mac", 0x12, TextValue); |
|
2753 add("creation-date", 0x13, DateValue); |
|
2754 add("modification-date", 0x14, DateValue); |
|
2755 add("read-date", 0x15, DateValue); |
|
2756 add("size", 0x16, IntegerValue); |
|
2757 //add("name", 0x17, TextValue); // Not supported in some carriers, eg. Hinet. |
|
2758 add("filename", 0x18, TextValue); |
|
2759 //add("start", 0x19, TextValue); // Not supported in some carriers, eg. Hinet. |
|
2760 add("start-info", 0x1A, TextValue); |
|
2761 add("comment", 0x1B, TextValue); |
|
2762 add("domain", 0x1C, TextValue); |
|
2763 add("path", 0x1D, TextValue); |
|
2764 |
|
2765 return params; |
|
2766 })(); |
|
2767 |
|
2768 // WSP Character Set Assignments |
|
2769 // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. |
|
2770 // @see http://www.iana.org/assignments/character-sets |
|
2771 this.WSP_WELL_KNOWN_CHARSETS = (function() { |
|
2772 let charsets = {}; |
|
2773 |
|
2774 function add(name, number, converter) { |
|
2775 let entry = { |
|
2776 name: name, |
|
2777 number: number, |
|
2778 converter: converter, |
|
2779 }; |
|
2780 |
|
2781 charsets[name] = charsets[number] = entry; |
|
2782 } |
|
2783 |
|
2784 add("us-ascii", 3, null); |
|
2785 add("iso-8859-1", 4, "ISO-8859-1"); |
|
2786 add("iso-8859-2", 5, "ISO-8859-2"); |
|
2787 add("iso-8859-3", 6, "ISO-8859-3"); |
|
2788 add("iso-8859-4", 7, "ISO-8859-4"); |
|
2789 add("iso-8859-5", 8, "ISO-8859-5"); |
|
2790 add("iso-8859-6", 9, "ISO-8859-6"); |
|
2791 add("iso-8859-7", 10, "ISO-8859-7"); |
|
2792 add("iso-8859-8", 11, "ISO-8859-8"); |
|
2793 add("iso-8859-9", 12, "ISO-8859-9"); |
|
2794 add("iso-8859-10", 13, "ISO-8859-10"); |
|
2795 add("shift_jis", 17, "Shift_JIS"); |
|
2796 add("euc-jp", 18, "EUC-JP"); |
|
2797 add("iso-2022-kr", 37, "ISO-2022-KR"); |
|
2798 add("euc-kr", 38, "EUC-KR"); |
|
2799 add("iso-2022-jp", 39, "ISO-2022-JP"); |
|
2800 add("iso-2022-jp-2", 40, "iso-2022-jp-2"); |
|
2801 add("iso-8859-6-e", 81, "ISO-8859-6-E"); |
|
2802 add("iso-8859-6-i", 82, "ISO-8859-6-I"); |
|
2803 add("iso-8859-8-e", 84, "ISO-8859-8-E"); |
|
2804 add("iso-8859-8-i", 85, "ISO-8859-8-I"); |
|
2805 add("utf-8", 106, "UTF-8"); |
|
2806 add("iso-10646-ucs-2", 1000, "iso-10646-ucs-2"); |
|
2807 add("utf-16", 1015, "UTF-16"); |
|
2808 add("gb2312", 2025, "GB2312"); |
|
2809 add("big5", 2026, "Big5"); |
|
2810 add("koi8-r", 2084, "KOI8-R"); |
|
2811 add("windows-1252", 2252, "windows-1252"); |
|
2812 |
|
2813 return charsets; |
|
2814 })(); |
|
2815 |
|
2816 // OMNA PUSH Application ID |
|
2817 // @see http://www.openmobilealliance.org/tech/omna/omna-push-app-id.aspx |
|
2818 this.OMNA_PUSH_APPLICATION_IDS = (function() { |
|
2819 let ids = {}; |
|
2820 |
|
2821 function add(urn, number) { |
|
2822 let entry = { |
|
2823 urn: urn, |
|
2824 number: number, |
|
2825 }; |
|
2826 |
|
2827 ids[urn] = ids[number] = entry; |
|
2828 } |
|
2829 |
|
2830 add("x-wap-application:wml.ua", 0x02); |
|
2831 add("x-wap-application:mms.ua", 0x04); |
|
2832 |
|
2833 return ids; |
|
2834 })(); |
|
2835 |
|
2836 let debug; |
|
2837 if (DEBUG) { |
|
2838 debug = function(s) { |
|
2839 dump("-@- WspPduHelper: " + s + "\n"); |
|
2840 }; |
|
2841 } else { |
|
2842 debug = function(s) {}; |
|
2843 } |
|
2844 |
|
2845 this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ |
|
2846 // Constant values |
|
2847 "WSP_HEADER_FIELDS", |
|
2848 "WSP_WELL_KNOWN_CONTENT_TYPES", |
|
2849 "WSP_WELL_KNOWN_PARAMS", |
|
2850 "WSP_WELL_KNOWN_CHARSETS", |
|
2851 "OMNA_PUSH_APPLICATION_IDS", |
|
2852 |
|
2853 // Error classes |
|
2854 "CodeError", |
|
2855 "FatalCodeError", |
|
2856 "NotWellKnownEncodingError", |
|
2857 |
|
2858 // Utility functions |
|
2859 "ensureHeader", |
|
2860 "skipValue", |
|
2861 "decodeAlternatives", |
|
2862 "encodeAlternatives", |
|
2863 |
|
2864 // Decoders |
|
2865 "Octet", |
|
2866 "Text", |
|
2867 "NullTerminatedTexts", |
|
2868 "Token", |
|
2869 "URIC", |
|
2870 "TextString", |
|
2871 "TokenText", |
|
2872 "QuotedString", |
|
2873 "ShortInteger", |
|
2874 "LongInteger", |
|
2875 "UintVar", |
|
2876 "ConstrainedEncoding", |
|
2877 "ValueLength", |
|
2878 "NoValue", |
|
2879 "TextValue", |
|
2880 "IntegerValue", |
|
2881 "DateValue", |
|
2882 "DeltaSecondsValue", |
|
2883 "QValue", |
|
2884 "VersionValue", |
|
2885 "UriValue", |
|
2886 "TypeValue", |
|
2887 "Parameter", |
|
2888 "Header", |
|
2889 "WellKnownHeader", |
|
2890 "ApplicationHeader", |
|
2891 "FieldName", |
|
2892 "AcceptCharsetValue", |
|
2893 "WellKnownCharset", |
|
2894 "ContentTypeValue", |
|
2895 "ApplicationIdValue", |
|
2896 |
|
2897 // Parser |
|
2898 "PduHelper", |
|
2899 ]); |