Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
5 "use strict";
7 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
9 Cu.import("resource://gre/modules/wap_consts.js", this);
11 let DEBUG; // set to true to see debug messages
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;
22 // Special ASCII character ranges
23 const CTLS = 32;
24 const ASCIIS = 128;
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;
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;
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;
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;
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 }
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 }
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 }
155 return value;
156 }
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 }
179 data.offset = begin;
180 }
181 }
182 }
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 }
206 data.offset = begin;
207 }
208 }
209 }
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 }
223 return data.array[data.offset++];
224 },
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 }
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 }
254 data.offset = end;
255 return result;
256 },
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 }
275 return expected;
276 },
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 },
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 };
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 }
334 if (code == NUL) {
335 throw new NullCharError();
336 }
338 if (code != CR) {
339 throw new CodeError("Text: invalid char code " + code);
340 }
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
347 let extra;
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 }
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 }
366 // Let's eat as many SP|HT as possible.
367 let begin;
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) {}
377 data.offset = begin;
378 return " ";
379 },
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 }
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 };
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 },
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 };
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 }
473 return String.fromCharCode(code);
474 }
476 if (code == NUL) {
477 throw new NullCharError();
478 }
480 throw new CodeError("Token: invalid char code " + code);
481 },
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 }
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 }
511 throw new CodeError("Token: invalid char code " + code);
512 },
513 };
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 }
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 }
549 return String.fromCharCode(code);
550 },
551 };
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 }
589 data.offset = begin;
590 return NullTerminatedTexts.decode(data);
591 },
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 }
607 let firstCharCode = str.charCodeAt(0);
608 if (firstCharCode >= 128) {
609 if (asciiOnly) {
610 throw new CodeError("Text: invalid char code " + code);
611 }
613 Octet.encode(data, 127);
614 }
616 NullTerminatedTexts.encode(data, str, asciiOnly);
617 },
618 };
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 },
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 };
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 }
683 return NullTerminatedTexts.decode(data);
684 },
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 };
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 }
722 return (value & 0x7F);
723 },
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 }
738 Octet.encode(data, value | 0x80);
739 },
740 };
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 }
775 return Octet.decodeMultiple(data, data.offset + length);
776 },
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 }
792 return this.decodeMultiOctetInteger(data, length);
793 },
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 }
809 let stack = [];
810 do {
811 stack.push(Math.floor(num % 256));
812 num = Math.floor(num / 256);
813 } while (num);
815 Octet.encode(data, stack.length);
816 while (stack.length) {
817 Octet.encode(data, stack.pop());
818 }
819 return;
820 }
822 let array = numOrArray;
823 if ((array.length < 1) || (array.length > 30)) {
824 throw new CodeError("Long-integer: invalid length " + array.length);
825 }
827 Octet.encode(data, array.length);
828 Octet.encodeMultiple(data, array);
829 },
830 };
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 }
850 return result;
851 },
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 }
864 let stack = [];
865 while (value >= 128) {
866 stack.push(Math.floor(value % 128));
867 value = Math.floor(value / 128);
868 }
870 while (stack.length) {
871 Octet.encode(data, value | 0x80);
872 value = stack.pop();
873 }
874 Octet.encode(data, value);
875 },
876 };
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 },
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 };
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 }
939 if (value == 31) {
940 return UintVar.decode(data);
941 }
943 throw new CodeError("Value-length: invalid value " + value);
944 },
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 };
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 },
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 };
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 },
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 };
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 },
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 };
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 }
1079 return new Date(seconds * 1000);
1080 },
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 }
1094 LongInteger.encode(data, seconds);
1095 },
1096 };
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;
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 }
1133 throw new CodeError("Q-value: invalid value " + value);
1134 },
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 }
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 };
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 }
1185 throw new CodeError("Version-value: invalid value " + value);
1186 } catch (e) {}
1188 data.offset = begin;
1190 let str = TextString.decode(data);
1191 if (!str.match(/^[1-7](\.1?\d)?$/)) {
1192 throw new CodeError("Version-value: invalid value " + str);
1193 }
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 }
1207 return major << 4 | minor;
1208 },
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 }
1221 ShortInteger.encode(data, version);
1222 },
1223 };
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 };
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 }
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 }
1281 return entry.type;
1282 },
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 };
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 }
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 }
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;
1361 // Skip current parameter.
1362 value = skipValue(data);
1363 debug("Skip malformed typed parameter: "
1364 + JSON.stringify({name: param.name, value: value}));
1366 return null;
1367 }
1369 return {
1370 name: param.name,
1371 value: value,
1372 };
1373 },
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);
1386 let begin = data.offset, value;
1387 try {
1388 value = decodeAlternatives(data, null, IntegerValue, TextValue);
1389 } catch (e) {
1390 data.offset = begin;
1392 // Skip current parameter.
1393 value = skipValue(data);
1394 debug("Skip malformed untyped parameter: "
1395 + JSON.stringify({name: name, value: value}));
1397 return null;
1398 }
1400 return {
1401 name: name.toLowerCase(),
1402 value: value,
1403 };
1404 },
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 },
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;
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 }
1449 return params;
1450 },
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 }
1465 IntegerValue.encode(data, entry.number);
1466 encodeAlternatives(data, param.value, null,
1467 entry.coder, TextValue, TextString);
1468 },
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 },
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 },
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 };
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 },
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 },
1544 encodeMessageHeader: function(data, header) {
1545 encodeAlternatives(data, header, null, WellKnownHeader, ApplicationHeader);
1546 },
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 };
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);
1583 let entry = WSP_HEADER_FIELDS[index];
1584 if (!entry) {
1585 throw new NotWellKnownEncodingError(
1586 "Well-known-header: not well known header " + index);
1587 }
1589 let begin = data.offset, value;
1590 try {
1591 value = decodeAlternatives(data, null, entry.coder, TextValue);
1592 } catch (e) {
1593 data.offset = begin;
1595 value = skipValue(data);
1596 debug("Skip malformed well known header(" + index + "): "
1597 + JSON.stringify({name: entry.name, value: value}));
1599 return null;
1600 }
1602 return {
1603 name: entry.name,
1604 value: value,
1605 };
1606 },
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 }
1622 ShortInteger.encode(data, entry.number);
1623 encodeAlternatives(data, header.value, null, entry.coder, TextValue);
1624 },
1625 };
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);
1646 let begin = data.offset, value;
1647 try {
1648 value = TextString.decode(data);
1649 } catch (e) {
1650 data.offset = begin;
1652 value = skipValue(data);
1653 debug("Skip malformed application header: "
1654 + JSON.stringify({name: name, value: value}));
1656 return null;
1657 }
1659 return {
1660 name: name.toLowerCase(),
1661 value: value,
1662 };
1663 },
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 }
1679 TokenText.encode(data, header.name);
1680 TextString.encode(data, header.value);
1681 },
1682 };
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) {}
1706 data.offset = begin;
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 }
1715 return entry.name;
1716 },
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 };
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 },
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) {}
1770 data.offset = begin;
1772 let numOrStr = ConstrainedEncoding.decode(data);
1773 if (typeof numOrStr == "string") {
1774 return {charset: numOrStr};
1775 }
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 }
1784 return {charset: entry.name};
1785 },
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);
1797 let begin = data.offset;
1798 let end = begin + length;
1800 let result;
1801 try {
1802 result = WellKnownCharset.decode(data);
1803 } catch (e) {
1804 data.offset = begin;
1806 result = {charset: TokenText.decode(data)};
1807 if (data.offset < end) {
1808 result.q = QValue.decode(data);
1809 }
1810 }
1812 if (data.offset != end) {
1813 data.offset = end;
1814 }
1816 return result;
1817 },
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 },
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 }
1848 throw new CodeError("Any-charset: invalid value " + value);
1849 },
1850 };
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;
1871 try {
1872 return AcceptCharsetValue.decodeAnyCharset(data);
1873 } catch (e) {}
1875 data.offset = begin;
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 }
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 }
1890 return {charset: entry.name};
1891 },
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) {}
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 }
1912 IntegerValue.encode(data, entry.number);
1913 },
1914 };
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 },
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 }
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 }
1974 let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number];
1975 if (!entry) {
1976 throw new NotWellKnownEncodingError("Media: not well known media " + number);
1977 }
1979 return entry.type;
1980 },
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);
1997 return {
1998 media: media,
1999 params: params,
2000 };
2001 },
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;
2016 let value = this.decodeMediaType(data, end);
2018 if (data.offset != end) {
2019 data.offset = end;
2020 }
2022 return value;
2023 },
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;
2037 try {
2038 return this.decodeConstrainedMedia(data);
2039 } catch (e) {
2040 data.offset = begin;
2041 return this.decodeContentGeneralForm(data);
2042 }
2043 },
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 }
2056 TypeValue.encode(data, value.media);
2057 },
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 }
2073 Parameter.encodeMultiple(data, value.params);
2074 },
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);
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;
2091 ValueLength.encode(data, len);
2092 this.encodeMediaType(data, value);
2093 },
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;
2104 try {
2105 this.encodeConstrainedMedia(data, value);
2106 } catch (e) {
2107 data.offset = begin;
2108 this.encodeContentGeneralForm(data, value);
2109 }
2110 },
2111 };
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) {}
2136 data.offset = begin;
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 }
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 }
2151 return entry.urn;
2152 },
2153 };
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);
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 },
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);
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 },
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 }
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 }
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 }
2244 return headers;
2245 },
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 }
2260 let headersLen = UintVar.decode(data);
2261 let headersEnd = data.offset + headersLen;
2263 let contentType = ContentTypeValue.decode(data);
2264 msg.headers["content-type"] = contentType;
2266 msg.headers = this.parseHeaders(data, headersEnd, msg.headers);
2267 },
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 }
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);
2291 let headersEnd = data.offset + headersLen;
2292 let contentEnd = headersEnd + contentLen;
2294 try {
2295 let headers = {};
2297 let contentType = ContentTypeValue.decode(data);
2298 headers["content-type"] = contentType;
2299 headers["content-length"] = contentLen;
2301 headers = this.parseHeaders(data, headersEnd, headers);
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;
2310 let mimeType = headers["content-type"].media;
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});
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 }
2335 if (!content) {
2336 content = new Blob([octetArray], {type : mimeType});
2337 }
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 }
2350 if (data.offset != contentEnd) {
2351 // Seek to entry boundary for next entry.
2352 data.offset = contentEnd;
2353 }
2354 }
2356 return parts;
2357 },
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 }
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 }
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 }
2394 return msg;
2395 },
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);
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();
2416 multiStream.appendStream(storageStream.newInputStream(0));
2417 },
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 }
2436 // Encode each part
2437 for (let i = 0; i < parts.length; i++) {
2438 let part = parts[i];
2439 let data = {array: [], offset: 0};
2441 // Encode Content-Type
2442 let contentType = part.headers["content-type"];
2443 ContentTypeValue.encode(data, contentType);
2445 // Encode other headers
2446 if (Object.keys(part).length > 1) {
2447 // Remove Content-Type temporarily
2448 delete part.headers["content-type"];
2450 for (let name in part.headers) {
2451 Header.encode(data, {name: name, value: part.headers[name]});
2452 }
2454 // Restore Content-Type back
2455 part.headers["content-type"] = contentType;
2456 }
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 }
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));
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 };
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 }
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
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
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);
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);
2586 return names;
2587 })();
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 = {};
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 }
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);
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);
2662 // Encoding Version: 1.3
2663 add("text/vnd.wap.connectivity-xml", 0x35);
2664 add("application/vnd.wap.connectivity-wbxml", 0x36);
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);
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);
2694 // Encoding Version: 1.6
2695 add("audio/*", 0x4F);
2696 add("video/*", 0x50);
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);
2709 return types;
2710 })();
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 = {};
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 }
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);
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
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);
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);
2765 return params;
2766 })();
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 = {};
2774 function add(name, number, converter) {
2775 let entry = {
2776 name: name,
2777 number: number,
2778 converter: converter,
2779 };
2781 charsets[name] = charsets[number] = entry;
2782 }
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");
2813 return charsets;
2814 })();
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 = {};
2821 function add(urn, number) {
2822 let entry = {
2823 urn: urn,
2824 number: number,
2825 };
2827 ids[urn] = ids[number] = entry;
2828 }
2830 add("x-wap-application:wml.ua", 0x02);
2831 add("x-wap-application:mms.ua", 0x04);
2833 return ids;
2834 })();
2836 let debug;
2837 if (DEBUG) {
2838 debug = function(s) {
2839 dump("-@- WspPduHelper: " + s + "\n");
2840 };
2841 } else {
2842 debug = function(s) {};
2843 }
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",
2853 // Error classes
2854 "CodeError",
2855 "FatalCodeError",
2856 "NotWellKnownEncodingError",
2858 // Utility functions
2859 "ensureHeader",
2860 "skipValue",
2861 "decodeAlternatives",
2862 "encodeAlternatives",
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",
2897 // Parser
2898 "PduHelper",
2899 ]);