mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1711 lines
52 KiB
JavaScript
1711 lines
52 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;
|
|
|
|
var WSP = {};
|
|
Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP);
|
|
|
|
Cu.import("resource://gre/modules/mms_consts.js");
|
|
|
|
Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
|
|
|
|
var DEBUG; // set to true to see debug messages
|
|
|
|
this.MMS_VERSION = (function() {
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
try {
|
|
return Services.prefs.getIntPref("dom.mms.version");
|
|
} catch(ex) {}
|
|
|
|
return MMS_VERSION_1_3;
|
|
})();
|
|
|
|
this.translatePduErrorToStatus = function translatePduErrorToStatus(error) {
|
|
if (error == MMS_PDU_ERROR_OK) {
|
|
return MMS_PDU_STATUS_RETRIEVED;
|
|
}
|
|
|
|
if ((error >= MMS_PDU_ERROR_TRANSIENT_FAILURE)
|
|
&& (error < MMS_PDU_ERROR_PERMANENT_FAILURE)) {
|
|
return MMS_PDU_STATUS_DEFERRED;
|
|
}
|
|
|
|
return MMS_PDU_STATUS_UNRECOGNISED;
|
|
}
|
|
|
|
function defineLazyRegExp(obj, name, pattern) {
|
|
obj.__defineGetter__(name, function() {
|
|
delete obj[name];
|
|
return obj[name] = new RegExp(pattern);
|
|
});
|
|
}
|
|
|
|
function RangedValue(name, min, max) {
|
|
this.name = name;
|
|
this.min = min;
|
|
this.max = max;
|
|
}
|
|
RangedValue.prototype = {
|
|
name: null,
|
|
min: null,
|
|
max: null,
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded integer.
|
|
*
|
|
* @throws CodeError if decoded value is not in the range [this.min, this.max].
|
|
*/
|
|
decode: function(data) {
|
|
let value = WSP.Octet.decode(data);
|
|
if ((value >= this.min) && (value <= this.max)) {
|
|
return value;
|
|
}
|
|
|
|
throw new WSP.CodeError(this.name + ": invalid value " + value);
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param value
|
|
* An integer value within thr range [this.min, this.max].
|
|
*/
|
|
encode: function(data, value) {
|
|
if ((value < this.min) || (value > this.max)) {
|
|
throw new WSP.CodeError(this.name + ": invalid value " + value);
|
|
}
|
|
|
|
WSP.Octet.encode(data, value);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Internal decoding function for boolean values.
|
|
*
|
|
* Boolean-value = Yes | No
|
|
* Yes = <Octet 128>
|
|
* No = <Octet 129>
|
|
*/
|
|
this.BooleanValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return Boolean true or false.
|
|
*
|
|
* @throws CodeError if read octet equals to neither 128 nor 129.
|
|
*/
|
|
decode: function(data) {
|
|
let value = WSP.Octet.decode(data);
|
|
if ((value != 128) && (value != 129)) {
|
|
throw new WSP.CodeError("Boolean-value: invalid value " + value);
|
|
}
|
|
|
|
return value == 128;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param value
|
|
* A boolean value to be encoded.
|
|
*/
|
|
encode: function(data, value) {
|
|
WSP.Octet.encode(data, value ? 128 : 129);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* MMS Address
|
|
*
|
|
* address = email | device-address | alphanum-shortcode | num-shortcode
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A section 8
|
|
*/
|
|
this.Address = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
*
|
|
* @return An object of two string-typed attributes: address and type.
|
|
*/
|
|
decode: function(data) {
|
|
let str = EncodedStringValue.decode(data);
|
|
|
|
let result;
|
|
if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null)
|
|
|| ((result = str.match(this.REGEXP_DECODE_IPV4)) != null)
|
|
|| ((result = str.match(this.REGEXP_DECODE_IPV6)) != null)
|
|
|| (((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)
|
|
&& (result[2] != "PLMN")
|
|
&& (result[2] != "IPv4")
|
|
&& (result[2] != "IPv6"))) {
|
|
return {address: result[1], type: result[2]};
|
|
}
|
|
|
|
let type;
|
|
if (str.match(this.REGEXP_NUM)) {
|
|
type = "num";
|
|
} else if (str.match(this.REGEXP_ALPHANUM)) {
|
|
type = "alphanum";
|
|
} else if (str.match(this.REGEXP_EMAIL)) {
|
|
type = "email";
|
|
} else {
|
|
throw new WSP.CodeError("Address: invalid address");
|
|
}
|
|
|
|
return {address: str, type: type};
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param value
|
|
* An object of two string-typed attributes: address and type.
|
|
*/
|
|
encode: function(data, value) {
|
|
if (!value || !value.type || !value.address) {
|
|
throw new WSP.CodeError("Address: invalid value");
|
|
}
|
|
|
|
let str;
|
|
switch (value.type) {
|
|
case "email":
|
|
if (value.address.match(this.REGEXP_EMAIL)) {
|
|
str = value.address;
|
|
}
|
|
break;
|
|
case "num":
|
|
if (value.address.match(this.REGEXP_NUM)) {
|
|
str = value.address;
|
|
}
|
|
break;
|
|
case "alphanum":
|
|
if (value.address.match(this.REGEXP_ALPHANUM)) {
|
|
str = value.address;
|
|
}
|
|
break;
|
|
case "IPv4":
|
|
if (value.address.match(this.REGEXP_ENCODE_IPV4)) {
|
|
str = value.address + "/TYPE=IPv4";
|
|
}
|
|
break;
|
|
case "IPv6":
|
|
if (value.address.match(this.REGEXP_ENCODE_IPV6)) {
|
|
str = value.address + "/TYPE=IPv6";
|
|
}
|
|
break;
|
|
case "PLMN":
|
|
if (value.address.match(this.REGEXP_ENCODE_PLMN)) {
|
|
str = value.address + "/TYPE=PLMN";
|
|
}
|
|
break;
|
|
default:
|
|
if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE)
|
|
&& value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) {
|
|
str = value.address + "/TYPE=" + value.type;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!str) {
|
|
throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value));
|
|
}
|
|
|
|
EncodedStringValue.encode(data, str);
|
|
},
|
|
|
|
/**
|
|
* @param address
|
|
* Address string which want to find the type.
|
|
*
|
|
* @return Address type.
|
|
*/
|
|
resolveType: function(address) {
|
|
if (address.match(this.REGEXP_EMAIL)) {
|
|
return "email";
|
|
}
|
|
|
|
if (address.match(this.REGEXP_ENCODE_IPV4)) {
|
|
return "IPv4";
|
|
}
|
|
|
|
if (address.match(this.REGEXP_ENCODE_IPV6)) {
|
|
return "IPv6";
|
|
}
|
|
|
|
let normalizedAddress = PhoneNumberUtils.normalize(address, false);
|
|
if (PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) {
|
|
return "PLMN";
|
|
}
|
|
|
|
return "Others";
|
|
},
|
|
};
|
|
|
|
defineLazyRegExp(Address, "REGEXP_DECODE_PLMN", "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$");
|
|
defineLazyRegExp(Address, "REGEXP_DECODE_IPV4", "^((?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9]))\\/TYPE=(IPv4)$");
|
|
defineLazyRegExp(Address, "REGEXP_DECODE_IPV6", "^(" +
|
|
"(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,7}:|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|" +
|
|
"[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|" +
|
|
":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])" +
|
|
")\\/TYPE=(IPv6)$");
|
|
defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM", "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$");
|
|
defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN", "^\\+?[\\d.-]+$");
|
|
defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4", "^(?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])$");
|
|
defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6", "^(?:" +
|
|
"(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,7}:|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|" +
|
|
"(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|" +
|
|
"[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|" +
|
|
":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)" +
|
|
")$");
|
|
defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$");
|
|
defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$");
|
|
defineLazyRegExp(Address, "REGEXP_NUM", "^[\\+*#]?\\d+$");
|
|
defineLazyRegExp(Address, "REGEXP_ALPHANUM", "^\\w+$");
|
|
// OMA-TS-MMS_ENC-V1_3-20110913-A chapter 8:
|
|
//
|
|
// E-mail should match the definition of `mailbox` as described in section
|
|
// 3.4 of RFC2822, but excluding the obsolete definitions as indicated by
|
|
// the "obs-" prefix.
|
|
//
|
|
// Here we try to match addr-spec only.
|
|
defineLazyRegExp(Address, "REGEXP_EMAIL", "(?:" +
|
|
"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|" +
|
|
"\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\"" +
|
|
")@(?:" +
|
|
"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|" +
|
|
"\\[(?:" +
|
|
"[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f]" +
|
|
")*\\]" +
|
|
")");
|
|
|
|
/**
|
|
* Header-field = MMS-header | Application-header
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
|
|
*/
|
|
this.HeaderField = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param options
|
|
* Extra context for decoding.
|
|
*
|
|
* @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, options) {
|
|
return WSP.decodeAlternatives(data, options,
|
|
MmsHeader, WSP.ApplicationHeader);
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param octet
|
|
* Octet value to be encoded.
|
|
* @param options
|
|
* Extra context for encoding.
|
|
*/
|
|
encode: function(data, value, options) {
|
|
WSP.encodeAlternatives(data, value, options,
|
|
MmsHeader, WSP.ApplicationHeader);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* MMS-header = MMS-field-name MMS-value
|
|
* MMS-field-name = Short-integer
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
|
|
*/
|
|
this.MmsHeader = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param options
|
|
* Extra context for decoding.
|
|
*
|
|
* @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, options) {
|
|
let index = WSP.ShortInteger.decode(data);
|
|
|
|
let entry = MMS_HEADER_FIELDS[index];
|
|
if (!entry) {
|
|
throw new WSP.NotWellKnownEncodingError(
|
|
"MMS-header: not well known header " + index);
|
|
}
|
|
|
|
let cur = data.offset, value;
|
|
try {
|
|
value = entry.coder.decode(data, options);
|
|
} catch (e) {
|
|
data.offset = cur;
|
|
|
|
value = WSP.skipValue(data);
|
|
debug("Skip malformed well known header: "
|
|
+ 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.
|
|
*
|
|
* @throws CodeError if got an empty header name.
|
|
* @throws NotWellKnownEncodingError if the well-known header field number is
|
|
* not registered or supported.
|
|
*/
|
|
encode: function(data, header) {
|
|
if (!header.name) {
|
|
throw new WSP.CodeError("MMS-header: empty header name");
|
|
}
|
|
|
|
let entry = MMS_HEADER_FIELDS[header.name.toLowerCase()];
|
|
if (!entry) {
|
|
throw new WSP.NotWellKnownEncodingError(
|
|
"MMS-header: not well known header " + header.name);
|
|
}
|
|
|
|
WSP.ShortInteger.encode(data, entry.number);
|
|
entry.coder.encode(data, header.value);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Cancel-status-value = Cancel Request Successfully received |
|
|
* Cancel Request corrupted
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.7
|
|
*/
|
|
this.CancelStatusValue = new RangedValue("Cancel-status-value", 128, 129);
|
|
|
|
/**
|
|
* Content-class-value = text | image-basic| image-rich | video-basic |
|
|
* video-rich | megapixel | content-basic | content-rich
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9
|
|
*/
|
|
this.ContentClassValue = new RangedValue("Content-class-value", 128, 135);
|
|
|
|
/**
|
|
* When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
|
|
*
|
|
* Content-location-value = Uri-value
|
|
*
|
|
* When used in the M-Mbox-Delete.conf and M-Delete.conf PDU:
|
|
*
|
|
* Content-location-Del-value = Value-length Status-count-value Content-location-value
|
|
* Status-count-value = Integer-value
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.10
|
|
*/
|
|
this.ContentLocationValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param options
|
|
* Extra context for decoding.
|
|
*
|
|
* @return A decoded object containing `uri` and conditional `statusCount`
|
|
* properties.
|
|
*/
|
|
decode: function(data, options) {
|
|
let type = WSP.ensureHeader(options, "x-mms-message-type");
|
|
|
|
let result = {};
|
|
if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF)
|
|
|| (type == MMS_PDU_TYPE_DELETE_CONF)) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
result.statusCount = WSP.IntegerValue.decode(data);
|
|
result.uri = WSP.UriValue.decode(data);
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
} else {
|
|
result.uri = WSP.UriValue.decode(data);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Element-Descriptor-value = Value-length Content-Reference-value *(Parameter)
|
|
* Content-Reference-value = Text-string
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
|
|
*/
|
|
this.ElementDescriptorValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded object containing a string property `contentReference`
|
|
* and an optinal `params` name-value map.
|
|
*/
|
|
decode: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let result = {};
|
|
result.contentReference = WSP.TextString.decode(data);
|
|
if (data.offset < end) {
|
|
result.params = Parameter.decodeMultiple(data, end);
|
|
}
|
|
|
|
if (data.offset != end) {
|
|
// Explicitly seek to end in case of skipped parameters.
|
|
data.offset = end;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18:
|
|
* `For well-known parameter names binary tokens MUST be used as defined in
|
|
* Table 27.` So we can't reuse that of WSP.
|
|
*
|
|
* Parameter = Parameter-name Parameter-value
|
|
* Parameter-name = Short-integer | Text-string
|
|
* Parameter-value = Constrained-encoding | Text-string
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
|
|
*/
|
|
this.Parameter = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded string.
|
|
*
|
|
* @throws NotWellKnownEncodingError if decoded well-known parameter number
|
|
* is not registered or supported.
|
|
*/
|
|
decodeParameterName: function(data) {
|
|
let begin = data.offset;
|
|
let number;
|
|
try {
|
|
number = WSP.ShortInteger.decode(data);
|
|
} catch (e) {
|
|
data.offset = begin;
|
|
return WSP.TextString.decode(data).toLowerCase();
|
|
}
|
|
|
|
let entry = MMS_WELL_KNOWN_PARAMS[number];
|
|
if (!entry) {
|
|
throw new WSP.NotWellKnownEncodingError(
|
|
"Parameter-name: not well known parameter " + number);
|
|
}
|
|
|
|
return entry.name;
|
|
},
|
|
|
|
/**
|
|
* @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 = this.decodeParameterName(data);
|
|
let value = WSP.decodeAlternatives(data, null,
|
|
WSP.ConstrainedEncoding, WSP.TextString);
|
|
return {
|
|
name: name,
|
|
value: value,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @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, 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 two attributes: `name` and `value`.
|
|
* @param options
|
|
* Extra context for encoding.
|
|
*/
|
|
encode: function(data, param, options) {
|
|
if (!param || !param.name) {
|
|
throw new WSP.CodeError("Parameter-name: empty param name");
|
|
}
|
|
|
|
let entry = MMS_WELL_KNOWN_PARAMS[param.name.toLowerCase()];
|
|
if (entry) {
|
|
WSP.ShortInteger.encode(data, entry.number);
|
|
} else {
|
|
WSP.TextString.encode(data, param.name);
|
|
}
|
|
|
|
WSP.encodeAlternatives(data, param.value, options,
|
|
WSP.ConstrainedEncoding, WSP.TextString);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* The Char-set values are registered by IANA as MIBEnum value and SHALL be
|
|
* encoded as Integer-value.
|
|
*
|
|
* Encoded-string-value = Text-string | Value-length Char-set Text-string
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19
|
|
* @see OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
|
|
*/
|
|
this.EncodedStringValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return Decoded string.
|
|
*
|
|
* @throws CodeError if the raw octets cannot be converted.
|
|
* @throws NotWellKnownEncodingError if decoded well-known charset number is
|
|
* not registered or supported.
|
|
*/
|
|
decodeCharsetEncodedString: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let charset = WSP.IntegerValue.decode(data);
|
|
let entry = WSP.WSP_WELL_KNOWN_CHARSETS[charset];
|
|
if (!entry) {
|
|
throw new WSP.NotWellKnownEncodingError(
|
|
"Charset-encoded-string: not well known charset " + charset);
|
|
}
|
|
|
|
let str;
|
|
if (entry.converter) {
|
|
// Read a possible string quote(<Octet 127>).
|
|
let begin = data.offset;
|
|
if (WSP.Octet.decode(data) != 127) {
|
|
data.offset = begin;
|
|
}
|
|
|
|
let raw = WSP.Octet.decodeMultiple(data, end - 1);
|
|
// Read NUL character.
|
|
WSP.Octet.decodeEqualTo(data, 0);
|
|
|
|
if (!raw) {
|
|
str = "";
|
|
} else {
|
|
let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
conv.charset = entry.converter;
|
|
try {
|
|
str = conv.convertFromByteArray(raw, raw.length);
|
|
} catch (e) {
|
|
throw new WSP.CodeError("Charset-encoded-string: " + e.message);
|
|
}
|
|
}
|
|
} else {
|
|
str = WSP.TextString.decode(data);
|
|
}
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
|
|
return str;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return Decoded string.
|
|
*/
|
|
decode: function(data) {
|
|
let begin = data.offset;
|
|
try {
|
|
return WSP.TextString.decode(data);
|
|
} catch (e) {
|
|
data.offset = begin;
|
|
return this.decodeCharsetEncodedString(data);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Always encode target string with UTF-8 encoding.
|
|
*
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param str
|
|
* A string.
|
|
*/
|
|
encodeCharsetEncodedString: function(data, str) {
|
|
let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
// `When the text string cannot be represented as us-ascii, the character
|
|
// set SHALL be encoded as utf-8(IANA MIBenum 106) which has unique byte
|
|
// ordering.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
|
|
conv.charset = "UTF-8";
|
|
|
|
let raw;
|
|
try {
|
|
raw = conv.convertToByteArray(str);
|
|
} catch (e) {
|
|
throw new WSP.CodeError("Charset-encoded-string: " + e.message);
|
|
}
|
|
|
|
let length = raw.length + 2; // Charset number and NUL character
|
|
// Prepend <Octet 127> if necessary.
|
|
if (raw[0] >= 128) {
|
|
++length;
|
|
}
|
|
|
|
WSP.ValueLength.encode(data, length);
|
|
|
|
let entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"];
|
|
WSP.IntegerValue.encode(data, entry.number);
|
|
|
|
if (raw[0] >= 128) {
|
|
WSP.Octet.encode(data, 127);
|
|
}
|
|
WSP.Octet.encodeMultiple(data, raw);
|
|
WSP.Octet.encode(data, 0);
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param str
|
|
* A string.
|
|
*/
|
|
encode: function(data, str) {
|
|
let begin = data.offset;
|
|
try {
|
|
// Quoted from OMA-TS-MMS-CONF-V1_3-20110913-A:
|
|
// Some of the MMS headers have been defined as "Encoded-string-value".
|
|
// The character set IANA MIBEnum value in these headers SHALL be
|
|
// encoded as Integer-value ([WAPWSP] section 8.4.2.3). The character
|
|
// set us-ascii (IANA MIBenum 3) SHALL always be accepted. If the
|
|
// character set is not specified (simple Text-string encoding) the
|
|
// character set SHALL be identified as us-ascii (lower half of ISO
|
|
// 8859-1 [ISO8859-1]). When the text string cannot be represented as
|
|
// us-ascii, the character set SHALL be encoded as utf-8 (IANA MIBenum
|
|
// 106) which has unique byte ordering.
|
|
WSP.TextString.encode(data, str, true);
|
|
} catch (e) {
|
|
data.offset = begin;
|
|
this.encodeCharsetEncodedString(data, str);
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value)
|
|
* Absolute-token = <Octet 128>
|
|
* Relative-token = <Octet 129>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.20
|
|
*/
|
|
this.ExpiryValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A Date object for absolute expiry or an integer for relative one.
|
|
*
|
|
* @throws CodeError if decoded token equals to neither 128 nor 129.
|
|
*/
|
|
decode: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let token = WSP.Octet.decode(data);
|
|
if ((token != 128) && (token != 129)) {
|
|
throw new WSP.CodeError("Expiry-value: invalid token " + token);
|
|
}
|
|
|
|
let result;
|
|
if (token == 128) {
|
|
result = WSP.DateValue.decode(data);
|
|
} else {
|
|
result = WSP.DeltaSecondsValue.decode(data);
|
|
}
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param value
|
|
* A Date object for absolute expiry or an integer for relative one.
|
|
*/
|
|
encode: function(data, value) {
|
|
let isDate, begin = data.offset;
|
|
if (value instanceof Date) {
|
|
isDate = true;
|
|
WSP.DateValue.encode(data, value);
|
|
} else if (typeof value == "number") {
|
|
isDate = false;
|
|
WSP.DeltaSecondsValue.encode(data, value);
|
|
} else {
|
|
throw new CodeError("Expiry-value: invalid value type");
|
|
}
|
|
|
|
// Calculate how much octets will be written and seek back.
|
|
// TODO: use memmove, see bug 730873
|
|
let len = data.offset - begin;
|
|
data.offset = begin;
|
|
|
|
WSP.ValueLength.encode(data, len + 1);
|
|
if (isDate) {
|
|
WSP.Octet.encode(data, 128);
|
|
WSP.DateValue.encode(data, value);
|
|
} else {
|
|
WSP.Octet.encode(data, 129);
|
|
WSP.DeltaSecondsValue.encode(data, value);
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* From-value = Value-length (Address-present-token Address | Insert-address-token)
|
|
* Address-present-token = <Octet 128>
|
|
* Insert-address-token = <Octet 129>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.21
|
|
*/
|
|
this.FromValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded Address-value or null for MMS Proxy-Relay Insert-Address
|
|
* mode.
|
|
*
|
|
* @throws CodeError if decoded token equals to neither 128 nor 129.
|
|
*/
|
|
decode: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let token = WSP.Octet.decode(data);
|
|
if ((token != 128) && (token != 129)) {
|
|
throw new WSP.CodeError("From-value: invalid token " + token);
|
|
}
|
|
|
|
let result = null;
|
|
if (token == 128) {
|
|
result = Address.decode(data);
|
|
}
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param value
|
|
* A Address-value or null for MMS Proxy-Relay Insert-Address mode.
|
|
*/
|
|
encode: function(data, value) {
|
|
if (!value) {
|
|
WSP.ValueLength.encode(data, 1);
|
|
WSP.Octet.encode(data, 129);
|
|
return;
|
|
}
|
|
|
|
// Calculate how much octets will be written and seek back.
|
|
// TODO: use memmove, see bug 730873
|
|
let begin = data.offset;
|
|
Address.encode(data, value);
|
|
let len = data.offset - begin;
|
|
data.offset = begin;
|
|
|
|
WSP.ValueLength.encode(data, len + 1);
|
|
WSP.Octet.encode(data, 128);
|
|
Address.encode(data, value);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Previously-sent-by-value = Value-length Forwarded-count-value Address
|
|
* Forwarded-count-value = Integer-value
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
|
|
*/
|
|
this.PreviouslySentByValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return Decoded object containing an integer `forwardedCount` and an
|
|
* string-typed `originator` attributes.
|
|
*/
|
|
decode: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let result = {};
|
|
result.forwardedCount = WSP.IntegerValue.decode(data);
|
|
result.originator = Address.decode(data);
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Previously-sent-date-value = Value-length Forwarded-count-value Date-value
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.24
|
|
*/
|
|
this.PreviouslySentDateValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return Decoded object containing an integer `forwardedCount` and an
|
|
* Date-typed `timestamp` attributes.
|
|
*/
|
|
decode: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let result = {};
|
|
result.forwardedCount = WSP.IntegerValue.decode(data);
|
|
result.timestamp = WSP.DateValue.decode(data);
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Message-class-value = Class-identifier | Token-text
|
|
* Class-identifier = Personal | Advertisement | Informational | Auto
|
|
* Personal = <Octet 128>
|
|
* Advertisement = <Octet 129>
|
|
* Informational = <Octet 130>
|
|
* Auto = <Octet 131>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27
|
|
*/
|
|
this.MessageClassValue = {
|
|
WELL_KNOWN_CLASSES: ["personal", "advertisement", "informational", "auto"],
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded string.
|
|
*
|
|
* @throws CodeError if decoded value is not in the range 128..131.
|
|
*/
|
|
decodeClassIdentifier: function(data) {
|
|
let value = WSP.Octet.decode(data);
|
|
if ((value >= 128) && (value < (128 + this.WELL_KNOWN_CLASSES.length))) {
|
|
return this.WELL_KNOWN_CLASSES[value - 128];
|
|
}
|
|
|
|
throw new WSP.CodeError("Class-identifier: invalid id " + value);
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded string.
|
|
*/
|
|
decode: function(data) {
|
|
let begin = data.offset;
|
|
try {
|
|
return this.decodeClassIdentifier(data);
|
|
} catch (e) {
|
|
data.offset = begin;
|
|
return WSP.TokenText.decode(data);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param klass
|
|
*/
|
|
encode: function(data, klass) {
|
|
let index = this.WELL_KNOWN_CLASSES.indexOf(klass.toLowerCase());
|
|
if (index >= 0) {
|
|
WSP.Octet.encode(data, index + 128);
|
|
} else {
|
|
WSP.TokenText.encode(data, klass);
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Message-type-value = <Octet 128..151>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
|
|
*/
|
|
this.MessageTypeValue = new RangedValue("Message-type-value", 128, 151);
|
|
|
|
/**
|
|
* MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value
|
|
* Add-token = <Octet 128>
|
|
* Remove-token = <Octet 129>
|
|
* Filter-token = <Octet 130>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32
|
|
*/
|
|
this.MmFlagsValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return Decoded object containing an integer `type` and an string-typed
|
|
* `text` attributes.
|
|
*
|
|
* @throws CodeError if decoded value is not in the range 128..130.
|
|
*/
|
|
decode: function(data) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
let result = {};
|
|
result.type = WSP.Octet.decode(data);
|
|
if ((result.type < 128) || (result.type > 130)) {
|
|
throw new WSP.CodeError("MM-flags-value: invalid type " + result.type);
|
|
}
|
|
result.text = EncodedStringValue.decode(data);
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param value
|
|
* An object containing an integer `type` and an string-typed
|
|
* `text` attributes.
|
|
*/
|
|
encode: function(data, value) {
|
|
if ((value.type < 128) || (value.type > 130)) {
|
|
throw new WSP.CodeError("MM-flags-value: invalid type " + value.type);
|
|
}
|
|
|
|
// Calculate how much octets will be written and seek back.
|
|
// TODO: use memmove, see bug 730873
|
|
let begin = data.offset;
|
|
EncodedStringValue.encode(data, value.text);
|
|
let len = data.offset - begin;
|
|
data.offset = begin;
|
|
|
|
WSP.ValueLength.encode(data, len + 1);
|
|
WSP.Octet.encode(data, value.type);
|
|
EncodedStringValue.encode(data, value.text);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* MM-state-value = Draft | Sent | New | Retrieved | Forwarded
|
|
* Draft = <Octet 128>
|
|
* Sent = <Octet 129>
|
|
* New = <Octet 130>
|
|
* Retrieved = <Octet 131>
|
|
* Forwarded = <Octet 132>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33
|
|
*/
|
|
this.MmStateValue = new RangedValue("MM-state-value", 128, 132);
|
|
|
|
/**
|
|
* Priority-value = Low | Normal | High
|
|
* Low = <Octet 128>
|
|
* Normal = <Octet 129>
|
|
* High = <Octet 130>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35
|
|
*/
|
|
this.PriorityValue = new RangedValue("Priority-value", 128, 130);
|
|
|
|
/**
|
|
* Read-status-value = Read | Deleted without being read
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.38
|
|
*/
|
|
this.ReadStatusValue = new RangedValue("Read-status-value", 128, 129);
|
|
|
|
/**
|
|
* Recommended-Retrieval-Mode-value = Manual
|
|
* Manual = <Octet 128>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39
|
|
*/
|
|
this.RecommendedRetrievalModeValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded integer.
|
|
*/
|
|
decode: function(data) {
|
|
return WSP.Octet.decodeEqualTo(data, 128);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Reply-charging-value = Requested | Requested text only | Accepted |
|
|
* Accepted text only
|
|
* Requested = <Octet 128>
|
|
* Requested text only = <Octet 129>
|
|
* Accepted = <Octet 130>
|
|
* Accepted text only = <Octet 131>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43
|
|
*/
|
|
this.ReplyChargingValue = new RangedValue("Reply-charging-value", 128, 131);
|
|
|
|
/**
|
|
* When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
|
|
*
|
|
* Response-text-value = Encoded-string-value
|
|
*
|
|
* When used in the M-Mbox-Delete.conf and M-Delete.conf PDUs:
|
|
*
|
|
* Response-text-Del-value = Value-length Status-count-value Response-text-value
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.49
|
|
*/
|
|
this.ResponseText = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param options
|
|
* Extra context for decoding.
|
|
*
|
|
* @return An object containing a string-typed `text` attribute and a
|
|
* integer-typed `statusCount` one.
|
|
*/
|
|
decode: function(data, options) {
|
|
let type = WSP.ensureHeader(options, "x-mms-message-type");
|
|
|
|
let result = {};
|
|
if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF)
|
|
|| (type == MMS_PDU_TYPE_DELETE_CONF)) {
|
|
let length = WSP.ValueLength.decode(data);
|
|
let end = data.offset + length;
|
|
|
|
result.statusCount = WSP.IntegerValue.decode(data);
|
|
result.text = EncodedStringValue.decode(data);
|
|
|
|
if (data.offset != end) {
|
|
data.offset = end;
|
|
}
|
|
} else {
|
|
result.text = EncodedStringValue.decode(data);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Retrieve-status-value = Ok | Error-transient-failure |
|
|
* Error-transient-message-not-found |
|
|
* Error-transient-network-problem |
|
|
* Error-permanent-failure |
|
|
* Error-permanent-service-denied |
|
|
* Error-permanent-message-not-found |
|
|
* Error-permanent-content-unsupported
|
|
* Ok = <Octet 128>
|
|
* Error-transient-failure = <Octet 192>
|
|
* Error-transient-message-not-found = <Octet 193>
|
|
* Error-transient-network-problem = <Octet 194>
|
|
* Error-permanent-failure = <Octet 224>
|
|
* Error-permanent-service-denied = <Octet 225>
|
|
* Error-permanent-message-not-found = <Octet 226>
|
|
* Error-permanent-content-unsupported = <Octet 227>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50
|
|
*/
|
|
this.RetrieveStatusValue = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
*
|
|
* @return A decoded integer.
|
|
*/
|
|
decode: function(data) {
|
|
let value = WSP.Octet.decode(data);
|
|
if (value == MMS_PDU_ERROR_OK) {
|
|
return value;
|
|
}
|
|
|
|
if ((value >= MMS_PDU_ERROR_TRANSIENT_FAILURE) && (value < 256)) {
|
|
return value;
|
|
}
|
|
|
|
// Any other values SHALL NOT be used. They are reserved for future use.
|
|
// An MMS Client that receives such a reserved value MUST react the same
|
|
// as it does to the value 224 (Error-permanent-failure).
|
|
return MMS_PDU_ERROR_PERMANENT_FAILURE;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Sender-visibility-value = Hide | Show
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.52
|
|
*/
|
|
this.SenderVisibilityValue = new RangedValue("Sender-visibility-value", 128, 129);
|
|
|
|
/**
|
|
* Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised |
|
|
* Indeterminate | Forwarded | Unreachable
|
|
* Expired = <Octet 128>
|
|
* Retrieved = <Octet 129>
|
|
* Rejected = <Octet 130>
|
|
* Deferred = <Octet 131>
|
|
* Unrecognised = <Octet 132>
|
|
* Indeterminate = <Octet 133>
|
|
* Forwarded = <Octet 134>
|
|
* Unreachable = <Octet 135>
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54
|
|
*/
|
|
this.StatusValue = new RangedValue("Status-value", 128, 135);
|
|
|
|
this.PduHelper = {
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param headers
|
|
* An optional object to store parsed header fields. Created
|
|
* automatically if undefined.
|
|
*
|
|
* @return A boolean value indicating whether it's followed by message body.
|
|
*/
|
|
parseHeaders: function(data, headers) {
|
|
if (!headers) {
|
|
headers = {};
|
|
}
|
|
|
|
let header;
|
|
while (data.offset < data.array.length) {
|
|
// There is no `header length` information in MMS PDU. If we just got
|
|
// something wrong in parsing header fields, we might not be able to
|
|
// determine the correct header-content boundary.
|
|
header = HeaderField.decode(data, headers);
|
|
|
|
if (header) {
|
|
let orig = headers[header.name];
|
|
if (Array.isArray(orig)) {
|
|
headers[header.name].push(header.value);
|
|
} else if (orig) {
|
|
headers[header.name] = [orig, header.value];
|
|
} else {
|
|
headers[header.name] = header.value;
|
|
}
|
|
if (header.name == "content-type") {
|
|
// `... if the PDU contains a message body the Content Type MUST be
|
|
// the last header field, followed by message body.` See
|
|
// OMA-TS-MMS_ENC-V1_3-20110913-A section 7.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return headers;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param msg
|
|
* A message object to store decoded multipart or octet array content.
|
|
*/
|
|
parseContent: function(data, msg) {
|
|
let contentType = msg.headers["content-type"].media;
|
|
if ((contentType == "application/vnd.wap.multipart.related")
|
|
|| (contentType == "application/vnd.wap.multipart.mixed")) {
|
|
msg.parts = WSP.PduHelper.parseMultiPart(data);
|
|
return;
|
|
}
|
|
|
|
if (data.offset >= data.array.length) {
|
|
return;
|
|
}
|
|
|
|
msg.content = WSP.Octet.decodeMultiple(data, data.array.length);
|
|
if (false) {
|
|
for (let begin = 0; begin < msg.content.length; begin += 20) {
|
|
debug("content: " + JSON.stringify(msg.content.subarray(begin, begin + 20)));
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check existences of all mandatory fields of a MMS message. Also sets `type`
|
|
* for convenient access.
|
|
*
|
|
* @param msg
|
|
* A MMS message object.
|
|
*
|
|
* @return The corresponding entry in MMS_PDU_TYPES;
|
|
*
|
|
* @throws FatalCodeError if the PDU type is not supported yet.
|
|
*/
|
|
checkMandatoryFields: function(msg) {
|
|
let type = WSP.ensureHeader(msg.headers, "x-mms-message-type");
|
|
let entry = MMS_PDU_TYPES[type];
|
|
if (!entry) {
|
|
throw new WSP.FatalCodeError(
|
|
"checkMandatoryFields: unsupported message type " + type);
|
|
}
|
|
|
|
entry.mandatoryFields.forEach(function(name) {
|
|
WSP.ensureHeader(msg.headers, name);
|
|
});
|
|
|
|
// Setup convenient alias that referenced frequently.
|
|
msg.type = type;
|
|
|
|
return entry;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object containing raw PDU data.
|
|
* @param msg [optional]
|
|
* Optional target object for decoding.
|
|
*
|
|
* @return A MMS message object or null in case of errors found.
|
|
*/
|
|
parse: function(data, msg) {
|
|
if (!msg) {
|
|
msg = {};
|
|
}
|
|
|
|
try {
|
|
msg.headers = this.parseHeaders(data, msg.headers);
|
|
|
|
// Validity checks
|
|
let typeinfo = this.checkMandatoryFields(msg);
|
|
if (typeinfo.hasContent) {
|
|
this.parseContent(data, msg);
|
|
}
|
|
} catch (e) {
|
|
debug("Failed to parse MMS message, error message: " + e.message);
|
|
return null;
|
|
}
|
|
|
|
return msg;
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param headers
|
|
* A dictionary object containing multiple name/value mapping.
|
|
* @param name
|
|
* Name of the header field to be encoded.
|
|
*/
|
|
encodeHeader: function(data, headers, name) {
|
|
let value = headers[name];
|
|
if (Array.isArray(value)) {
|
|
for (let i = 0; i < value.length; i++) {
|
|
HeaderField.encode(data, {name: name, value: value[i]}, headers);
|
|
}
|
|
} else {
|
|
HeaderField.encode(data, {name: name, value: value}, headers);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param data
|
|
* A wrapped object to store encoded raw data.
|
|
* @param headers
|
|
* A dictionary object containing multiple name/value mapping.
|
|
*/
|
|
encodeHeaderIfExists: function(data, headers, name) {
|
|
// Header value could be zero or null.
|
|
if (headers[name] !== undefined) {
|
|
this.encodeHeader(data, headers, name);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param data [optional]
|
|
* A wrapped object to store encoded raw data. Created if undefined.
|
|
* @param headers
|
|
* A dictionary object containing multiple name/value mapping.
|
|
*
|
|
* @return the passed data parameter or a created one.
|
|
*/
|
|
encodeHeaders: function(data, headers) {
|
|
if (!data) {
|
|
data = {array: [], offset: 0};
|
|
}
|
|
|
|
// `In the encoding of the header fields, the order of the fields is not
|
|
// significant, except that X-Mms-Message-Type, X-Mms-Transaction-ID (when
|
|
// present) and X-Mms-MMS-Version MUST be at the beginning of the message
|
|
// headers, in that order, and if the PDU contains a message body the
|
|
// Content Type MUST be the last header field, followed by message body.`
|
|
// ~ OMA-TS-MMS_ENC-V1_3-20110913-A section 7
|
|
this.encodeHeader(data, headers, "x-mms-message-type");
|
|
this.encodeHeaderIfExists(data, headers, "x-mms-transaction-id");
|
|
this.encodeHeaderIfExists(data, headers, "x-mms-mms-version");
|
|
|
|
for (let key in headers) {
|
|
if ((key == "x-mms-message-type")
|
|
|| (key == "x-mms-transaction-id")
|
|
|| (key == "x-mms-mms-version")
|
|
|| (key == "content-type")) {
|
|
continue;
|
|
}
|
|
this.encodeHeader(data, headers, key);
|
|
}
|
|
|
|
this.encodeHeaderIfExists(data, headers, "content-type");
|
|
|
|
return data;
|
|
},
|
|
|
|
/**
|
|
* @param multiStream
|
|
* An exsiting nsIMultiplexInputStream.
|
|
* @param msg
|
|
* A MMS message object.
|
|
*
|
|
* @return An instance of nsIMultiplexInputStream or null in case of errors.
|
|
*/
|
|
compose: function(multiStream, msg) {
|
|
if (!multiStream) {
|
|
multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"]
|
|
.createInstance(Ci.nsIMultiplexInputStream);
|
|
}
|
|
|
|
try {
|
|
// Validity checks
|
|
let typeinfo = this.checkMandatoryFields(msg);
|
|
|
|
let data = this.encodeHeaders(null, msg.headers);
|
|
debug("Composed PDU Header: " + JSON.stringify(data.array));
|
|
WSP.PduHelper.appendArrayToMultiStream(multiStream, data.array, data.offset);
|
|
|
|
if (msg.content) {
|
|
WSP.PduHelper.appendArrayToMultiStream(multiStream, msg.content, msg.content.length);
|
|
} else if (msg.parts) {
|
|
WSP.PduHelper.composeMultiPart(multiStream, msg.parts);
|
|
} else if (typeinfo.hasContent) {
|
|
throw new WSP.CodeError("Missing message content");
|
|
}
|
|
|
|
return multiStream;
|
|
} catch (e) {
|
|
debug("Failed to compose MMS message, error message: " + e.message);
|
|
return null;
|
|
}
|
|
},
|
|
};
|
|
|
|
const MMS_PDU_TYPES = (function() {
|
|
let pdus = {};
|
|
function add(number, hasContent, mandatoryFields) {
|
|
pdus[number] = {
|
|
number: number,
|
|
hasContent: hasContent,
|
|
mandatoryFields: mandatoryFields,
|
|
};
|
|
}
|
|
|
|
add(MMS_PDU_TYPE_SEND_REQ, true, ["x-mms-message-type",
|
|
"x-mms-transaction-id",
|
|
"x-mms-mms-version",
|
|
"from",
|
|
"content-type"]);
|
|
add(MMS_PDU_TYPE_SEND_CONF, false, ["x-mms-message-type",
|
|
"x-mms-transaction-id",
|
|
"x-mms-mms-version",
|
|
"x-mms-response-status"]);
|
|
add(MMS_PDU_TYPE_NOTIFICATION_IND, false, ["x-mms-message-type",
|
|
"x-mms-transaction-id",
|
|
"x-mms-mms-version",
|
|
"x-mms-message-class",
|
|
"x-mms-message-size",
|
|
"x-mms-expiry",
|
|
"x-mms-content-location"]);
|
|
add(MMS_PDU_TYPE_RETRIEVE_CONF, true, ["x-mms-message-type",
|
|
"x-mms-mms-version",
|
|
"date",
|
|
"content-type"]);
|
|
add(MMS_PDU_TYPE_NOTIFYRESP_IND, false, ["x-mms-message-type",
|
|
"x-mms-transaction-id",
|
|
"x-mms-mms-version",
|
|
"x-mms-status"]);
|
|
add(MMS_PDU_TYPE_DELIVERY_IND, false, ["x-mms-message-type",
|
|
"x-mms-mms-version",
|
|
"message-id",
|
|
"to",
|
|
"date",
|
|
"x-mms-status"]);
|
|
add(MMS_PDU_TYPE_ACKNOWLEDGE_IND, false, ["x-mms-message-type",
|
|
"x-mms-transaction-id",
|
|
"x-mms-mms-version"]);
|
|
add(MMS_PDU_TYPE_READ_REC_IND, false, ["x-mms-message-type",
|
|
"message-id",
|
|
"x-mms-mms-version",
|
|
"to",
|
|
"from",
|
|
"x-mms-read-status"]);
|
|
add(MMS_PDU_TYPE_READ_ORIG_IND, false, ["x-mms-message-type",
|
|
"x-mms-mms-version",
|
|
"message-id",
|
|
"to",
|
|
"from",
|
|
"date",
|
|
"x-mms-read-status"]);
|
|
|
|
return pdus;
|
|
})();
|
|
|
|
/**
|
|
* Header field names and assigned numbers.
|
|
*
|
|
* @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.4
|
|
*/
|
|
const MMS_HEADER_FIELDS = (function() {
|
|
let names = {};
|
|
function add(name, number, coder) {
|
|
let entry = {
|
|
name: name,
|
|
number: number,
|
|
coder: coder,
|
|
};
|
|
names[name] = names[number] = entry;
|
|
}
|
|
|
|
add("bcc", 0x01, Address);
|
|
add("cc", 0x02, Address);
|
|
add("x-mms-content-location", 0x03, ContentLocationValue);
|
|
add("content-type", 0x04, WSP.ContentTypeValue);
|
|
add("date", 0x05, WSP.DateValue);
|
|
add("x-mms-delivery-report", 0x06, BooleanValue);
|
|
add("x-mms-delivery-time", 0x07, ExpiryValue);
|
|
add("x-mms-expiry", 0x08, ExpiryValue);
|
|
add("from", 0x09, FromValue);
|
|
add("x-mms-message-class", 0x0A, MessageClassValue);
|
|
add("message-id", 0x0B, WSP.TextString);
|
|
add("x-mms-message-type", 0x0C, MessageTypeValue);
|
|
add("x-mms-mms-version", 0x0D, WSP.ShortInteger);
|
|
add("x-mms-message-size", 0x0E, WSP.LongInteger);
|
|
add("x-mms-priority", 0x0F, PriorityValue);
|
|
add("x-mms-read-report", 0x10, BooleanValue);
|
|
add("x-mms-report-allowed", 0x11, BooleanValue);
|
|
add("x-mms-response-status", 0x12, RetrieveStatusValue);
|
|
add("x-mms-response-text", 0x13, ResponseText);
|
|
add("x-mms-sender-visibility", 0x14, SenderVisibilityValue);
|
|
add("x-mms-status", 0x15, StatusValue);
|
|
add("subject", 0x16, EncodedStringValue);
|
|
add("to", 0x17, Address);
|
|
add("x-mms-transaction-id", 0x18, WSP.TextString);
|
|
add("x-mms-retrieve-status", 0x19, RetrieveStatusValue);
|
|
add("x-mms-retrieve-text", 0x1A, EncodedStringValue);
|
|
add("x-mms-read-status", 0x1B, ReadStatusValue);
|
|
add("x-mms-reply-charging", 0x1C, ReplyChargingValue);
|
|
add("x-mms-reply-charging-deadline", 0x1D, ExpiryValue);
|
|
add("x-mms-reply-charging-id", 0x1E, WSP.TextString);
|
|
add("x-mms-reply-charging-size", 0x1F, WSP.LongInteger);
|
|
add("x-mms-previously-sent-by", 0x20, PreviouslySentByValue);
|
|
add("x-mms-previously-sent-date", 0x21, PreviouslySentDateValue);
|
|
add("x-mms-store", 0x22, BooleanValue);
|
|
add("x-mms-mm-state", 0x23, MmStateValue);
|
|
add("x-mms-mm-flags", 0x24, MmFlagsValue);
|
|
add("x-mms-store-status", 0x25, RetrieveStatusValue);
|
|
add("x-mms-store-status-text", 0x26, EncodedStringValue);
|
|
add("x-mms-stored", 0x27, BooleanValue);
|
|
//add("x-mms-attributes", 0x28);
|
|
add("x-mms-totals", 0x29, BooleanValue);
|
|
//add("x-mms-mbox-totals", 0x2A);
|
|
add("x-mms-quotas", 0x2B, BooleanValue);
|
|
//add("x-mms-mbox-quotas", 0x2C);
|
|
add("x-mms-message-count", 0x2D, WSP.IntegerValue);
|
|
//add("content", 0x2E);
|
|
add("x-mms-start", 0x2F, WSP.IntegerValue);
|
|
//add("additional-headers", 0x30);
|
|
add("x-mms-distribution-indicator", 0x31, BooleanValue);
|
|
add("x-mms-element-descriptor", 0x32, ElementDescriptorValue);
|
|
add("x-mms-limit", 0x33, WSP.IntegerValue);
|
|
add("x-mms-recommended-retrieval-mode", 0x34, RecommendedRetrievalModeValue);
|
|
add("x-mms-recommended-retrieval-mode-text", 0x35, EncodedStringValue);
|
|
//add("x-mms-status-text", 0x36);
|
|
add("x-mms-applic-id", 0x37, WSP.TextString);
|
|
add("x-mms-reply-applic-id", 0x38, WSP.TextString);
|
|
add("x-mms-aux-applic-id", 0x39, WSP.TextString);
|
|
add("x-mms-content-class", 0x3A, ContentClassValue);
|
|
add("x-mms-drm-content", 0x3B, BooleanValue);
|
|
add("x-mms-adaptation-allowed", 0x3C, BooleanValue);
|
|
add("x-mms-replace-id", 0x3D, WSP.TextString);
|
|
add("x-mms-cancel-id", 0x3E, WSP.TextString);
|
|
add("x-mms-cancel-status", 0x3F, CancelStatusValue);
|
|
|
|
return names;
|
|
})();
|
|
|
|
// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments
|
|
const MMS_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.2
|
|
add("type", 0x02, WSP.TypeValue);
|
|
|
|
return params;
|
|
})();
|
|
|
|
var debug;
|
|
if (DEBUG) {
|
|
debug = function(s) {
|
|
dump("-$- MmsPduHelper: " + s + "\n");
|
|
};
|
|
} else {
|
|
debug = function(s) {};
|
|
}
|
|
|
|
this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
|
|
// Constant values
|
|
"MMS_VERSION",
|
|
|
|
// Utility functions
|
|
"translatePduErrorToStatus",
|
|
|
|
// Decoders
|
|
"BooleanValue",
|
|
"Address",
|
|
"HeaderField",
|
|
"MmsHeader",
|
|
"CancelStatusValue",
|
|
"ContentClassValue",
|
|
"ContentLocationValue",
|
|
"ElementDescriptorValue",
|
|
"Parameter",
|
|
"EncodedStringValue",
|
|
"ExpiryValue",
|
|
"FromValue",
|
|
"PreviouslySentByValue",
|
|
"PreviouslySentDateValue",
|
|
"MessageClassValue",
|
|
"MessageTypeValue",
|
|
"MmFlagsValue",
|
|
"MmStateValue",
|
|
"PriorityValue",
|
|
"ReadStatusValue",
|
|
"RecommendedRetrievalModeValue",
|
|
"ReplyChargingValue",
|
|
"ResponseText",
|
|
"RetrieveStatusValue",
|
|
"SenderVisibilityValue",
|
|
"StatusValue",
|
|
|
|
// Parser
|
|
"PduHelper",
|
|
]);
|
|
|