dom/mobilemessage/src/gonk/WspPduHelper.jsm

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:0404f0f558dc
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 ]);

mercurial