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