gecko-dev/dom/mobilemessage/gonk/WspPduHelper.jsm

2901 lines
80 KiB
JavaScript

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