michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/wap_consts.js", this); michael@0: michael@0: let DEBUG; // set to true to see debug messages michael@0: michael@0: // Special ASCII characters michael@0: const NUL = 0; michael@0: const CR = 13; michael@0: const LF = 10; michael@0: const SP = 32; michael@0: const HT = 9; michael@0: const DQUOTE = 34; michael@0: const DEL = 127; michael@0: michael@0: // Special ASCII character ranges michael@0: const CTLS = 32; michael@0: const ASCIIS = 128; michael@0: michael@0: /** michael@0: * Error class for generic encoding/decoding failures. michael@0: */ michael@0: this.CodeError = function CodeError(message) { michael@0: this.name = "CodeError"; michael@0: this.message = message || "Invalid format"; michael@0: } michael@0: CodeError.prototype = new Error(); michael@0: CodeError.prototype.constructor = CodeError; michael@0: michael@0: /** michael@0: * Error class for unexpected NUL char at decoding text elements. michael@0: * michael@0: * @param message [optional] michael@0: * A short description for the error. michael@0: */ michael@0: function NullCharError(message) { michael@0: this.name = "NullCharError"; michael@0: this.message = message || "Null character found"; michael@0: } michael@0: NullCharError.prototype = new CodeError(); michael@0: NullCharError.prototype.constructor = NullCharError; michael@0: michael@0: /** michael@0: * Error class for fatal encoding/decoding failures. michael@0: * michael@0: * This error is only raised when expected format isn't met and the parser michael@0: * context can't do anything more to either skip it or hand over to other michael@0: * alternative encoding/decoding steps. michael@0: * michael@0: * @param message [optional] michael@0: * A short description for the error. michael@0: */ michael@0: this.FatalCodeError = function FatalCodeError(message) { michael@0: this.name = "FatalCodeError"; michael@0: this.message = message || "Decoding fails"; michael@0: } michael@0: FatalCodeError.prototype = new Error(); michael@0: FatalCodeError.prototype.constructor = FatalCodeError; michael@0: michael@0: /** michael@0: * Error class for undefined well known encoding. michael@0: * michael@0: * When a encoded header field/parameter has unknown/unsupported value, we may michael@0: * never know how to decode the next value. For example, a parameter of michael@0: * undefined well known encoding may be followed by a Q-value, which is michael@0: * basically a uintvar. However, there is no way you can distiguish an Q-value michael@0: * 0.64, encoded as 0x41, from a string begins with 'A', which is also 0x41. michael@0: * The `skipValue` will try the latter one, which is not expected. michael@0: * michael@0: * @param message [optional] michael@0: * A short description for the error. michael@0: */ michael@0: this.NotWellKnownEncodingError = function NotWellKnownEncodingError(message) { michael@0: this.name = "NotWellKnownEncodingError"; michael@0: this.message = message || "Not well known encoding"; michael@0: } michael@0: NotWellKnownEncodingError.prototype = new FatalCodeError(); michael@0: NotWellKnownEncodingError.prototype.constructor = NotWellKnownEncodingError; michael@0: michael@0: /** michael@0: * Internal helper function to retrieve the value of a property with its name michael@0: * specified by `name` inside the object `headers`. michael@0: * michael@0: * @param headers michael@0: * An object that contains parsed header fields. michael@0: * @param name michael@0: * Header name string to be checked. michael@0: * michael@0: * @return Value of specified header field. michael@0: * michael@0: * @throws FatalCodeError if headers[name] is undefined. michael@0: */ michael@0: this.ensureHeader = function ensureHeader(headers, name) { michael@0: let value = headers[name]; michael@0: // Header field might have a null value as NoValue michael@0: if (value === undefined) { michael@0: throw new FatalCodeError("ensureHeader: header " + name + " not defined"); michael@0: } michael@0: return value; michael@0: } michael@0: michael@0: /** michael@0: * Skip field value. michael@0: * michael@0: * The WSP field values are encoded so that the length of the field value can michael@0: * always be determined, even if the detailed format of a specific field value michael@0: * is not known. This makes it possible to skip over individual header fields michael@0: * without interpreting their content. ... the first octet in all the field michael@0: * values can be interpreted as follows: michael@0: * michael@0: * 0 - 30 | This octet is followed by the indicated number (0 - 30) of data michael@0: * octets. michael@0: * 31 | This octet is followed by a unitvar, which indicates the number michael@0: * of data octets after it. michael@0: * 32 - 127 | The value is a text string, terminated by a zero octet (NUL michael@0: * character). michael@0: * 128 - 255 | It is an encoded 7-bit value; this header has no more data. michael@0: * michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Skipped value of several possible types like string, integer, or michael@0: * an array of octets. michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.1.2 michael@0: */ michael@0: this.skipValue = function skipValue(data) { michael@0: let begin = data.offset; michael@0: let value = Octet.decode(data); michael@0: if (value <= 31) { michael@0: if (value == 31) { michael@0: value = UintVar.decode(data); michael@0: } michael@0: michael@0: if (value) { michael@0: // `value` can be larger than 30, max length of a multi-octet integer michael@0: // here. So we must decode it as an array instead. michael@0: value = Octet.decodeMultiple(data, data.offset + value); michael@0: } else { michael@0: value = null; michael@0: } michael@0: } else if (value <= 127) { michael@0: data.offset = begin; michael@0: value = NullTerminatedTexts.decode(data); michael@0: } else { michael@0: value &= 0x7F; michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: /** michael@0: * Helper function for decoding multiple alternative forms. michael@0: * michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param options michael@0: * Extra context for decoding. michael@0: * michael@0: * @return Decoded value. michael@0: */ michael@0: this.decodeAlternatives = function decodeAlternatives(data, options) { michael@0: let begin = data.offset; michael@0: for (let i = 2; i < arguments.length; i++) { michael@0: try { michael@0: return arguments[i].decode(data, options); michael@0: } catch (e) { michael@0: // Throw the last exception we get michael@0: if (i == (arguments.length - 1)) { michael@0: throw e; michael@0: } michael@0: michael@0: data.offset = begin; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Helper function for encoding multiple alternative forms. michael@0: * michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * Object value of arbitrary type to be encoded. michael@0: * @param options michael@0: * Extra context for encoding. michael@0: */ michael@0: this.encodeAlternatives = function encodeAlternatives(data, value, options) { michael@0: let begin = data.offset; michael@0: for (let i = 3; i < arguments.length; i++) { michael@0: try { michael@0: arguments[i].encode(data, value, options); michael@0: return; michael@0: } catch (e) { michael@0: // Throw the last exception we get michael@0: if (i == (arguments.length - 1)) { michael@0: throw e; michael@0: } michael@0: michael@0: data.offset = begin; michael@0: } michael@0: } michael@0: } michael@0: michael@0: this.Octet = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @throws RangeError if no more data is available. michael@0: */ michael@0: decode: function(data) { michael@0: if (data.offset >= data.array.length) { michael@0: throw new RangeError(); michael@0: } michael@0: michael@0: return data.array[data.offset++]; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param end michael@0: * An ending offset indicating the end of octet array to read. michael@0: * michael@0: * @return A decoded array object. michael@0: * michael@0: * @throws RangeError if no enough data to read. michael@0: * @throws TypeError if `data` has neither subarray() nor slice() method. michael@0: */ michael@0: decodeMultiple: function(data, end) { michael@0: if ((end < data.offset) || (end > data.array.length)) { michael@0: throw new RangeError(); michael@0: } michael@0: if (end == data.offset) { michael@0: return []; michael@0: } michael@0: michael@0: let result; michael@0: if (data.array.subarray) { michael@0: result = data.array.subarray(data.offset, end); michael@0: } else if (data.array.slice) { michael@0: result = data.array.slice(data.offset, end); michael@0: } else { michael@0: throw new TypeError(); michael@0: } michael@0: michael@0: data.offset = end; michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Internal octet decoding for specific value. michael@0: * michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param expected michael@0: * Expected octet value. michael@0: * michael@0: * @return Expected octet value. michael@0: * michael@0: * @throws CodeError if read octet is not equal to expected one. michael@0: */ michael@0: decodeEqualTo: function(data, expected) { michael@0: if (this.decode(data) != expected) { michael@0: throw new CodeError("Octet - decodeEqualTo: doesn't match " + expected); michael@0: } michael@0: michael@0: return expected; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param octet michael@0: * Octet value to be encoded. michael@0: */ michael@0: encode: function(data, octet) { michael@0: if (data.offset >= data.array.length) { michael@0: data.array.push(octet); michael@0: data.offset++; michael@0: } else { michael@0: data.array[data.offset++] = octet; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param octet michael@0: * An octet array object. michael@0: */ michael@0: encodeMultiple: function(data, array) { michael@0: for (let i = 0; i < array.length; i++) { michael@0: this.encode(data, array[i]); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * TEXT = michael@0: * CTL = michael@0: * LWS = [CRLF] 1*(SP|HT) michael@0: * CRLF = CR LF michael@0: * CR = michael@0: * LF = michael@0: * SP = michael@0: * HT = michael@0: * michael@0: * @see RFC 2616 clause 2.2 Basic Rules michael@0: */ michael@0: this.Text = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded character. michael@0: * michael@0: * @throws NullCharError if a NUL character read. michael@0: * @throws CodeError if a control character read. michael@0: */ michael@0: decode: function(data) { michael@0: let code = Octet.decode(data); michael@0: if ((code >= CTLS) && (code != DEL)) { michael@0: return String.fromCharCode(code); michael@0: } michael@0: michael@0: if (code == NUL) { michael@0: throw new NullCharError(); michael@0: } michael@0: michael@0: if (code != CR) { michael@0: throw new CodeError("Text: invalid char code " + code); michael@0: } michael@0: michael@0: // "A CRLF is allowed in the definition of TEXT only as part of a header michael@0: // field continuation. It is expected that the folding LWS will be michael@0: // replaced with a single SP before interpretation of the TEXT value." michael@0: // ~ RFC 2616 clause 2.2 michael@0: michael@0: let extra; michael@0: michael@0: // Rethrow everything as CodeError. We had already a successful read above. michael@0: try { michael@0: extra = Octet.decode(data); michael@0: if (extra != LF) { michael@0: throw new CodeError("Text: doesn't match LWS sequence"); michael@0: } michael@0: michael@0: extra = Octet.decode(data); michael@0: if ((extra != SP) && (extra != HT)) { michael@0: throw new CodeError("Text: doesn't match LWS sequence"); michael@0: } michael@0: } catch (e if e instanceof CodeError) { michael@0: throw e; michael@0: } catch (e) { michael@0: throw new CodeError("Text: doesn't match LWS sequence"); michael@0: } michael@0: michael@0: // Let's eat as many SP|HT as possible. michael@0: let begin; michael@0: michael@0: // Do not throw anything here. We had already matched (SP | HT). michael@0: try { michael@0: do { michael@0: begin = data.offset; michael@0: extra = Octet.decode(data); michael@0: } while ((extra == SP) || (extra == HT)); michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: return " "; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param text michael@0: * String text of one character to be encoded. michael@0: * @param asciiOnly michael@0: * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). michael@0: * michael@0: * @throws CodeError if a control character got. michael@0: */ michael@0: encode: function(data, text, asciiOnly) { michael@0: if (!text) { michael@0: throw new CodeError("Text: empty string"); michael@0: } michael@0: michael@0: let code = text.charCodeAt(0); michael@0: if ((code < CTLS) || (code == DEL) || (code > 255) || michael@0: (code >= 128 && asciiOnly)) { michael@0: throw new CodeError("Text: invalid char code " + code); michael@0: } michael@0: Octet.encode(data, code); michael@0: }, michael@0: }; michael@0: michael@0: this.NullTerminatedTexts = { michael@0: /** michael@0: * Decode internal referenced null terminated text string. michael@0: * michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded string. michael@0: */ michael@0: decode: function(data) { michael@0: let str = ""; michael@0: try { michael@0: // A End-of-string is also a CTL, which should cause a error. michael@0: while (true) { michael@0: str += Text.decode(data); michael@0: } michael@0: } catch (e if e instanceof NullCharError) { michael@0: return str; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param str michael@0: * A String to be encoded. michael@0: * @param asciiOnly michael@0: * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). michael@0: */ michael@0: encode: function(data, str, asciiOnly) { michael@0: if (str) { michael@0: for (let i = 0; i < str.length; i++) { michael@0: Text.encode(data, str.charAt(i), asciiOnly); michael@0: } michael@0: } michael@0: Octet.encode(data, 0); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * TOKEN = 1* michael@0: * CHAR = michael@0: * SEPARATORS = ()<>@,;:\"/[]?={} SP HT michael@0: * michael@0: * @see RFC 2616 clause 2.2 Basic Rules michael@0: */ michael@0: this.Token = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded character. michael@0: * michael@0: * @throws NullCharError if a NUL character read. michael@0: * @throws CodeError if an invalid character read. michael@0: */ michael@0: decode: function(data) { michael@0: let code = Octet.decode(data); michael@0: if ((code < ASCIIS) && (code >= CTLS)) { michael@0: if ((code == HT) || (code == SP) michael@0: || (code == 34) || (code == 40) || (code == 41) // ASCII "() michael@0: || (code == 44) || (code == 47) // ASCII ,/ michael@0: || ((code >= 58) && (code <= 64)) // ASCII :;<=>?@ michael@0: || ((code >= 91) && (code <= 93)) // ASCII [\] michael@0: || (code == 123) || (code == 125)) { // ASCII {} michael@0: throw new CodeError("Token: invalid char code " + code); michael@0: } michael@0: michael@0: return String.fromCharCode(code); michael@0: } michael@0: michael@0: if (code == NUL) { michael@0: throw new NullCharError(); michael@0: } michael@0: michael@0: throw new CodeError("Token: invalid char code " + code); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param token michael@0: * String text of one character to be encoded. michael@0: * michael@0: * @throws CodeError if an invalid character got. michael@0: */ michael@0: encode: function(data, token) { michael@0: if (!token) { michael@0: throw new CodeError("Token: empty string"); michael@0: } michael@0: michael@0: let code = token.charCodeAt(0); michael@0: if ((code < ASCIIS) && (code >= CTLS)) { michael@0: if ((code == HT) || (code == SP) michael@0: || (code == 34) || (code == 40) || (code == 41) // ASCII "() michael@0: || (code == 44) || (code == 47) // ASCII ,/ michael@0: || ((code >= 58) && (code <= 64)) // ASCII :;<=>?@ michael@0: || ((code >= 91) && (code <= 93)) // ASCII [\] michael@0: || (code == 123) || (code == 125)) { // ASCII {} michael@0: // Fallback to throw CodeError michael@0: } else { michael@0: Octet.encode(data, token.charCodeAt(0)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: throw new CodeError("Token: invalid char code " + code); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * uric = reserved | unreserved | escaped michael@0: * reserved = ;/?:@&=+$, michael@0: * unreserved = alphanum | mark michael@0: * mark = -_.!~*'() michael@0: * escaped = % hex hex michael@0: * excluded but used = #% michael@0: * michael@0: * Or, in decimal, they are: 33,35-59,61,63-90,95,97-122,126 michael@0: * michael@0: * @see RFC 2396 Uniform Resource Indentifiers (URI) michael@0: */ michael@0: this.URIC = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded character. michael@0: * michael@0: * @throws NullCharError if a NUL character read. michael@0: * @throws CodeError if an invalid character read. michael@0: */ michael@0: decode: function(data) { michael@0: let code = Octet.decode(data); michael@0: if (code == NUL) { michael@0: throw new NullCharError(); michael@0: } michael@0: michael@0: if ((code <= CTLS) || (code >= ASCIIS) || (code == 34) || (code == 60) michael@0: || (code == 62) || ((code >= 91) && (code <= 94)) || (code == 96) michael@0: || ((code >= 123) && (code <= 125)) || (code == 127)) { michael@0: throw new CodeError("URIC: invalid char code " + code); michael@0: } michael@0: michael@0: return String.fromCharCode(code); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * If the first character in the TEXT is in the range of 128-255, a Quote michael@0: * character must precede it. Otherwise the Quote character must be omitted. michael@0: * The Quote is not part of the contents. michael@0: * michael@0: * Text-string = [Quote] *TEXT End-of-string michael@0: * Quote = michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: */ michael@0: this.TextString = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded string. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: let firstCode = Octet.decode(data); michael@0: if (firstCode == 127) { michael@0: // Quote found, check if first char code is larger-equal than 128. michael@0: begin = data.offset; michael@0: try { michael@0: if (Octet.decode(data) < 128) { michael@0: throw new CodeError("Text-string: illegal quote found."); michael@0: } michael@0: } catch (e if e instanceof CodeError) { michael@0: throw e; michael@0: } catch (e) { michael@0: throw new CodeError("Text-string: unexpected error."); michael@0: } michael@0: } else if (firstCode >= 128) { michael@0: throw new CodeError("Text-string: invalid char code " + firstCode); michael@0: } michael@0: michael@0: data.offset = begin; michael@0: return NullTerminatedTexts.decode(data); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param str michael@0: * A String to be encoded. michael@0: * @param asciiOnly michael@0: * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). michael@0: */ michael@0: encode: function(data, str, asciiOnly) { michael@0: if (!str) { michael@0: Octet.encode(data, 0); michael@0: return; michael@0: } michael@0: michael@0: let firstCharCode = str.charCodeAt(0); michael@0: if (firstCharCode >= 128) { michael@0: if (asciiOnly) { michael@0: throw new CodeError("Text: invalid char code " + code); michael@0: } michael@0: michael@0: Octet.encode(data, 127); michael@0: } michael@0: michael@0: NullTerminatedTexts.encode(data, str, asciiOnly); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Token-text = Token End-of-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: */ michael@0: this.TokenText = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded string. michael@0: */ michael@0: decode: function(data) { michael@0: let str = ""; michael@0: try { michael@0: // A End-of-string is also a CTL, which should cause a error. michael@0: while (true) { michael@0: str += Token.decode(data); michael@0: } michael@0: } catch (e if e instanceof NullCharError) { michael@0: return str; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param str michael@0: * A String to be encoded. michael@0: */ michael@0: encode: function(data, str) { michael@0: if (str) { michael@0: for (let i = 0; i < str.length; i++) { michael@0: Token.encode(data, str.charAt(i)); michael@0: } michael@0: } michael@0: Octet.encode(data, 0); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * The TEXT encodes an RFC2616 Quoted-string with the enclosing michael@0: * quotation-marks <"> removed. michael@0: * michael@0: * Quoted-string = *TEXT End-of-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: */ michael@0: this.QuotedString = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded string. michael@0: * michael@0: * @throws CodeError if first octet read is not 0x34. michael@0: */ michael@0: decode: function(data) { michael@0: let value = Octet.decode(data); michael@0: if (value != 34) { michael@0: throw new CodeError("Quoted-string: not quote " + value); michael@0: } michael@0: michael@0: return NullTerminatedTexts.decode(data); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param str michael@0: * A String to be encoded. michael@0: */ michael@0: encode: function(data, str) { michael@0: Octet.encode(data, 34); michael@0: NullTerminatedTexts.encode(data, str); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Integers in range 0-127 shall be encoded as a one octet value with the michael@0: * most significant bit set to one (1xxx xxxx) and with the value in the michael@0: * remaining least significant bits. michael@0: * michael@0: * Short-integer = OCTET michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: */ michael@0: this.ShortInteger = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded integer value. michael@0: * michael@0: * @throws CodeError if the octet read is less than 0x80. michael@0: */ michael@0: decode: function(data) { michael@0: let value = Octet.decode(data); michael@0: if (!(value & 0x80)) { michael@0: throw new CodeError("Short-integer: invalid value " + value); michael@0: } michael@0: michael@0: return (value & 0x7F); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * A numeric value to be encoded. michael@0: * michael@0: * @throws CodeError if the octet read is larger-equal than 0x80. michael@0: */ michael@0: encode: function(data, value) { michael@0: if (value >= 0x80) { michael@0: throw new CodeError("Short-integer: invalid value " + value); michael@0: } michael@0: michael@0: Octet.encode(data, value | 0x80); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * The content octets shall be an unsigned integer value with the most michael@0: * significant octet encoded first (big-endian representation). The minimum michael@0: * number of octets must be used to encode the value. michael@0: * michael@0: * Long-integer = Short-length Multi-octet-integer michael@0: * Short-length = michael@0: * Multi-octet-integer = 1*30 OCTET michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: */ michael@0: this.LongInteger = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param length michael@0: * Number of octets to read. michael@0: * michael@0: * @return A decoded integer value or an octets array of max 30 elements. michael@0: */ michael@0: decodeMultiOctetInteger: function(data, length) { michael@0: if (length < 7) { michael@0: // Return a integer instead of an array as possible. For a multi-octet michael@0: // integer, there are only maximum 53 bits for integer in javascript. We michael@0: // will get an inaccurate one beyond that. We can't neither use bitwise michael@0: // operation here, for it will be limited in 32 bits. michael@0: let value = 0; michael@0: while (length--) { michael@0: value = value * 256 + Octet.decode(data); michael@0: } michael@0: return value; michael@0: } michael@0: michael@0: return Octet.decodeMultiple(data, data.offset + length); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded integer value or an octets array of max 30 elements. michael@0: * michael@0: * @throws CodeError if the length read is not in 1..30. michael@0: */ michael@0: decode: function(data) { michael@0: let length = Octet.decode(data); michael@0: if ((length < 1) || (length > 30)) { michael@0: throw new CodeError("Long-integer: invalid length " + length); michael@0: } michael@0: michael@0: return this.decodeMultiOctetInteger(data, length); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param numOrArray michael@0: * An octet array of less-equal than 30 elements or an integer michael@0: * greater-equal than 128. michael@0: */ michael@0: encode: function(data, numOrArray) { michael@0: if (typeof numOrArray === "number") { michael@0: let num = numOrArray; michael@0: if (num >= 0x1000000000000) { michael@0: throw new CodeError("Long-integer: number too large " + num); michael@0: } michael@0: michael@0: let stack = []; michael@0: do { michael@0: stack.push(Math.floor(num % 256)); michael@0: num = Math.floor(num / 256); michael@0: } while (num); michael@0: michael@0: Octet.encode(data, stack.length); michael@0: while (stack.length) { michael@0: Octet.encode(data, stack.pop()); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let array = numOrArray; michael@0: if ((array.length < 1) || (array.length > 30)) { michael@0: throw new CodeError("Long-integer: invalid length " + array.length); michael@0: } michael@0: michael@0: Octet.encode(data, array.length); michael@0: Octet.encodeMultiple(data, array); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: */ michael@0: this.UintVar = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded integer value. michael@0: */ michael@0: decode: function(data) { michael@0: let value = Octet.decode(data); michael@0: let result = value & 0x7F; michael@0: while (value & 0x80) { michael@0: value = Octet.decode(data); michael@0: result = result * 128 + (value & 0x7F); michael@0: } michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An integer value. michael@0: */ michael@0: encode: function(data, value) { michael@0: if (value < 0) { michael@0: throw new CodeError("UintVar: invalid value " + value); michael@0: } michael@0: michael@0: let stack = []; michael@0: while (value >= 128) { michael@0: stack.push(Math.floor(value % 128)); michael@0: value = Math.floor(value / 128); michael@0: } michael@0: michael@0: while (stack.length) { michael@0: Octet.encode(data, value | 0x80); michael@0: value = stack.pop(); michael@0: } michael@0: Octet.encode(data, value); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * This encoding is used for token values, which have no well-known binary michael@0: * encoding, or when the assigned number of the well-known encoding is small michael@0: * enough to fit into Short-Integer. We change Extension-Media from michael@0: * NullTerminatedTexts to TextString because of Bug 823816. michael@0: * michael@0: * Constrained-encoding = Extension-Media | Short-integer michael@0: * Extension-Media = TextString michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.1 michael@0: * @see https://bugzilla.mozilla.org/show_bug.cgi?id=823816 michael@0: */ michael@0: this.ConstrainedEncoding = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decode integer value or string. michael@0: */ michael@0: decode: function(data) { michael@0: return decodeAlternatives(data, null, TextString, ShortInteger); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An integer or a string value. michael@0: */ michael@0: encode: function(data, value) { michael@0: if (typeof value == "number") { michael@0: ShortInteger.encode(data, value); michael@0: } else { michael@0: TextString.encode(data, value); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Value-length = Short-length | (Length-quote Length) michael@0: * Short-length = michael@0: * Length-quote = michael@0: * Length = Uintvar-integer michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.2 michael@0: */ michael@0: this.ValueLength = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded integer value. michael@0: * michael@0: * @throws CodeError if the first octet read is larger than 31. michael@0: */ michael@0: decode: function(data) { michael@0: let value = Octet.decode(data); michael@0: if (value <= 30) { michael@0: return value; michael@0: } michael@0: michael@0: if (value == 31) { michael@0: return UintVar.decode(data); michael@0: } michael@0: michael@0: throw new CodeError("Value-length: invalid value " + value); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: */ michael@0: encode: function(data, value) { michael@0: if (value <= 30) { michael@0: Octet.encode(data, value); michael@0: } else { michael@0: Octet.encode(data, 31); michael@0: UintVar.encode(data, value); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * No-value = michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.NoValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Always returns null. michael@0: */ michael@0: decode: function(data) { michael@0: Octet.decodeEqualTo(data, 0); michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * A null or undefined value. michael@0: */ michael@0: encode: function(data, value) { michael@0: if (value != null) { michael@0: throw new CodeError("No-value: invalid value " + value); michael@0: } michael@0: Octet.encode(data, 0); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Text-value = No-value | Token-text | Quoted-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.TextValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded string or null for No-value. michael@0: */ michael@0: decode: function(data) { michael@0: return decodeAlternatives(data, null, NoValue, TokenText, QuotedString); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param text michael@0: * A null or undefined or text string. michael@0: */ michael@0: encode: function(data, text) { michael@0: encodeAlternatives(data, text, null, NoValue, TokenText, QuotedString); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Integer-Value = Short-integer | Long-integer michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.IntegerValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded integer value or array of octets. michael@0: */ michael@0: decode: function(data) { michael@0: return decodeAlternatives(data, null, ShortInteger, LongInteger); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An integer value or an octet array of less-equal than 31 elements. michael@0: */ michael@0: encode: function(data, value) { michael@0: if (typeof value === "number") { michael@0: encodeAlternatives(data, value, null, ShortInteger, LongInteger); michael@0: } else if (Array.isArray(value) || (value instanceof Uint8Array)) { michael@0: LongInteger.encode(data, value); michael@0: } else { michael@0: throw new CodeError("Integer-Value: invalid value type"); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * The encoding of dates shall be done in number of seconds from michael@0: * 1970-01-01, 00:00:00 GMT. michael@0: * michael@0: * Date-value = Long-integer michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.DateValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A Date object. michael@0: */ michael@0: decode: function(data) { michael@0: let numOrArray = LongInteger.decode(data); michael@0: let seconds; michael@0: if (typeof numOrArray == "number") { michael@0: seconds = numOrArray; michael@0: } else { michael@0: seconds = 0; michael@0: for (let i = 0; i < numOrArray.length; i++) { michael@0: seconds = seconds * 256 + numOrArray[i]; michael@0: } michael@0: } michael@0: michael@0: return new Date(seconds * 1000); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param date michael@0: * A Date object. michael@0: */ michael@0: encode: function(data, date) { michael@0: let seconds = date.getTime() / 1000; michael@0: if (seconds < 0) { michael@0: throw new CodeError("Date-value: negative seconds " + seconds); michael@0: } michael@0: michael@0: LongInteger.encode(data, seconds); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Delta-seconds-value = Integer-value michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.DeltaSecondsValue = IntegerValue; michael@0: michael@0: /** michael@0: * Quality factor 0 and quality factors with one or two decimal digits are michael@0: * encoded into 1-100; three digits ones into 101-1099. michael@0: * michael@0: * Q-value = 1*2 OCTET michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.QValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded integer value of 1..1099. michael@0: * michael@0: * @throws CodeError if decoded UintVar is not in range 1..1099. michael@0: */ michael@0: decode: function(data) { michael@0: let value = UintVar.decode(data); michael@0: if (value > 0) { michael@0: if (value <= 100) { michael@0: return (value - 1) / 100.0; michael@0: } michael@0: if (value <= 1099) { michael@0: return (value - 100) / 1000.0; michael@0: } michael@0: } michael@0: michael@0: throw new CodeError("Q-value: invalid value " + value); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An integer within the range 1..1099. michael@0: */ michael@0: encode: function(data, value) { michael@0: if ((value < 0) || (value >= 1)) { michael@0: throw new CodeError("Q-value: invalid value " + value); michael@0: } michael@0: michael@0: value *= 1000; michael@0: if ((value % 10) == 0) { michael@0: // Two digits only. michael@0: UintVar.encode(data, Math.floor(value / 10 + 1)); michael@0: } else { michael@0: // Three digits. michael@0: UintVar.encode(data, Math.floor(value + 100)); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * The three most significant bits of the Short-integer value are interpreted michael@0: * to encode a major version number in the range 1-7, and the four least michael@0: * significant bits contain a minor version number in the range 0-14. If michael@0: * there is only a major version number, this is encoded by placing the value michael@0: * 15 in the four least significant bits. michael@0: * michael@0: * Version-value = Short-integer | Text-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: */ michael@0: this.VersionValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Binary encoded version number. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: let value; michael@0: try { michael@0: value = ShortInteger.decode(data); michael@0: if ((value >= 0x10) && (value < 0x80)) { michael@0: return value; michael@0: } michael@0: michael@0: throw new CodeError("Version-value: invalid value " + value); michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: michael@0: let str = TextString.decode(data); michael@0: if (!str.match(/^[1-7](\.1?\d)?$/)) { michael@0: throw new CodeError("Version-value: invalid value " + str); michael@0: } michael@0: michael@0: let major = str.charCodeAt(0) - 0x30; michael@0: let minor = 0x0F; michael@0: if (str.length > 1) { michael@0: minor = str.charCodeAt(2) - 0x30; michael@0: if (str.length > 3) { michael@0: minor = 10 + (str.charCodeAt(3) - 0x30); michael@0: if (minor > 14) { michael@0: throw new CodeError("Version-value: invalid minor " + minor); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return major << 4 | minor; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param version michael@0: * A binary encoded version number. michael@0: */ michael@0: encode: function(data, version) { michael@0: if ((version < 0x10) || (version >= 0x80)) { michael@0: throw new CodeError("Version-value: invalid version " + version); michael@0: } michael@0: michael@0: ShortInteger.encode(data, version); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * URI value should be encoded per [RFC2616], but service user may use a michael@0: * different format. michael@0: * michael@0: * Uri-value = Text-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.3 michael@0: * @see RFC 2616 clause 2.2 Basic Rules michael@0: */ michael@0: this.UriValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded uri string. michael@0: */ michael@0: decode: function(data) { michael@0: let str = ""; michael@0: try { michael@0: // A End-of-string is also a CTL, which should cause a error. michael@0: while (true) { michael@0: str += URIC.decode(data); michael@0: } michael@0: } catch (e if e instanceof NullCharError) { michael@0: return str; michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Internal coder for "type" parameter. michael@0: * michael@0: * Type-value = Constrained-encoding michael@0: * michael@0: * @see WAP-230-WSP-20010705-a table 38 michael@0: */ michael@0: this.TypeValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded content type string. michael@0: */ michael@0: decode: function(data) { michael@0: let numOrStr = ConstrainedEncoding.decode(data); michael@0: if (typeof numOrStr == "string") { michael@0: return numOrStr.toLowerCase(); michael@0: } michael@0: michael@0: let number = numOrStr; michael@0: let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Constrained-media: not well known media " + number); michael@0: } michael@0: michael@0: return entry.type; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param type michael@0: * A content type string. michael@0: */ michael@0: encode: function(data, type) { michael@0: let entry = WSP_WELL_KNOWN_CONTENT_TYPES[type.toLowerCase()]; michael@0: if (entry) { michael@0: ConstrainedEncoding.encode(data, entry.number); michael@0: } else { michael@0: ConstrainedEncoding.encode(data, type); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Parameter = Typed-parameter | Untyped-parameter michael@0: * michael@0: * For Typed-parameters, the actual expected type of the value is implied by michael@0: * the well-known parameter. In addition to the expected type, there may be no michael@0: * value. If the value cannot be encoded using expected type, it shall be michael@0: * encoded as text. michael@0: * michael@0: * Typed-parameter = Well-known-parameter-token Typed-value michael@0: * Well-known-parameter-token = Integer-value michael@0: * Typed-value = Compact-value | Text-value michael@0: * Compact-value = Integer-value | Date-value | Delta-seconds-value | Q-value michael@0: * | Version-value | Uri-value michael@0: * michael@0: * For Untyped-parameters, the type of the value is unknown, but is shall be michael@0: * encoded as an integer, if that is possible. michael@0: * michael@0: * Untyped-parameter = Token-text Untyped-value michael@0: * Untyped-value = Integer-value | Text-value michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.4 michael@0: */ michael@0: this.Parameter = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * if something wrong. The `name` property must be a string, but the michael@0: * `value` property can be many different types depending on `name`. michael@0: * michael@0: * @throws CodeError if decoded IntegerValue is an array. michael@0: * @throws NotWellKnownEncodingError if decoded well-known parameter number michael@0: * is not registered or supported. michael@0: */ michael@0: decodeTypedParameter: function(data) { michael@0: let numOrArray = IntegerValue.decode(data); michael@0: // `decodeIntegerValue` can return a array, which doesn't apply here. michael@0: if (typeof numOrArray != "number") { michael@0: throw new CodeError("Typed-parameter: invalid integer type"); michael@0: } michael@0: michael@0: let number = numOrArray; michael@0: let param = WSP_WELL_KNOWN_PARAMS[number]; michael@0: if (!param) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Typed-parameter: not well known parameter " + number); michael@0: } michael@0: michael@0: let begin = data.offset, value; michael@0: try { michael@0: // Althought Text-string is not included in BNF of Compact-value, but michael@0: // some service provider might still pass a less-strict text form and michael@0: // cause a unexpected CodeError raised. For example, the `start` michael@0: // parameter expects its value of Text-value, but service provider might michael@0: // gives "", which contains illegal characters "<" and ">". michael@0: value = decodeAlternatives(data, null, michael@0: param.coder, TextValue, TextString); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: michael@0: // Skip current parameter. michael@0: value = skipValue(data); michael@0: debug("Skip malformed typed parameter: " michael@0: + JSON.stringify({name: param.name, value: value})); michael@0: michael@0: return null; michael@0: } michael@0: michael@0: return { michael@0: name: param.name, michael@0: value: value, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * if something wrong. The `name` property must be a string, but the michael@0: * `value` property can be many different types depending on `name`. michael@0: */ michael@0: decodeUntypedParameter: function(data) { michael@0: let name = TokenText.decode(data); michael@0: michael@0: let begin = data.offset, value; michael@0: try { michael@0: value = decodeAlternatives(data, null, IntegerValue, TextValue); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: michael@0: // Skip current parameter. michael@0: value = skipValue(data); michael@0: debug("Skip malformed untyped parameter: " michael@0: + JSON.stringify({name: name, value: value})); michael@0: michael@0: return null; michael@0: } michael@0: michael@0: return { michael@0: name: name.toLowerCase(), michael@0: value: value, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * if something wrong. The `name` property must be a string, but the michael@0: * `value` property can be many different types depending on `name`. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: try { michael@0: return this.decodeTypedParameter(data); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: return this.decodeUntypedParameter(data); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param end michael@0: * Ending offset of following parameters. michael@0: * michael@0: * @return An array of decoded objects. michael@0: */ michael@0: decodeMultiple: function(data, end) { michael@0: let params = null, param; michael@0: michael@0: while (data.offset < end) { michael@0: try { michael@0: param = this.decode(data); michael@0: } catch (e) { michael@0: break; michael@0: } michael@0: if (param) { michael@0: if (!params) { michael@0: params = {}; michael@0: } michael@0: params[param.name] = param.value; michael@0: } michael@0: } michael@0: michael@0: return params; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param param michael@0: * An object containing `name` and `value` properties. michael@0: */ michael@0: encodeTypedParameter: function(data, param) { michael@0: let entry = WSP_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Typed-parameter: not well known parameter " + param.name); michael@0: } michael@0: michael@0: IntegerValue.encode(data, entry.number); michael@0: encodeAlternatives(data, param.value, null, michael@0: entry.coder, TextValue, TextString); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param param michael@0: * An object containing `name` and `value` properties. michael@0: */ michael@0: encodeUntypedParameter: function(data, param) { michael@0: TokenText.encode(data, param.name); michael@0: encodeAlternatives(data, param.value, null, IntegerValue, TextValue); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param param michael@0: * An array of parameter objects. michael@0: */ michael@0: encodeMultiple: function(data, params) { michael@0: for (let name in params) { michael@0: this.encode(data, {name: name, value: params[name]}); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param param michael@0: * An object containing `name` and `value` properties. michael@0: */ michael@0: encode: function(data, param) { michael@0: let begin = data.offset; michael@0: try { michael@0: this.encodeTypedParameter(data, param); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: this.encodeUntypedParameter(data, param); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Header = Message-header | Shift-sequence michael@0: * Message-header = Well-known-header | Application-header michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.6 michael@0: */ michael@0: this.Header = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * in case of a failed parsing. The `name` property must be a string, michael@0: * but the `value` property can be many different types depending on michael@0: * `name`. michael@0: */ michael@0: decodeMessageHeader: function(data) { michael@0: return decodeAlternatives(data, null, WellKnownHeader, ApplicationHeader); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * in case of a failed parsing. The `name` property must be a string, michael@0: * but the `value` property can be many different types depending on michael@0: * `name`. michael@0: */ michael@0: decode: function(data) { michael@0: // TODO: support header code page shift-sequence michael@0: return this.decodeMessageHeader(data); michael@0: }, michael@0: michael@0: encodeMessageHeader: function(data, header) { michael@0: encodeAlternatives(data, header, null, WellKnownHeader, ApplicationHeader); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param header michael@0: * An object containing two attributes: a string-typed `name` and a michael@0: * `value` of arbitrary type. michael@0: */ michael@0: encode: function(data, header) { michael@0: // TODO: support header code page shift-sequence michael@0: this.encodeMessageHeader(data, header); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Well-known-header = Well-known-field-name Wap-value michael@0: * Well-known-field-name = Short-integer michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.6 michael@0: */ michael@0: this.WellKnownHeader = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * in case of a failed parsing. The `name` property must be a string, michael@0: * but the `value` property can be many different types depending on michael@0: * `name`. michael@0: * michael@0: * @throws NotWellKnownEncodingError if decoded well-known header field michael@0: * number is not registered or supported. michael@0: */ michael@0: decode: function(data) { michael@0: let index = ShortInteger.decode(data); michael@0: michael@0: let entry = WSP_HEADER_FIELDS[index]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Well-known-header: not well known header " + index); michael@0: } michael@0: michael@0: let begin = data.offset, value; michael@0: try { michael@0: value = decodeAlternatives(data, null, entry.coder, TextValue); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: michael@0: value = skipValue(data); michael@0: debug("Skip malformed well known header(" + index + "): " michael@0: + JSON.stringify({name: entry.name, value: value})); michael@0: michael@0: return null; michael@0: } michael@0: michael@0: return { michael@0: name: entry.name, michael@0: value: value, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param header michael@0: * An object containing two attributes: a string-typed `name` and a michael@0: * `value` of arbitrary type. michael@0: */ michael@0: encode: function(data, header) { michael@0: let entry = WSP_HEADER_FIELDS[header.name.toLowerCase()]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Well-known-header: not well known header " + header.name); michael@0: } michael@0: michael@0: ShortInteger.encode(data, entry.number); michael@0: encodeAlternatives(data, header.value, null, entry.coder, TextValue); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Application-header = Token-text Application-specific-value michael@0: * Application-specific-value = Text-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.6 michael@0: */ michael@0: this.ApplicationHeader = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `name` and `value` properties or null michael@0: * in case of a failed parsing. The `name` property must be a string, michael@0: * but the `value` property can be many different types depending on michael@0: * `name`. michael@0: */ michael@0: decode: function(data) { michael@0: let name = TokenText.decode(data); michael@0: michael@0: let begin = data.offset, value; michael@0: try { michael@0: value = TextString.decode(data); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: michael@0: value = skipValue(data); michael@0: debug("Skip malformed application header: " michael@0: + JSON.stringify({name: name, value: value})); michael@0: michael@0: return null; michael@0: } michael@0: michael@0: return { michael@0: name: name.toLowerCase(), michael@0: value: value, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param header michael@0: * An object containing two attributes: a string-typed `name` and a michael@0: * `value` of arbitrary type. michael@0: * michael@0: * @throws CodeError if got an empty header name. michael@0: */ michael@0: encode: function(data, header) { michael@0: if (!header.name) { michael@0: throw new CodeError("Application-header: empty header name"); michael@0: } michael@0: michael@0: TokenText.encode(data, header.name); michael@0: TextString.encode(data, header.value); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Field-name = Token-text | Well-known-field-name michael@0: * Well-known-field-name = Short-integer michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.6 michael@0: */ michael@0: this.FieldName = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A field name string. michael@0: * michael@0: * @throws NotWellKnownEncodingError if decoded well-known header field michael@0: * number is not registered or supported. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: try { michael@0: return TokenText.decode(data).toLowerCase(); michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: michael@0: let number = ShortInteger.decode(data); michael@0: let entry = WSP_HEADER_FIELDS[number]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Field-name: not well known encoding " + number); michael@0: } michael@0: michael@0: return entry.name; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param name michael@0: * A field name string. michael@0: */ michael@0: encode: function(data, name) { michael@0: let entry = WSP_HEADER_FIELDS[name.toLowerCase()]; michael@0: if (entry) { michael@0: ShortInteger.encode(data, entry.number); michael@0: } else { michael@0: TokenText.encode(data, name); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Accept-charset-value = Constrained-charset | Accept-charset-general-form michael@0: * Constrained-charset = Any-charset | Constrained-encoding michael@0: * Any-charset = michael@0: * Accept-charset-general-form = Value-length (Well-known-charset | Token-text) [Q-value] michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.8 michael@0: */ michael@0: this.AcceptCharsetValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A object with a property `charset` of string "*". michael@0: */ michael@0: decodeAnyCharset: function(data) { michael@0: Octet.decodeEqualTo(data, 128); michael@0: return {charset: "*"}; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A object with a string property `charset` and a optional integer michael@0: * property `q`. michael@0: * michael@0: * @throws NotWellKnownEncodingError if decoded well-known charset number is michael@0: * not registered or supported. michael@0: */ michael@0: decodeConstrainedCharset: function(data) { michael@0: let begin = data.offset; michael@0: try { michael@0: return this.decodeAnyCharset(data); michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: michael@0: let numOrStr = ConstrainedEncoding.decode(data); michael@0: if (typeof numOrStr == "string") { michael@0: return {charset: numOrStr}; michael@0: } michael@0: michael@0: let charset = numOrStr; michael@0: let entry = WSP_WELL_KNOWN_CHARSETS[charset]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Constrained-charset: not well known charset: " + charset); michael@0: } michael@0: michael@0: return {charset: entry.name}; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A object with a string property `charset` and a optional integer michael@0: * property `q`. michael@0: */ michael@0: decodeAcceptCharsetGeneralForm: function(data) { michael@0: let length = ValueLength.decode(data); michael@0: michael@0: let begin = data.offset; michael@0: let end = begin + length; michael@0: michael@0: let result; michael@0: try { michael@0: result = WellKnownCharset.decode(data); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: michael@0: result = {charset: TokenText.decode(data)}; michael@0: if (data.offset < end) { michael@0: result.q = QValue.decode(data); michael@0: } michael@0: } michael@0: michael@0: if (data.offset != end) { michael@0: data.offset = end; michael@0: } michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A object with a string property `charset` and a optional integer michael@0: * property `q`. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: try { michael@0: return this.decodeConstrainedCharset(data); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: return this.decodeAcceptCharsetGeneralForm(data); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An object with a string property `charset`. michael@0: */ michael@0: encodeAnyCharset: function(data, value) { michael@0: if (!value || !value.charset || (value.charset === "*")) { michael@0: Octet.encode(data, 128); michael@0: return; michael@0: } michael@0: michael@0: throw new CodeError("Any-charset: invalid value " + value); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Well-known-charset = Any-charset | Integer-value michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.8 michael@0: */ michael@0: this.WellKnownCharset = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A object with a string property `charset`. michael@0: * michael@0: * @throws CodeError if decoded charset number is an array. michael@0: * @throws NotWellKnownEncodingError if decoded well-known charset number michael@0: * is not registered or supported. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: michael@0: try { michael@0: return AcceptCharsetValue.decodeAnyCharset(data); michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: michael@0: // `IntegerValue.decode` can return a array, which doesn't apply here. michael@0: let numOrArray = IntegerValue.decode(data); michael@0: if (typeof numOrArray != "number") { michael@0: throw new CodeError("Well-known-charset: invalid integer type"); michael@0: } michael@0: michael@0: let charset = numOrArray; michael@0: let entry = WSP_WELL_KNOWN_CHARSETS[charset]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Well-known-charset: not well known charset " + charset); michael@0: } michael@0: michael@0: return {charset: entry.name}; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: */ michael@0: encode: function(data, value) { michael@0: let begin = data.offset; michael@0: try { michael@0: AcceptCharsetValue.encodeAnyCharset(data, value); michael@0: return; michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: let entry = WSP_WELL_KNOWN_CHARSETS[value.charset.toLowerCase()]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Well-known-charset: not well known charset " + value.charset); michael@0: } michael@0: michael@0: IntegerValue.encode(data, entry.number); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * The short form of the Content-type-value MUST only be used when the michael@0: * well-known media is in the range of 0-127 or a text string. In all other michael@0: * cases the general form MUST be used. michael@0: * michael@0: * Content-type-value = Constrained-media | Content-general-form michael@0: * Constrained-media = Constrained-encoding michael@0: * Content-general-form = Value-length Media-type michael@0: * Media-type = Media *(Parameter) michael@0: * Media = Well-known-media | Extension-Media michael@0: * Well-known-media = Integer-value michael@0: * Extension-Media = *TEXT End-of-string michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.24 michael@0: */ michael@0: this.ContentTypeValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `media` and `params` properties or michael@0: * null in case of a failed parsing. The `media` property must be a michael@0: * string, and the `params` property is always null. michael@0: * michael@0: * @throws NotWellKnownEncodingError if decoded well-known content type number michael@0: * is not registered or supported. michael@0: */ michael@0: decodeConstrainedMedia: function(data) { michael@0: return { michael@0: media: TypeValue.decode(data), michael@0: params: null, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decode string. michael@0: * michael@0: * @throws CodeError if decoded content type number is an array. michael@0: * @throws NotWellKnownEncodingError if decoded well-known content type michael@0: * number is not registered or supported. michael@0: */ michael@0: decodeMedia: function(data) { michael@0: let begin = data.offset, number; michael@0: try { michael@0: number = IntegerValue.decode(data); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: return NullTerminatedTexts.decode(data).toLowerCase(); michael@0: } michael@0: michael@0: // `decodeIntegerValue` can return a array, which doesn't apply here. michael@0: if (typeof number != "number") { michael@0: throw new CodeError("Media: invalid integer type"); michael@0: } michael@0: michael@0: let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError("Media: not well known media " + number); michael@0: } michael@0: michael@0: return entry.type; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param end michael@0: * Ending offset of the Media-type value. michael@0: * michael@0: * @return A decoded object containing `media` and `params` properties or michael@0: * null in case of a failed parsing. The `media` property must be a michael@0: * string, and the `params` property is a hash map from a string to michael@0: * an value of unspecified type. michael@0: */ michael@0: decodeMediaType: function(data, end) { michael@0: let media = this.decodeMedia(data); michael@0: let params = Parameter.decodeMultiple(data, end); michael@0: michael@0: return { michael@0: media: media, michael@0: params: params, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `media` and `params` properties or michael@0: * null in case of a failed parsing. The `media` property must be a michael@0: * string, and the `params` property is null or a hash map from a michael@0: * string to an value of unspecified type. michael@0: */ michael@0: decodeContentGeneralForm: function(data) { michael@0: let length = ValueLength.decode(data); michael@0: let end = data.offset + length; michael@0: michael@0: let value = this.decodeMediaType(data, end); michael@0: michael@0: if (data.offset != end) { michael@0: data.offset = end; michael@0: } michael@0: michael@0: return value; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return A decoded object containing `media` and `params` properties or michael@0: * null in case of a failed parsing. The `media` property must be a michael@0: * string, and the `params` property is null or a hash map from a michael@0: * string to an value of unspecified type. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: michael@0: try { michael@0: return this.decodeConstrainedMedia(data); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: return this.decodeContentGeneralForm(data); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An object containing `media` and `params` properties. michael@0: */ michael@0: encodeConstrainedMedia: function(data, value) { michael@0: if (value.params) { michael@0: throw new CodeError("Constrained-media: should use general form instead"); michael@0: } michael@0: michael@0: TypeValue.encode(data, value.media); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An object containing `media` and `params` properties. michael@0: */ michael@0: encodeMediaType: function(data, value) { michael@0: let entry = WSP_WELL_KNOWN_CONTENT_TYPES[value.media.toLowerCase()]; michael@0: if (entry) { michael@0: IntegerValue.encode(data, entry.number); michael@0: } else { michael@0: NullTerminatedTexts.encode(data, value.media); michael@0: } michael@0: michael@0: Parameter.encodeMultiple(data, value.params); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An object containing `media` and `params` properties. michael@0: */ michael@0: encodeContentGeneralForm: function(data, value) { michael@0: let begin = data.offset; michael@0: this.encodeMediaType(data, value); michael@0: michael@0: // Calculate how much octets will be written and seek back. michael@0: // TODO: use memmove, see bug 730873 michael@0: let len = data.offset - begin; michael@0: data.offset = begin; michael@0: michael@0: ValueLength.encode(data, len); michael@0: this.encodeMediaType(data, value); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object to store encoded raw data. michael@0: * @param value michael@0: * An object containing `media` and `params` properties. michael@0: */ michael@0: encode: function(data, value) { michael@0: let begin = data.offset; michael@0: michael@0: try { michael@0: this.encodeConstrainedMedia(data, value); michael@0: } catch (e) { michael@0: data.offset = begin; michael@0: this.encodeContentGeneralForm(data, value); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Application-id-value = Uri-value | App-assigned-code michael@0: * App-assigned-code = Integer-value michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.4.2.54 michael@0: */ michael@0: this.ApplicationIdValue = { michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return Decoded string value. michael@0: * michael@0: * @throws CodeError if decoded application id number is an array. michael@0: * @throws NotWellKnownEncodingError if decoded well-known application id michael@0: * number is not registered or supported. michael@0: */ michael@0: decode: function(data) { michael@0: let begin = data.offset; michael@0: try { michael@0: return UriValue.decode(data); michael@0: } catch (e) {} michael@0: michael@0: data.offset = begin; michael@0: michael@0: // `decodeIntegerValue` can return a array, which doesn't apply here. michael@0: let numOrArray = IntegerValue.decode(data); michael@0: if (typeof numOrArray != "number") { michael@0: throw new CodeError("Application-id-value: invalid integer type"); michael@0: } michael@0: michael@0: let id = numOrArray; michael@0: let entry = OMNA_PUSH_APPLICATION_IDS[id]; michael@0: if (!entry) { michael@0: throw new NotWellKnownEncodingError( michael@0: "Application-id-value: not well known id: " + id); michael@0: } michael@0: michael@0: return entry.urn; michael@0: }, michael@0: }; michael@0: michael@0: this.PduHelper = { michael@0: /** michael@0: * @param data michael@0: * A UInt8Array of data for decode. michael@0: * @param charset michael@0: * charset for decode michael@0: * michael@0: * @return Decoded string. michael@0: */ michael@0: decodeStringContent: function(data, charset) { michael@0: let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: michael@0: let entry; michael@0: if (charset) { michael@0: entry = WSP_WELL_KNOWN_CHARSETS[charset]; michael@0: } michael@0: // Set converter to default one if (entry && entry.converter) is null. michael@0: // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9 michael@0: conv.charset = (entry && entry.converter) || "UTF-8"; michael@0: try { michael@0: return conv.convertFromByteArray(data, data.length); michael@0: } catch (e) { michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * @param strContent michael@0: * Decoded string content. michael@0: * @param charset michael@0: * Charset for encode. michael@0: * michael@0: * @return An encoded UInt8Array of string content. michael@0: */ michael@0: encodeStringContent: function(strContent, charset) { michael@0: let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: michael@0: let entry; michael@0: if (charset) { michael@0: entry = WSP_WELL_KNOWN_CHARSETS[charset]; michael@0: } michael@0: // Set converter to default one if (entry && entry.converter) is null. michael@0: // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9 michael@0: conv.charset = (entry && entry.converter) || "UTF-8"; michael@0: try { michael@0: return conv.convertToByteArray(strContent); michael@0: } catch (e) { michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Parse multiple header fields with end mark. michael@0: * michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param end michael@0: * An ending offset indicating the end of headers. michael@0: * @param headers [optional] michael@0: * An optional object to store parsed header fields. Created michael@0: * automatically if undefined. michael@0: * michael@0: * @return A object containing decoded header fields as its attributes. michael@0: */ michael@0: parseHeaders: function(data, end, headers) { michael@0: if (!headers) { michael@0: headers = {}; michael@0: } michael@0: michael@0: let header; michael@0: while (data.offset < end) { michael@0: try { michael@0: header = Header.decode(data); michael@0: } catch (e) { michael@0: break; michael@0: } michael@0: if (header) { michael@0: headers[header.name] = header.value; michael@0: } michael@0: } michael@0: michael@0: if (data.offset != end) { michael@0: debug("Parser expects ending in " + end + ", but in " + data.offset); michael@0: // Explicitly seek to end in case of skipped header fields. michael@0: data.offset = end; michael@0: } michael@0: michael@0: return headers; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param msg michael@0: * Message object to be populated with decoded header fields. michael@0: * michael@0: * @see WAP-230-WSP-20010705-a clause 8.2.4 michael@0: */ michael@0: parsePushHeaders: function(data, msg) { michael@0: if (!msg.headers) { michael@0: msg.headers = {}; michael@0: } michael@0: michael@0: let headersLen = UintVar.decode(data); michael@0: let headersEnd = data.offset + headersLen; michael@0: michael@0: let contentType = ContentTypeValue.decode(data); michael@0: msg.headers["content-type"] = contentType; michael@0: michael@0: msg.headers = this.parseHeaders(data, headersEnd, msg.headers); michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * michael@0: * @return An array of objects representing multipart entries or null in case michael@0: * of errors found. michael@0: * michael@0: * @see WAP-230-WSP-20010705-a section 8.5 michael@0: */ michael@0: parseMultiPart: function(data) { michael@0: let nEntries = UintVar.decode(data); michael@0: if (!nEntries) { michael@0: return null; michael@0: } michael@0: michael@0: let parts = new Array(nEntries); michael@0: for (let i = 0; i < nEntries; i++) { michael@0: // Length of the ContentType and Headers fields combined. michael@0: let headersLen = UintVar.decode(data); michael@0: // Length of the Data field michael@0: let contentLen = UintVar.decode(data); michael@0: michael@0: let headersEnd = data.offset + headersLen; michael@0: let contentEnd = headersEnd + contentLen; michael@0: michael@0: try { michael@0: let headers = {}; michael@0: michael@0: let contentType = ContentTypeValue.decode(data); michael@0: headers["content-type"] = contentType; michael@0: headers["content-length"] = contentLen; michael@0: michael@0: headers = this.parseHeaders(data, headersEnd, headers); michael@0: michael@0: let octetArray = Octet.decodeMultiple(data, contentEnd); michael@0: let content = null; michael@0: let charset = headers["content-type"].params && michael@0: headers["content-type"].params.charset michael@0: ? headers["content-type"].params.charset.charset michael@0: : null; michael@0: michael@0: let mimeType = headers["content-type"].media; michael@0: michael@0: if (mimeType) { michael@0: if (mimeType == "application/smil") { michael@0: // If the content is a SMIL type, convert it to a string. michael@0: // We hope to save and expose the SMIL content in a string way. michael@0: content = this.decodeStringContent(octetArray, charset); michael@0: } else if (mimeType.indexOf("text/") == 0 && charset != "utf-8") { michael@0: // If the content is a "text/plain" type, we have to make sure michael@0: // the encoding of the blob content should always be "utf-8". michael@0: let tmpStr = this.decodeStringContent(octetArray, charset); michael@0: let encoder = new TextEncoder("UTF-8"); michael@0: content = new Blob([encoder.encode(tmpStr)], {type : mimeType}); michael@0: michael@0: // Make up the missing encoding info. michael@0: if (!headers["content-type"].params) { michael@0: headers["content-type"].params = {}; michael@0: } michael@0: if (!headers["content-type"].params.charset) { michael@0: headers["content-type"].params.charset = {}; michael@0: } michael@0: headers["content-type"].params.charset.charset = "utf-8"; michael@0: } michael@0: } michael@0: michael@0: if (!content) { michael@0: content = new Blob([octetArray], {type : mimeType}); michael@0: } michael@0: michael@0: parts[i] = { michael@0: index: i, michael@0: headers: headers, michael@0: content: content, michael@0: }; michael@0: } catch (e) { michael@0: debug("Failed to parse multipart entry, message: " + e.message); michael@0: // Placeholder to keep original index of following entries. michael@0: parts[i] = null; michael@0: } michael@0: michael@0: if (data.offset != contentEnd) { michael@0: // Seek to entry boundary for next entry. michael@0: data.offset = contentEnd; michael@0: } michael@0: } michael@0: michael@0: return parts; michael@0: }, michael@0: michael@0: /** michael@0: * @param data michael@0: * A wrapped object containing raw PDU data. michael@0: * @param isSessionless michael@0: * Whether or not the PDU contains a session less WSP PDU. michael@0: * @param msg [optional] michael@0: * Optional pre-defined PDU object. michael@0: * michael@0: * @return Parsed WSP PDU object or null in case of errors found. michael@0: */ michael@0: parse: function(data, isSessionless, msg) { michael@0: if (!msg) { michael@0: msg = { michael@0: type: null, michael@0: }; michael@0: } michael@0: michael@0: try { michael@0: if (isSessionless) { michael@0: // "The `transactionId` is used to associate requests with replies in michael@0: // the connectionless session service." ~ WAP-230-WSP-20010705-a 8.2.1 michael@0: msg.transactionId = Octet.decode(data); michael@0: } michael@0: michael@0: msg.type = Octet.decode(data); michael@0: switch (msg.type) { michael@0: case WSP_PDU_TYPE_PUSH: michael@0: this.parsePushHeaders(data, msg); michael@0: break; michael@0: } michael@0: } catch (e) { michael@0: debug("Parse error. Message: " + e.message); michael@0: msg = null; michael@0: } michael@0: michael@0: return msg; michael@0: }, michael@0: michael@0: /** michael@0: * @param multiStream michael@0: * An exsiting nsIMultiplexInputStream. michael@0: * @param array michael@0: * An octet array. michael@0: * @param length michael@0: * Max number of octets to be coverted into an input stream. michael@0: */ michael@0: appendArrayToMultiStream: function(multiStream, array, length) { michael@0: let storageStream = Cc["@mozilla.org/storagestream;1"] michael@0: .createInstance(Ci.nsIStorageStream); michael@0: storageStream.init(4096, length, null); michael@0: michael@0: let boStream = Cc["@mozilla.org/binaryoutputstream;1"] michael@0: .createInstance(Ci.nsIBinaryOutputStream); michael@0: boStream.setOutputStream(storageStream.getOutputStream(0)); michael@0: boStream.writeByteArray(array, length); michael@0: boStream.close(); michael@0: michael@0: multiStream.appendStream(storageStream.newInputStream(0)); michael@0: }, michael@0: michael@0: /** michael@0: * @param multiStream michael@0: * An exsiting nsIMultiplexInputStream. michael@0: * @param parts michael@0: * An array of objects representing multipart entries. michael@0: * michael@0: * @see WAP-230-WSP-20010705-a section 8.5 michael@0: */ michael@0: composeMultiPart: function(multiStream, parts) { michael@0: // Encode multipart header michael@0: { michael@0: let data = {array: [], offset: 0}; michael@0: UintVar.encode(data, parts.length); michael@0: debug("Encoded multipart header: " + JSON.stringify(data.array)); michael@0: this.appendArrayToMultiStream(multiStream, data.array, data.offset); michael@0: } michael@0: michael@0: // Encode each part michael@0: for (let i = 0; i < parts.length; i++) { michael@0: let part = parts[i]; michael@0: let data = {array: [], offset: 0}; michael@0: michael@0: // Encode Content-Type michael@0: let contentType = part.headers["content-type"]; michael@0: ContentTypeValue.encode(data, contentType); michael@0: michael@0: // Encode other headers michael@0: if (Object.keys(part).length > 1) { michael@0: // Remove Content-Type temporarily michael@0: delete part.headers["content-type"]; michael@0: michael@0: for (let name in part.headers) { michael@0: Header.encode(data, {name: name, value: part.headers[name]}); michael@0: } michael@0: michael@0: // Restore Content-Type back michael@0: part.headers["content-type"] = contentType; michael@0: } michael@0: michael@0: // Encode headersLen, DataLen michael@0: let headersLen = data.offset; michael@0: let content = part.content; michael@0: UintVar.encode(data, headersLen); michael@0: if (typeof content === "string") { michael@0: let charset; michael@0: if (contentType && contentType.params && contentType.params.charset && michael@0: contentType.params.charset.charset) { michael@0: charset = contentType.params.charset.charset; michael@0: } michael@0: content = this.encodeStringContent(content, charset); michael@0: UintVar.encode(data, content.length); michael@0: } else if (part.content instanceof Uint8Array) { michael@0: UintVar.encode(data, content.length); michael@0: } else { michael@0: throw new TypeError(); michael@0: } michael@0: michael@0: // Move them to the beginning of encoded octet array. michael@0: let slice1 = data.array.slice(headersLen); michael@0: let slice2 = data.array.slice(0, headersLen); michael@0: data.array = slice1.concat(slice2); michael@0: debug("Encoded per-part header: " + JSON.stringify(data.array)); michael@0: michael@0: // Append per-part header michael@0: this.appendArrayToMultiStream(multiStream, data.array, data.offset); michael@0: // Append part content michael@0: this.appendArrayToMultiStream(multiStream, content, content.length); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: // WSP Header Field Name Assignments michael@0: // Note: Items commented out are either deprecated or not implemented. michael@0: // Deprecated items should only be supported for backward compatibility michael@0: // purpose. michael@0: // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. michael@0: this.WSP_HEADER_FIELDS = (function() { michael@0: let names = {}; michael@0: function add(name, number, coder) { michael@0: let entry = { michael@0: name: name, michael@0: number: number, michael@0: coder: coder, michael@0: }; michael@0: names[name] = names[number] = entry; michael@0: } michael@0: michael@0: // Encoding Version: 1.1 michael@0: //add("accept", 0x00); michael@0: //add("accept-charset", 0x01); Deprecated michael@0: //add("accept-encoding", 0x02); Deprecated michael@0: //add("accept-language", 0x03); michael@0: //add("accept-ranges", 0x04); michael@0: add("age", 0x05, DeltaSecondsValue); michael@0: //add("allow", 0x06); michael@0: //add("authorization", 0x07); michael@0: //add("cache-control", 0x08); Deprecated michael@0: //add("connection", 0x09); michael@0: //add("content-base", 0x0A); Deprecated michael@0: //add("content-encoding", 0x0B); michael@0: //add("content-language", 0x0C); michael@0: add("content-length", 0x0D, IntegerValue); michael@0: add("content-location", 0x0E, UriValue); michael@0: //add("content-md5", 0x0F); michael@0: //add("content-range", 0x10); Deprecated michael@0: add("content-type", 0x11, ContentTypeValue); michael@0: add("date", 0x12, DateValue); michael@0: add("etag", 0x13, TextString); michael@0: add("expires", 0x14, DateValue); michael@0: add("from", 0x15, TextString); michael@0: add("host", 0x16, TextString); michael@0: add("if-modified-since", 0x17, DateValue); michael@0: add("if-match", 0x18, TextString); michael@0: add("if-none-match", 0x19, TextString); michael@0: //add("if-range", 0x1A); michael@0: add("if-unmodified-since", 0x1B, DateValue); michael@0: add("location", 0x1C, UriValue); michael@0: add("last-modified", 0x1D, DateValue); michael@0: add("max-forwards", 0x1E, IntegerValue); michael@0: //add("pragma", 0x1F); michael@0: //add("proxy-authenticate", 0x20); michael@0: //add("proxy-authentication", 0x21); michael@0: //add("public", 0x22); michael@0: //add("range", 0x23); michael@0: add("referer", 0x24, UriValue); michael@0: //add("retry-after", 0x25); michael@0: add("server", 0x26, TextString); michael@0: //add("transfer-encoding", 0x27); michael@0: add("upgrade", 0x28, TextString); michael@0: add("user-agent", 0x29, TextString); michael@0: //add("vary", 0x2A); michael@0: add("via", 0x2B, TextString); michael@0: //add("warning", 0x2C); michael@0: //add("www-authenticate", 0x2D); michael@0: //add("content-disposition", 0x2E); Deprecated michael@0: michael@0: // Encoding Version: 1.2 michael@0: add("x-wap-application-id", 0x2F, ApplicationIdValue); michael@0: add("x-wap-content-uri", 0x30, UriValue); michael@0: add("x-wap-initiator-uri", 0x31, UriValue); michael@0: //add("accept-application", 0x32); michael@0: add("bearer-indication", 0x33, IntegerValue); michael@0: add("push-flag", 0x34, ShortInteger); michael@0: add("profile", 0x35, UriValue); michael@0: //add("profile-diff", 0x36); michael@0: //add("profile-warning", 0x37); Deprecated michael@0: michael@0: // Encoding Version: 1.3 michael@0: //add("expect", 0x38); michael@0: //add("te", 0x39); michael@0: //add("trailer", 0x3A); michael@0: add("accept-charset", 0x3B, AcceptCharsetValue); michael@0: //add("accept-encoding", 0x3C); michael@0: //add("cache-control", 0x3D); Deprecated michael@0: //add("content-range", 0x3E); michael@0: add("x-wap-tod", 0x3F, DateValue); michael@0: add("content-id", 0x40, QuotedString); michael@0: //add("set-cookie", 0x41); michael@0: //add("cookie", 0x42); michael@0: //add("encoding-version", 0x43); michael@0: michael@0: // Encoding Version: 1.4 michael@0: //add("profile-warning", 0x44); michael@0: //add("content-disposition", 0x45); michael@0: //add("x-wap-security", 0x46); michael@0: //add("cache-control", 0x47); michael@0: michael@0: return names; michael@0: })(); michael@0: michael@0: // WSP Content Type Assignments michael@0: // @see http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.aspx michael@0: this.WSP_WELL_KNOWN_CONTENT_TYPES = (function() { michael@0: let types = {}; michael@0: michael@0: function add(type, number) { michael@0: let entry = { michael@0: type: type, michael@0: number: number, michael@0: }; michael@0: // For case like "text/x-vCalendar", we need toLoweCase() for generating michael@0: // the same index. michael@0: types[type.toLowerCase()] = types[number] = entry; michael@0: } michael@0: michael@0: // Well Known Values michael@0: // Encoding Version: 1.1 michael@0: add("*/*", 0x00); michael@0: add("text/*", 0x01); michael@0: add("text/html", 0x02); michael@0: add("text/plain", 0x03); michael@0: add("text/x-hdml", 0x04); michael@0: add("text/x-ttml", 0x05); michael@0: add("text/x-vCalendar", 0x06); michael@0: add("text/x-vCard", 0x07); michael@0: add("text/vnd.wap.wml", 0x08); michael@0: add("text/vnd.wap.wmlscript", 0x09); michael@0: add("text/vnd.wap.wta-event", 0x0A); michael@0: add("multipart/*", 0x0B); michael@0: add("multipart/mixed", 0x0C); michael@0: add("multipart/form-data", 0x0D); michael@0: add("multipart/byterantes", 0x0E); michael@0: add("multipart/alternative", 0x0F); michael@0: add("application/*", 0x10); michael@0: add("application/java-vm", 0x11); michael@0: add("application/x-www-form-urlencoded", 0x12); michael@0: add("application/x-hdmlc", 0x13); michael@0: add("application/vnd.wap.wmlc", 0x14); michael@0: add("application/vnd.wap.wmlscriptc", 0x15); michael@0: add("application/vnd.wap.wta-eventc", 0x16); michael@0: add("application/vnd.wap.uaprof", 0x17); michael@0: add("application/vnd.wap.wtls-ca-certificate", 0x18); michael@0: add("application/vnd.wap.wtls-user-certificate", 0x19); michael@0: add("application/x-x509-ca-cert", 0x1A); michael@0: add("application/x-x509-user-cert", 0x1B); michael@0: add("image/*", 0x1C); michael@0: add("image/gif", 0x1D); michael@0: add("image/jpeg", 0x1E); michael@0: add("image/tiff", 0x1F); michael@0: add("image/png", 0x20); michael@0: add("image/vnd.wap.wbmp", 0x21); michael@0: add("application/vnd.wap.multipart.*", 0x22); michael@0: add("application/vnd.wap.multipart.mixed", 0x23); michael@0: add("application/vnd.wap.multipart.form-data", 0x24); michael@0: add("application/vnd.wap.multipart.byteranges", 0x25); michael@0: add("application/vnd.wap.multipart.alternative", 0x26); michael@0: add("application/xml", 0x27); michael@0: add("text/xml", 0x28); michael@0: add("application/vnd.wap.wbxml", 0x29); michael@0: add("application/x-x968-cross-cert", 0x2A); michael@0: add("application/x-x968-ca-cert", 0x2B); michael@0: add("application/x-x968-user-cert", 0x2C); michael@0: add("text/vnd.wap.si", 0x2D); michael@0: michael@0: // Encoding Version: 1.2 michael@0: add("application/vnd.wap.sic", 0x2E); michael@0: add("text/vnd.wap.sl", 0x2F); michael@0: add("application/vnd.wap.slc", 0x30); michael@0: add("text/vnd.wap.co", 0x31); michael@0: add("application/vnd.wap.coc", 0x32); michael@0: add("application/vnd.wap.multipart.related", 0x33); michael@0: add("application/vnd.wap.sia", 0x34); michael@0: michael@0: // Encoding Version: 1.3 michael@0: add("text/vnd.wap.connectivity-xml", 0x35); michael@0: add("application/vnd.wap.connectivity-wbxml", 0x36); michael@0: michael@0: // Encoding Version: 1.4 michael@0: add("application/pkcs7-mime", 0x37); michael@0: add("application/vnd.wap.hashed-certificate", 0x38); michael@0: add("application/vnd.wap.signed-certificate", 0x39); michael@0: add("application/vnd.wap.cert-response", 0x3A); michael@0: add("application/xhtml+xml", 0x3B); michael@0: add("application/wml+xml", 0x3C); michael@0: add("text/css", 0x3D); michael@0: add("application/vnd.wap.mms-message", 0x3E); michael@0: add("application/vnd.wap.rollover-certificate", 0x3F); michael@0: michael@0: // Encoding Version: 1.5 michael@0: add("application/vnd.wap.locc+wbxml", 0x40); michael@0: add("application/vnd.wap.loc+xml", 0x41); michael@0: add("application/vnd.syncml.dm+wbxml", 0x42); michael@0: add("application/vnd.syncml.dm+xml", 0x43); michael@0: add("application/vnd.syncml.notification", 0x44); michael@0: add("application/vnd.wap.xhtml+xml", 0x45); michael@0: add("application/vnd.wv.csp.cir", 0x46); michael@0: add("application/vnd.oma.dd+xml", 0x47); michael@0: add("application/vnd.oma.drm.message", 0x48); michael@0: add("application/vnd.oma.drm.content", 0x49); michael@0: add("application/vnd.oma.drm.rights+xml", 0x4A); michael@0: add("application/vnd.oma.drm.rights+wbxml", 0x4B); michael@0: add("application/vnd.wv.csp+xml", 0x4C); michael@0: add("application/vnd.wv.csp+wbxml", 0x4D); michael@0: add("application/vnd.syncml.ds.notification", 0x4E); michael@0: michael@0: // Encoding Version: 1.6 michael@0: add("audio/*", 0x4F); michael@0: add("video/*", 0x50); michael@0: michael@0: // Encoding Version: TBD michael@0: add("application/vnd.oma.dd2+xml", 0x51); michael@0: add("application/mikey", 0x52); michael@0: add("application/vnd.oma.dcd", 0x53); michael@0: add("application/vnd.oma.dcdc", 0x54); michael@0: add("text/x-vMessage", 0x55); michael@0: add("application/vnd.omads-email+wbxml", 0x56); michael@0: add("text/x-vBookmark", 0x57); michael@0: add("application/vnd.syncml.dm.notification", 0x58); michael@0: add("application/octet-stream", 0x5A); michael@0: michael@0: return types; michael@0: })(); michael@0: michael@0: // WSP Well-Known Parameter Assignments michael@0: // Note: Items commented out are either deprecated or not implemented. michael@0: // Deprecated items should not be used. michael@0: // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. michael@0: this.WSP_WELL_KNOWN_PARAMS = (function() { michael@0: let params = {}; michael@0: michael@0: function add(name, number, coder) { michael@0: let entry = { michael@0: name: name, michael@0: number: number, michael@0: coder: coder, michael@0: }; michael@0: params[name] = params[number] = entry; michael@0: } michael@0: michael@0: // Encoding Version: 1.1 michael@0: add("q", 0x00, QValue); michael@0: add("charset", 0x01, WellKnownCharset); michael@0: add("level", 0x02, VersionValue); michael@0: add("type", 0x03, IntegerValue); michael@0: add("name", 0x05, TextValue); // Deprecated, but used in some carriers, eg. Hinet. michael@0: //add("filename", 0x06); Deprecated michael@0: add("differences", 0x07, FieldName); michael@0: add("padding", 0x08, ShortInteger); michael@0: michael@0: // Encoding Version: 1.2 michael@0: add("type", 0x09, TypeValue); michael@0: add("start", 0x0A, TextValue); // Deprecated, but used in some carriers, eg. T-Mobile. michael@0: //add("start-info", 0x0B); Deprecated michael@0: michael@0: // Encoding Version: 1.3 michael@0: //add("comment", 0x0C); Deprecated michael@0: //add("domain", 0x0D); Deprecated michael@0: add("max-age", 0x0E, DeltaSecondsValue); michael@0: //add("path", 0x0F); Deprecated michael@0: add("secure", 0x10, NoValue); michael@0: michael@0: // Encoding Version: 1.4 michael@0: add("sec", 0x11, ShortInteger); michael@0: add("mac", 0x12, TextValue); michael@0: add("creation-date", 0x13, DateValue); michael@0: add("modification-date", 0x14, DateValue); michael@0: add("read-date", 0x15, DateValue); michael@0: add("size", 0x16, IntegerValue); michael@0: //add("name", 0x17, TextValue); // Not supported in some carriers, eg. Hinet. michael@0: add("filename", 0x18, TextValue); michael@0: //add("start", 0x19, TextValue); // Not supported in some carriers, eg. Hinet. michael@0: add("start-info", 0x1A, TextValue); michael@0: add("comment", 0x1B, TextValue); michael@0: add("domain", 0x1C, TextValue); michael@0: add("path", 0x1D, TextValue); michael@0: michael@0: return params; michael@0: })(); michael@0: michael@0: // WSP Character Set Assignments michael@0: // @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. michael@0: // @see http://www.iana.org/assignments/character-sets michael@0: this.WSP_WELL_KNOWN_CHARSETS = (function() { michael@0: let charsets = {}; michael@0: michael@0: function add(name, number, converter) { michael@0: let entry = { michael@0: name: name, michael@0: number: number, michael@0: converter: converter, michael@0: }; michael@0: michael@0: charsets[name] = charsets[number] = entry; michael@0: } michael@0: michael@0: add("us-ascii", 3, null); michael@0: add("iso-8859-1", 4, "ISO-8859-1"); michael@0: add("iso-8859-2", 5, "ISO-8859-2"); michael@0: add("iso-8859-3", 6, "ISO-8859-3"); michael@0: add("iso-8859-4", 7, "ISO-8859-4"); michael@0: add("iso-8859-5", 8, "ISO-8859-5"); michael@0: add("iso-8859-6", 9, "ISO-8859-6"); michael@0: add("iso-8859-7", 10, "ISO-8859-7"); michael@0: add("iso-8859-8", 11, "ISO-8859-8"); michael@0: add("iso-8859-9", 12, "ISO-8859-9"); michael@0: add("iso-8859-10", 13, "ISO-8859-10"); michael@0: add("shift_jis", 17, "Shift_JIS"); michael@0: add("euc-jp", 18, "EUC-JP"); michael@0: add("iso-2022-kr", 37, "ISO-2022-KR"); michael@0: add("euc-kr", 38, "EUC-KR"); michael@0: add("iso-2022-jp", 39, "ISO-2022-JP"); michael@0: add("iso-2022-jp-2", 40, "iso-2022-jp-2"); michael@0: add("iso-8859-6-e", 81, "ISO-8859-6-E"); michael@0: add("iso-8859-6-i", 82, "ISO-8859-6-I"); michael@0: add("iso-8859-8-e", 84, "ISO-8859-8-E"); michael@0: add("iso-8859-8-i", 85, "ISO-8859-8-I"); michael@0: add("utf-8", 106, "UTF-8"); michael@0: add("iso-10646-ucs-2", 1000, "iso-10646-ucs-2"); michael@0: add("utf-16", 1015, "UTF-16"); michael@0: add("gb2312", 2025, "GB2312"); michael@0: add("big5", 2026, "Big5"); michael@0: add("koi8-r", 2084, "KOI8-R"); michael@0: add("windows-1252", 2252, "windows-1252"); michael@0: michael@0: return charsets; michael@0: })(); michael@0: michael@0: // OMNA PUSH Application ID michael@0: // @see http://www.openmobilealliance.org/tech/omna/omna-push-app-id.aspx michael@0: this.OMNA_PUSH_APPLICATION_IDS = (function() { michael@0: let ids = {}; michael@0: michael@0: function add(urn, number) { michael@0: let entry = { michael@0: urn: urn, michael@0: number: number, michael@0: }; michael@0: michael@0: ids[urn] = ids[number] = entry; michael@0: } michael@0: michael@0: add("x-wap-application:wml.ua", 0x02); michael@0: add("x-wap-application:mms.ua", 0x04); michael@0: michael@0: return ids; michael@0: })(); michael@0: michael@0: let debug; michael@0: if (DEBUG) { michael@0: debug = function(s) { michael@0: dump("-@- WspPduHelper: " + s + "\n"); michael@0: }; michael@0: } else { michael@0: debug = function(s) {}; michael@0: } michael@0: michael@0: this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ michael@0: // Constant values michael@0: "WSP_HEADER_FIELDS", michael@0: "WSP_WELL_KNOWN_CONTENT_TYPES", michael@0: "WSP_WELL_KNOWN_PARAMS", michael@0: "WSP_WELL_KNOWN_CHARSETS", michael@0: "OMNA_PUSH_APPLICATION_IDS", michael@0: michael@0: // Error classes michael@0: "CodeError", michael@0: "FatalCodeError", michael@0: "NotWellKnownEncodingError", michael@0: michael@0: // Utility functions michael@0: "ensureHeader", michael@0: "skipValue", michael@0: "decodeAlternatives", michael@0: "encodeAlternatives", michael@0: michael@0: // Decoders michael@0: "Octet", michael@0: "Text", michael@0: "NullTerminatedTexts", michael@0: "Token", michael@0: "URIC", michael@0: "TextString", michael@0: "TokenText", michael@0: "QuotedString", michael@0: "ShortInteger", michael@0: "LongInteger", michael@0: "UintVar", michael@0: "ConstrainedEncoding", michael@0: "ValueLength", michael@0: "NoValue", michael@0: "TextValue", michael@0: "IntegerValue", michael@0: "DateValue", michael@0: "DeltaSecondsValue", michael@0: "QValue", michael@0: "VersionValue", michael@0: "UriValue", michael@0: "TypeValue", michael@0: "Parameter", michael@0: "Header", michael@0: "WellKnownHeader", michael@0: "ApplicationHeader", michael@0: "FieldName", michael@0: "AcceptCharsetValue", michael@0: "WellKnownCharset", michael@0: "ContentTypeValue", michael@0: "ApplicationIdValue", michael@0: michael@0: // Parser michael@0: "PduHelper", michael@0: ]);