1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1677 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.11 + 1.12 +let WSP = {}; 1.13 +Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP); 1.14 + 1.15 +Cu.import("resource://gre/modules/mms_consts.js"); 1.16 + 1.17 +Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); 1.18 + 1.19 +let DEBUG; // set to true to see debug messages 1.20 + 1.21 +this.MMS_VERSION = (function() { 1.22 + Cu.import("resource://gre/modules/Services.jsm"); 1.23 + 1.24 + try { 1.25 + return Services.prefs.getIntPref("dom.mms.version"); 1.26 + } catch(ex) {} 1.27 + 1.28 + return MMS_VERSION_1_3; 1.29 +})(); 1.30 + 1.31 +this.translatePduErrorToStatus = function translatePduErrorToStatus(error) { 1.32 + if (error == MMS_PDU_ERROR_OK) { 1.33 + return MMS_PDU_STATUS_RETRIEVED; 1.34 + } 1.35 + 1.36 + if ((error >= MMS_PDU_ERROR_TRANSIENT_FAILURE) 1.37 + && (error < MMS_PDU_ERROR_PERMANENT_FAILURE)) { 1.38 + return MMS_PDU_STATUS_DEFERRED; 1.39 + } 1.40 + 1.41 + return MMS_PDU_STATUS_UNRECOGNISED; 1.42 +} 1.43 + 1.44 +function defineLazyRegExp(obj, name, pattern) { 1.45 + obj.__defineGetter__(name, function() { 1.46 + delete obj[name]; 1.47 + return obj[name] = new RegExp(pattern); 1.48 + }); 1.49 +} 1.50 + 1.51 +function RangedValue(name, min, max) { 1.52 + this.name = name; 1.53 + this.min = min; 1.54 + this.max = max; 1.55 +} 1.56 +RangedValue.prototype = { 1.57 + name: null, 1.58 + min: null, 1.59 + max: null, 1.60 + 1.61 + /** 1.62 + * @param data 1.63 + * A wrapped object containing raw PDU data. 1.64 + * 1.65 + * @return A decoded integer. 1.66 + * 1.67 + * @throws CodeError if decoded value is not in the range [this.min, this.max]. 1.68 + */ 1.69 + decode: function(data) { 1.70 + let value = WSP.Octet.decode(data); 1.71 + if ((value >= this.min) && (value <= this.max)) { 1.72 + return value; 1.73 + } 1.74 + 1.75 + throw new WSP.CodeError(this.name + ": invalid value " + value); 1.76 + }, 1.77 + 1.78 + /** 1.79 + * @param data 1.80 + * A wrapped object to store encoded raw data. 1.81 + * @param value 1.82 + * An integer value within thr range [this.min, this.max]. 1.83 + */ 1.84 + encode: function(data, value) { 1.85 + if ((value < this.min) || (value > this.max)) { 1.86 + throw new WSP.CodeError(this.name + ": invalid value " + value); 1.87 + } 1.88 + 1.89 + WSP.Octet.encode(data, value); 1.90 + }, 1.91 +}; 1.92 + 1.93 +/** 1.94 + * Internal decoding function for boolean values. 1.95 + * 1.96 + * Boolean-value = Yes | No 1.97 + * Yes = <Octet 128> 1.98 + * No = <Octet 129> 1.99 + */ 1.100 +this.BooleanValue = { 1.101 + /** 1.102 + * @param data 1.103 + * A wrapped object containing raw PDU data. 1.104 + * 1.105 + * @return Boolean true or false. 1.106 + * 1.107 + * @throws CodeError if read octet equals to neither 128 nor 129. 1.108 + */ 1.109 + decode: function(data) { 1.110 + let value = WSP.Octet.decode(data); 1.111 + if ((value != 128) && (value != 129)) { 1.112 + throw new WSP.CodeError("Boolean-value: invalid value " + value); 1.113 + } 1.114 + 1.115 + return value == 128; 1.116 + }, 1.117 + 1.118 + /** 1.119 + * @param data 1.120 + * A wrapped object to store encoded raw data. 1.121 + * @param value 1.122 + * A boolean value to be encoded. 1.123 + */ 1.124 + encode: function(data, value) { 1.125 + WSP.Octet.encode(data, value ? 128 : 129); 1.126 + }, 1.127 +}; 1.128 + 1.129 +/** 1.130 + * MMS Address 1.131 + * 1.132 + * address = email | device-address | alphanum-shortcode | num-shortcode 1.133 + * 1.134 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 8 1.135 + */ 1.136 +this.Address = { 1.137 + /** 1.138 + * @param data 1.139 + * A wrapped object to store encoded raw data. 1.140 + * 1.141 + * @return An object of two string-typed attributes: address and type. 1.142 + */ 1.143 + decode: function(data) { 1.144 + let str = EncodedStringValue.decode(data); 1.145 + 1.146 + let result; 1.147 + if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null) 1.148 + || ((result = str.match(this.REGEXP_DECODE_IPV4)) != null) 1.149 + || ((result = str.match(this.REGEXP_DECODE_IPV6)) != null) 1.150 + || ((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)) { 1.151 + return {address: result[1], type: result[2]}; 1.152 + } 1.153 + 1.154 + let type; 1.155 + if (str.match(this.REGEXP_NUM)) { 1.156 + type = "num"; 1.157 + } else if (str.match(this.REGEXP_ALPHANUM)) { 1.158 + type = "alphanum"; 1.159 + } else if (str.indexOf("@") > 0) { 1.160 + // E-mail should match the definition of `mailbox` as described in section 1.161 + // 3.4 of RFC2822, but excluding the obsolete definitions as indicated by 1.162 + // the "obs-" prefix. Here we match only a `@` character. 1.163 + type = "email"; 1.164 + } else { 1.165 + throw new WSP.CodeError("Address: invalid address"); 1.166 + } 1.167 + 1.168 + return {address: str, type: type}; 1.169 + }, 1.170 + 1.171 + /** 1.172 + * @param data 1.173 + * A wrapped object to store encoded raw data. 1.174 + * @param value 1.175 + * An object of two string-typed attributes: address and type. 1.176 + */ 1.177 + encode: function(data, value) { 1.178 + if (!value || !value.type || !value.address) { 1.179 + throw new WSP.CodeError("Address: invalid value"); 1.180 + } 1.181 + 1.182 + let str; 1.183 + switch (value.type) { 1.184 + case "email": 1.185 + if (value.address.indexOf("@") > 0) { 1.186 + str = value.address; 1.187 + } 1.188 + break; 1.189 + case "num": 1.190 + if (value.address.match(this.REGEXP_NUM)) { 1.191 + str = value.address; 1.192 + } 1.193 + break; 1.194 + case "alphanum": 1.195 + if (value.address.match(this.REGEXP_ALPHANUM)) { 1.196 + str = value.address; 1.197 + } 1.198 + break; 1.199 + case "IPv4": 1.200 + if (value.address.match(this.REGEXP_ENCODE_IPV4)) { 1.201 + str = value.address + "/TYPE=IPv4"; 1.202 + } 1.203 + break; 1.204 + case "IPv6": 1.205 + if (value.address.match(this.REGEXP_ENCODE_IPV6)) { 1.206 + str = value.address + "/TYPE=IPv6"; 1.207 + } 1.208 + break; 1.209 + case "PLMN": 1.210 + if (value.address.match(this.REGEXP_ENCODE_PLMN)) { 1.211 + str = value.address + "/TYPE=PLMN"; 1.212 + } 1.213 + break; 1.214 + default: 1.215 + if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE) 1.216 + && value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) { 1.217 + str = value.address + "/TYPE=" + value.type; 1.218 + } 1.219 + break; 1.220 + } 1.221 + 1.222 + if (!str) { 1.223 + throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value)); 1.224 + } 1.225 + 1.226 + EncodedStringValue.encode(data, str); 1.227 + }, 1.228 + 1.229 + /** 1.230 + * @param address 1.231 + * Address string which want to find the type. 1.232 + * 1.233 + * @return Address type. 1.234 + */ 1.235 + resolveType: function(address) { 1.236 + if (address.match(this.REGEXP_EMAIL)) { 1.237 + return "email"; 1.238 + } 1.239 + 1.240 + if (address.match(this.REGEXP_IPV4)) { 1.241 + return "IPv4"; 1.242 + } 1.243 + 1.244 + if (address.match(this.REGEXP_IPV6)) { 1.245 + return "IPv6"; 1.246 + } 1.247 + 1.248 + let normalizedAddress = PhoneNumberUtils.normalize(address, false); 1.249 + if (PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) { 1.250 + return "PLMN"; 1.251 + } 1.252 + 1.253 + return "Others"; 1.254 + }, 1.255 +}; 1.256 + 1.257 +defineLazyRegExp(Address, "REGEXP_DECODE_PLMN", "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$"); 1.258 +defineLazyRegExp(Address, "REGEXP_DECODE_IPV4", "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$"); 1.259 +defineLazyRegExp(Address, "REGEXP_DECODE_IPV6", "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$"); 1.260 +defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM", "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$"); 1.261 +defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN", "^\\+?[\\d.-]+$"); 1.262 +defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$"); 1.263 +defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$"); 1.264 +defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$"); 1.265 +defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$"); 1.266 +defineLazyRegExp(Address, "REGEXP_NUM", "^[\\+*#]\\d+$"); 1.267 +defineLazyRegExp(Address, "REGEXP_ALPHANUM", "^\\w+$"); 1.268 +defineLazyRegExp(Address, "REGEXP_PLMN", "^\\?[\\d.-]$"); 1.269 +defineLazyRegExp(Address, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$"); 1.270 +defineLazyRegExp(Address, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$"); 1.271 +defineLazyRegExp(Address, "REGEXP_EMAIL", "@"); 1.272 + 1.273 +/** 1.274 + * Header-field = MMS-header | Application-header 1.275 + * 1.276 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2 1.277 + */ 1.278 +this.HeaderField = { 1.279 + /** 1.280 + * @param data 1.281 + * A wrapped object containing raw PDU data. 1.282 + * @param options 1.283 + * Extra context for decoding. 1.284 + * 1.285 + * @return A decoded object containing `name` and `value` properties or null 1.286 + * in case of a failed parsing. The `name` property must be a string, 1.287 + * but the `value` property can be many different types depending on 1.288 + * `name`. 1.289 + */ 1.290 + decode: function(data, options) { 1.291 + return WSP.decodeAlternatives(data, options, 1.292 + MmsHeader, WSP.ApplicationHeader); 1.293 + }, 1.294 + 1.295 + /** 1.296 + * @param data 1.297 + * A wrapped object to store encoded raw data. 1.298 + * @param octet 1.299 + * Octet value to be encoded. 1.300 + * @param options 1.301 + * Extra context for encoding. 1.302 + */ 1.303 + encode: function(data, value, options) { 1.304 + WSP.encodeAlternatives(data, value, options, 1.305 + MmsHeader, WSP.ApplicationHeader); 1.306 + }, 1.307 +}; 1.308 + 1.309 +/** 1.310 + * MMS-header = MMS-field-name MMS-value 1.311 + * MMS-field-name = Short-integer 1.312 + * 1.313 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2 1.314 + */ 1.315 +this.MmsHeader = { 1.316 + /** 1.317 + * @param data 1.318 + * A wrapped object containing raw PDU data. 1.319 + * @param options 1.320 + * Extra context for decoding. 1.321 + * 1.322 + * @return A decoded object containing `name` and `value` properties or null 1.323 + * in case of a failed parsing. The `name` property must be a string, 1.324 + * but the `value` property can be many different types depending on 1.325 + * `name`. 1.326 + * 1.327 + * @throws NotWellKnownEncodingError if decoded well-known header field 1.328 + * number is not registered or supported. 1.329 + */ 1.330 + decode: function(data, options) { 1.331 + let index = WSP.ShortInteger.decode(data); 1.332 + 1.333 + let entry = MMS_HEADER_FIELDS[index]; 1.334 + if (!entry) { 1.335 + throw new WSP.NotWellKnownEncodingError( 1.336 + "MMS-header: not well known header " + index); 1.337 + } 1.338 + 1.339 + let cur = data.offset, value; 1.340 + try { 1.341 + value = entry.coder.decode(data, options); 1.342 + } catch (e) { 1.343 + data.offset = cur; 1.344 + 1.345 + value = WSP.skipValue(data); 1.346 + debug("Skip malformed well known header: " 1.347 + + JSON.stringify({name: entry.name, value: value})); 1.348 + 1.349 + return null; 1.350 + } 1.351 + 1.352 + return { 1.353 + name: entry.name, 1.354 + value: value, 1.355 + }; 1.356 + }, 1.357 + 1.358 + /** 1.359 + * @param data 1.360 + * A wrapped object to store encoded raw data. 1.361 + * @param header 1.362 + * An object containing two attributes: a string-typed `name` and a 1.363 + * `value` of arbitrary type. 1.364 + * 1.365 + * @throws CodeError if got an empty header name. 1.366 + * @throws NotWellKnownEncodingError if the well-known header field number is 1.367 + * not registered or supported. 1.368 + */ 1.369 + encode: function(data, header) { 1.370 + if (!header.name) { 1.371 + throw new WSP.CodeError("MMS-header: empty header name"); 1.372 + } 1.373 + 1.374 + let entry = MMS_HEADER_FIELDS[header.name.toLowerCase()]; 1.375 + if (!entry) { 1.376 + throw new WSP.NotWellKnownEncodingError( 1.377 + "MMS-header: not well known header " + header.name); 1.378 + } 1.379 + 1.380 + WSP.ShortInteger.encode(data, entry.number); 1.381 + entry.coder.encode(data, header.value); 1.382 + }, 1.383 +}; 1.384 + 1.385 +/** 1.386 + * Cancel-status-value = Cancel Request Successfully received | 1.387 + * Cancel Request corrupted 1.388 + * 1.389 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.7 1.390 + */ 1.391 +this.CancelStatusValue = new RangedValue("Cancel-status-value", 128, 129); 1.392 + 1.393 +/** 1.394 + * Content-class-value = text | image-basic| image-rich | video-basic | 1.395 + * video-rich | megapixel | content-basic | content-rich 1.396 + * 1.397 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9 1.398 + */ 1.399 +this.ContentClassValue = new RangedValue("Content-class-value", 128, 135); 1.400 + 1.401 +/** 1.402 + * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: 1.403 + * 1.404 + * Content-location-value = Uri-value 1.405 + * 1.406 + * When used in the M-Mbox-Delete.conf and M-Delete.conf PDU: 1.407 + * 1.408 + * Content-location-Del-value = Value-length Status-count-value Content-location-value 1.409 + * Status-count-value = Integer-value 1.410 + * 1.411 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.10 1.412 + */ 1.413 +this.ContentLocationValue = { 1.414 + /** 1.415 + * @param data 1.416 + * A wrapped object containing raw PDU data. 1.417 + * @param options 1.418 + * Extra context for decoding. 1.419 + * 1.420 + * @return A decoded object containing `uri` and conditional `statusCount` 1.421 + * properties. 1.422 + */ 1.423 + decode: function(data, options) { 1.424 + let type = WSP.ensureHeader(options, "x-mms-message-type"); 1.425 + 1.426 + let result = {}; 1.427 + if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF) 1.428 + || (type == MMS_PDU_TYPE_DELETE_CONF)) { 1.429 + let length = WSP.ValueLength.decode(data); 1.430 + let end = data.offset + length; 1.431 + 1.432 + result.statusCount = WSP.IntegerValue.decode(data); 1.433 + result.uri = WSP.UriValue.decode(data); 1.434 + 1.435 + if (data.offset != end) { 1.436 + data.offset = end; 1.437 + } 1.438 + } else { 1.439 + result.uri = WSP.UriValue.decode(data); 1.440 + } 1.441 + 1.442 + return result; 1.443 + }, 1.444 +}; 1.445 + 1.446 +/** 1.447 + * Element-Descriptor-value = Value-length Content-Reference-value *(Parameter) 1.448 + * Content-Reference-value = Text-string 1.449 + * 1.450 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18 1.451 + */ 1.452 +this.ElementDescriptorValue = { 1.453 + /** 1.454 + * @param data 1.455 + * A wrapped object containing raw PDU data. 1.456 + * 1.457 + * @return A decoded object containing a string property `contentReference` 1.458 + * and an optinal `params` name-value map. 1.459 + */ 1.460 + decode: function(data) { 1.461 + let length = WSP.ValueLength.decode(data); 1.462 + let end = data.offset + length; 1.463 + 1.464 + let result = {}; 1.465 + result.contentReference = WSP.TextString.decode(data); 1.466 + if (data.offset < end) { 1.467 + result.params = Parameter.decodeMultiple(data, end); 1.468 + } 1.469 + 1.470 + if (data.offset != end) { 1.471 + // Explicitly seek to end in case of skipped parameters. 1.472 + data.offset = end; 1.473 + } 1.474 + 1.475 + return result; 1.476 + }, 1.477 +}; 1.478 + 1.479 +/** 1.480 + * OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18: 1.481 + * `For well-known parameter names binary tokens MUST be used as defined in 1.482 + * Table 27.` So we can't reuse that of WSP. 1.483 + * 1.484 + * Parameter = Parameter-name Parameter-value 1.485 + * Parameter-name = Short-integer | Text-string 1.486 + * Parameter-value = Constrained-encoding | Text-string 1.487 + * 1.488 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18 1.489 + */ 1.490 +this.Parameter = { 1.491 + /** 1.492 + * @param data 1.493 + * A wrapped object containing raw PDU data. 1.494 + * 1.495 + * @return A decoded string. 1.496 + * 1.497 + * @throws NotWellKnownEncodingError if decoded well-known parameter number 1.498 + * is not registered or supported. 1.499 + */ 1.500 + decodeParameterName: function(data) { 1.501 + let begin = data.offset; 1.502 + let number; 1.503 + try { 1.504 + number = WSP.ShortInteger.decode(data); 1.505 + } catch (e) { 1.506 + data.offset = begin; 1.507 + return WSP.TextString.decode(data).toLowerCase(); 1.508 + } 1.509 + 1.510 + let entry = MMS_WELL_KNOWN_PARAMS[number]; 1.511 + if (!entry) { 1.512 + throw new WSP.NotWellKnownEncodingError( 1.513 + "Parameter-name: not well known parameter " + number); 1.514 + } 1.515 + 1.516 + return entry.name; 1.517 + }, 1.518 + 1.519 + /** 1.520 + * @param data 1.521 + * A wrapped object containing raw PDU data. 1.522 + * 1.523 + * @return A decoded object containing `name` and `value` properties or null 1.524 + * in case of a failed parsing. The `name` property must be a string, 1.525 + * but the `value` property can be many different types depending on 1.526 + * `name`. 1.527 + */ 1.528 + decode: function(data) { 1.529 + let name = this.decodeParameterName(data); 1.530 + let value = WSP.decodeAlternatives(data, null, 1.531 + WSP.ConstrainedEncoding, WSP.TextString); 1.532 + return { 1.533 + name: name, 1.534 + value: value, 1.535 + }; 1.536 + }, 1.537 + 1.538 + /** 1.539 + * @param data 1.540 + * A wrapped object containing raw PDU data. 1.541 + * @param end 1.542 + * Ending offset of following parameters. 1.543 + * 1.544 + * @return An array of decoded objects. 1.545 + */ 1.546 + decodeMultiple: function(data, end) { 1.547 + let params, param; 1.548 + 1.549 + while (data.offset < end) { 1.550 + try { 1.551 + param = this.decode(data); 1.552 + } catch (e) { 1.553 + break; 1.554 + } 1.555 + if (param) { 1.556 + if (!params) { 1.557 + params = {}; 1.558 + } 1.559 + params[param.name] = param.value; 1.560 + } 1.561 + } 1.562 + 1.563 + return params; 1.564 + }, 1.565 + 1.566 + /** 1.567 + * @param data 1.568 + * A wrapped object to store encoded raw data. 1.569 + * @param param 1.570 + * An object containing two attributes: `name` and `value`. 1.571 + * @param options 1.572 + * Extra context for encoding. 1.573 + */ 1.574 + encode: function(data, param, options) { 1.575 + if (!param || !param.name) { 1.576 + throw new WSP.CodeError("Parameter-name: empty param name"); 1.577 + } 1.578 + 1.579 + let entry = MMS_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; 1.580 + if (entry) { 1.581 + WSP.ShortInteger.encode(data, entry.number); 1.582 + } else { 1.583 + WSP.TextString.encode(data, param.name); 1.584 + } 1.585 + 1.586 + WSP.encodeAlternatives(data, param.value, options, 1.587 + WSP.ConstrainedEncoding, WSP.TextString); 1.588 + }, 1.589 +}; 1.590 + 1.591 +/** 1.592 + * The Char-set values are registered by IANA as MIBEnum value and SHALL be 1.593 + * encoded as Integer-value. 1.594 + * 1.595 + * Encoded-string-value = Text-string | Value-length Char-set Text-string 1.596 + * 1.597 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19 1.598 + * @see OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1 1.599 + */ 1.600 +this.EncodedStringValue = { 1.601 + /** 1.602 + * @param data 1.603 + * A wrapped object containing raw PDU data. 1.604 + * 1.605 + * @return Decoded string. 1.606 + * 1.607 + * @throws CodeError if the raw octets cannot be converted. 1.608 + * @throws NotWellKnownEncodingError if decoded well-known charset number is 1.609 + * not registered or supported. 1.610 + */ 1.611 + decodeCharsetEncodedString: function(data) { 1.612 + let length = WSP.ValueLength.decode(data); 1.613 + let end = data.offset + length; 1.614 + 1.615 + let charset = WSP.IntegerValue.decode(data); 1.616 + let entry = WSP.WSP_WELL_KNOWN_CHARSETS[charset]; 1.617 + if (!entry) { 1.618 + throw new WSP.NotWellKnownEncodingError( 1.619 + "Charset-encoded-string: not well known charset " + charset); 1.620 + } 1.621 + 1.622 + let str; 1.623 + if (entry.converter) { 1.624 + // Read a possible string quote(<Octet 127>). 1.625 + let begin = data.offset; 1.626 + if (WSP.Octet.decode(data) != 127) { 1.627 + data.offset = begin; 1.628 + } 1.629 + 1.630 + let raw = WSP.Octet.decodeMultiple(data, end - 1); 1.631 + // Read NUL character. 1.632 + WSP.Octet.decodeEqualTo(data, 0); 1.633 + 1.634 + if (!raw) { 1.635 + str = ""; 1.636 + } else { 1.637 + let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.638 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.639 + conv.charset = entry.converter; 1.640 + try { 1.641 + str = conv.convertFromByteArray(raw, raw.length); 1.642 + } catch (e) { 1.643 + throw new WSP.CodeError("Charset-encoded-string: " + e.message); 1.644 + } 1.645 + } 1.646 + } else { 1.647 + str = WSP.TextString.decode(data); 1.648 + } 1.649 + 1.650 + if (data.offset != end) { 1.651 + data.offset = end; 1.652 + } 1.653 + 1.654 + return str; 1.655 + }, 1.656 + 1.657 + /** 1.658 + * @param data 1.659 + * A wrapped object containing raw PDU data. 1.660 + * 1.661 + * @return Decoded string. 1.662 + */ 1.663 + decode: function(data) { 1.664 + let begin = data.offset; 1.665 + try { 1.666 + return WSP.TextString.decode(data); 1.667 + } catch (e) { 1.668 + data.offset = begin; 1.669 + return this.decodeCharsetEncodedString(data); 1.670 + } 1.671 + }, 1.672 + 1.673 + /** 1.674 + * Always encode target string with UTF-8 encoding. 1.675 + * 1.676 + * @param data 1.677 + * A wrapped object to store encoded raw data. 1.678 + * @param str 1.679 + * A string. 1.680 + */ 1.681 + encodeCharsetEncodedString: function(data, str) { 1.682 + let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.683 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.684 + // `When the text string cannot be represented as us-ascii, the character 1.685 + // set SHALL be encoded as utf-8(IANA MIBenum 106) which has unique byte 1.686 + // ordering.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1 1.687 + conv.charset = "UTF-8"; 1.688 + 1.689 + let raw; 1.690 + try { 1.691 + raw = conv.convertToByteArray(str); 1.692 + } catch (e) { 1.693 + throw new WSP.CodeError("Charset-encoded-string: " + e.message); 1.694 + } 1.695 + 1.696 + let length = raw.length + 2; // Charset number and NUL character 1.697 + // Prepend <Octet 127> if necessary. 1.698 + if (raw[0] >= 128) { 1.699 + ++length; 1.700 + } 1.701 + 1.702 + WSP.ValueLength.encode(data, length); 1.703 + 1.704 + let entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]; 1.705 + WSP.IntegerValue.encode(data, entry.number); 1.706 + 1.707 + if (raw[0] >= 128) { 1.708 + WSP.Octet.encode(data, 127); 1.709 + } 1.710 + WSP.Octet.encodeMultiple(data, raw); 1.711 + WSP.Octet.encode(data, 0); 1.712 + }, 1.713 + 1.714 + /** 1.715 + * @param data 1.716 + * A wrapped object to store encoded raw data. 1.717 + * @param str 1.718 + * A string. 1.719 + */ 1.720 + encode: function(data, str) { 1.721 + let begin = data.offset; 1.722 + try { 1.723 + // Quoted from OMA-TS-MMS-CONF-V1_3-20110913-A: 1.724 + // Some of the MMS headers have been defined as "Encoded-string-value". 1.725 + // The character set IANA MIBEnum value in these headers SHALL be 1.726 + // encoded as Integer-value ([WAPWSP] section 8.4.2.3). The character 1.727 + // set us-ascii (IANA MIBenum 3) SHALL always be accepted. If the 1.728 + // character set is not specified (simple Text-string encoding) the 1.729 + // character set SHALL be identified as us-ascii (lower half of ISO 1.730 + // 8859-1 [ISO8859-1]). When the text string cannot be represented as 1.731 + // us-ascii, the character set SHALL be encoded as utf-8 (IANA MIBenum 1.732 + // 106) which has unique byte ordering. 1.733 + WSP.TextString.encode(data, str, true); 1.734 + } catch (e) { 1.735 + data.offset = begin; 1.736 + this.encodeCharsetEncodedString(data, str); 1.737 + } 1.738 + }, 1.739 +}; 1.740 + 1.741 +/** 1.742 + * Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value) 1.743 + * Absolute-token = <Octet 128> 1.744 + * Relative-token = <Octet 129> 1.745 + * 1.746 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.20 1.747 + */ 1.748 +this.ExpiryValue = { 1.749 + /** 1.750 + * @param data 1.751 + * A wrapped object containing raw PDU data. 1.752 + * 1.753 + * @return A Date object for absolute expiry or an integer for relative one. 1.754 + * 1.755 + * @throws CodeError if decoded token equals to neither 128 nor 129. 1.756 + */ 1.757 + decode: function(data) { 1.758 + let length = WSP.ValueLength.decode(data); 1.759 + let end = data.offset + length; 1.760 + 1.761 + let token = WSP.Octet.decode(data); 1.762 + if ((token != 128) && (token != 129)) { 1.763 + throw new WSP.CodeError("Expiry-value: invalid token " + token); 1.764 + } 1.765 + 1.766 + let result; 1.767 + if (token == 128) { 1.768 + result = WSP.DateValue.decode(data); 1.769 + } else { 1.770 + result = WSP.DeltaSecondsValue.decode(data); 1.771 + } 1.772 + 1.773 + if (data.offset != end) { 1.774 + data.offset = end; 1.775 + } 1.776 + 1.777 + return result; 1.778 + }, 1.779 + 1.780 + /** 1.781 + * @param data 1.782 + * A wrapped object to store encoded raw data. 1.783 + * @param value 1.784 + * A Date object for absolute expiry or an integer for relative one. 1.785 + */ 1.786 + encode: function(data, value) { 1.787 + let isDate, begin = data.offset; 1.788 + if (value instanceof Date) { 1.789 + isDate = true; 1.790 + WSP.DateValue.encode(data, value); 1.791 + } else if (typeof value == "number") { 1.792 + isDate = false; 1.793 + WSP.DeltaSecondsValue.encode(data, value); 1.794 + } else { 1.795 + throw new CodeError("Expiry-value: invalid value type"); 1.796 + } 1.797 + 1.798 + // Calculate how much octets will be written and seek back. 1.799 + // TODO: use memmove, see bug 730873 1.800 + let len = data.offset - begin; 1.801 + data.offset = begin; 1.802 + 1.803 + WSP.ValueLength.encode(data, len + 1); 1.804 + if (isDate) { 1.805 + WSP.Octet.encode(data, 128); 1.806 + WSP.DateValue.encode(data, value); 1.807 + } else { 1.808 + WSP.Octet.encode(data, 129); 1.809 + WSP.DeltaSecondsValue.encode(data, value); 1.810 + } 1.811 + }, 1.812 +}; 1.813 + 1.814 +/** 1.815 + * From-value = Value-length (Address-present-token Address | Insert-address-token) 1.816 + * Address-present-token = <Octet 128> 1.817 + * Insert-address-token = <Octet 129> 1.818 + * 1.819 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.21 1.820 + */ 1.821 +this.FromValue = { 1.822 + /** 1.823 + * @param data 1.824 + * A wrapped object containing raw PDU data. 1.825 + * 1.826 + * @return A decoded Address-value or null for MMS Proxy-Relay Insert-Address 1.827 + * mode. 1.828 + * 1.829 + * @throws CodeError if decoded token equals to neither 128 nor 129. 1.830 + */ 1.831 + decode: function(data) { 1.832 + let length = WSP.ValueLength.decode(data); 1.833 + let end = data.offset + length; 1.834 + 1.835 + let token = WSP.Octet.decode(data); 1.836 + if ((token != 128) && (token != 129)) { 1.837 + throw new WSP.CodeError("From-value: invalid token " + token); 1.838 + } 1.839 + 1.840 + let result = null; 1.841 + if (token == 128) { 1.842 + result = Address.decode(data); 1.843 + } 1.844 + 1.845 + if (data.offset != end) { 1.846 + data.offset = end; 1.847 + } 1.848 + 1.849 + return result; 1.850 + }, 1.851 + 1.852 + /** 1.853 + * @param data 1.854 + * A wrapped object to store encoded raw data. 1.855 + * @param value 1.856 + * A Address-value or null for MMS Proxy-Relay Insert-Address mode. 1.857 + */ 1.858 + encode: function(data, value) { 1.859 + if (!value) { 1.860 + WSP.ValueLength.encode(data, 1); 1.861 + WSP.Octet.encode(data, 129); 1.862 + return; 1.863 + } 1.864 + 1.865 + // Calculate how much octets will be written and seek back. 1.866 + // TODO: use memmove, see bug 730873 1.867 + let begin = data.offset; 1.868 + Address.encode(data, value); 1.869 + let len = data.offset - begin; 1.870 + data.offset = begin; 1.871 + 1.872 + WSP.ValueLength.encode(data, len + 1); 1.873 + WSP.Octet.encode(data, 128); 1.874 + Address.encode(data, value); 1.875 + }, 1.876 +}; 1.877 + 1.878 +/** 1.879 + * Previously-sent-by-value = Value-length Forwarded-count-value Address 1.880 + * Forwarded-count-value = Integer-value 1.881 + * 1.882 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23 1.883 + */ 1.884 +this.PreviouslySentByValue = { 1.885 + /** 1.886 + * @param data 1.887 + * A wrapped object containing raw PDU data. 1.888 + * 1.889 + * @return Decoded object containing an integer `forwardedCount` and an 1.890 + * string-typed `originator` attributes. 1.891 + */ 1.892 + decode: function(data) { 1.893 + let length = WSP.ValueLength.decode(data); 1.894 + let end = data.offset + length; 1.895 + 1.896 + let result = {}; 1.897 + result.forwardedCount = WSP.IntegerValue.decode(data); 1.898 + result.originator = Address.decode(data); 1.899 + 1.900 + if (data.offset != end) { 1.901 + data.offset = end; 1.902 + } 1.903 + 1.904 + return result; 1.905 + }, 1.906 +}; 1.907 + 1.908 +/** 1.909 + * Previously-sent-date-value = Value-length Forwarded-count-value Date-value 1.910 + * 1.911 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23 1.912 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.24 1.913 + */ 1.914 +this.PreviouslySentDateValue = { 1.915 + /** 1.916 + * @param data 1.917 + * A wrapped object containing raw PDU data. 1.918 + * 1.919 + * @return Decoded object containing an integer `forwardedCount` and an 1.920 + * Date-typed `timestamp` attributes. 1.921 + */ 1.922 + decode: function(data) { 1.923 + let length = WSP.ValueLength.decode(data); 1.924 + let end = data.offset + length; 1.925 + 1.926 + let result = {}; 1.927 + result.forwardedCount = WSP.IntegerValue.decode(data); 1.928 + result.timestamp = WSP.DateValue.decode(data); 1.929 + 1.930 + if (data.offset != end) { 1.931 + data.offset = end; 1.932 + } 1.933 + 1.934 + return result; 1.935 + }, 1.936 +}; 1.937 + 1.938 +/** 1.939 + * Message-class-value = Class-identifier | Token-text 1.940 + * Class-identifier = Personal | Advertisement | Informational | Auto 1.941 + * Personal = <Octet 128> 1.942 + * Advertisement = <Octet 129> 1.943 + * Informational = <Octet 130> 1.944 + * Auto = <Octet 131> 1.945 + * 1.946 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27 1.947 + */ 1.948 +this.MessageClassValue = { 1.949 + WELL_KNOWN_CLASSES: ["personal", "advertisement", "informational", "auto"], 1.950 + 1.951 + /** 1.952 + * @param data 1.953 + * A wrapped object containing raw PDU data. 1.954 + * 1.955 + * @return A decoded string. 1.956 + * 1.957 + * @throws CodeError if decoded value is not in the range 128..131. 1.958 + */ 1.959 + decodeClassIdentifier: function(data) { 1.960 + let value = WSP.Octet.decode(data); 1.961 + if ((value >= 128) && (value < (128 + this.WELL_KNOWN_CLASSES.length))) { 1.962 + return this.WELL_KNOWN_CLASSES[value - 128]; 1.963 + } 1.964 + 1.965 + throw new WSP.CodeError("Class-identifier: invalid id " + value); 1.966 + }, 1.967 + 1.968 + /** 1.969 + * @param data 1.970 + * A wrapped object containing raw PDU data. 1.971 + * 1.972 + * @return A decoded string. 1.973 + */ 1.974 + decode: function(data) { 1.975 + let begin = data.offset; 1.976 + try { 1.977 + return this.decodeClassIdentifier(data); 1.978 + } catch (e) { 1.979 + data.offset = begin; 1.980 + return WSP.TokenText.decode(data); 1.981 + } 1.982 + }, 1.983 + 1.984 + /** 1.985 + * @param data 1.986 + * A wrapped object to store encoded raw data. 1.987 + * @param klass 1.988 + */ 1.989 + encode: function(data, klass) { 1.990 + let index = this.WELL_KNOWN_CLASSES.indexOf(klass.toLowerCase()); 1.991 + if (index >= 0) { 1.992 + WSP.Octet.encode(data, index + 128); 1.993 + } else { 1.994 + WSP.TokenText.encode(data, klass); 1.995 + } 1.996 + }, 1.997 +}; 1.998 + 1.999 + /** 1.1000 + * Message-type-value = <Octet 128..151> 1.1001 + * 1.1002 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30 1.1003 + */ 1.1004 +this.MessageTypeValue = new RangedValue("Message-type-value", 128, 151); 1.1005 + 1.1006 +/** 1.1007 + * MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value 1.1008 + * Add-token = <Octet 128> 1.1009 + * Remove-token = <Octet 129> 1.1010 + * Filter-token = <Octet 130> 1.1011 + * 1.1012 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32 1.1013 + */ 1.1014 +this.MmFlagsValue = { 1.1015 + /** 1.1016 + * @param data 1.1017 + * A wrapped object containing raw PDU data. 1.1018 + * 1.1019 + * @return Decoded object containing an integer `type` and an string-typed 1.1020 + * `text` attributes. 1.1021 + * 1.1022 + * @throws CodeError if decoded value is not in the range 128..130. 1.1023 + */ 1.1024 + decode: function(data) { 1.1025 + let length = WSP.ValueLength.decode(data); 1.1026 + let end = data.offset + length; 1.1027 + 1.1028 + let result = {}; 1.1029 + result.type = WSP.Octet.decode(data); 1.1030 + if ((result.type < 128) || (result.type > 130)) { 1.1031 + throw new WSP.CodeError("MM-flags-value: invalid type " + result.type); 1.1032 + } 1.1033 + result.text = EncodedStringValue.decode(data); 1.1034 + 1.1035 + if (data.offset != end) { 1.1036 + data.offset = end; 1.1037 + } 1.1038 + 1.1039 + return result; 1.1040 + }, 1.1041 + 1.1042 + /** 1.1043 + * @param data 1.1044 + * A wrapped object to store encoded raw data. 1.1045 + * @param value 1.1046 + * An object containing an integer `type` and an string-typed 1.1047 + * `text` attributes. 1.1048 + */ 1.1049 + encode: function(data, value) { 1.1050 + if ((value.type < 128) || (value.type > 130)) { 1.1051 + throw new WSP.CodeError("MM-flags-value: invalid type " + value.type); 1.1052 + } 1.1053 + 1.1054 + // Calculate how much octets will be written and seek back. 1.1055 + // TODO: use memmove, see bug 730873 1.1056 + let begin = data.offset; 1.1057 + EncodedStringValue.encode(data, value.text); 1.1058 + let len = data.offset - begin; 1.1059 + data.offset = begin; 1.1060 + 1.1061 + WSP.ValueLength.encode(data, len + 1); 1.1062 + WSP.Octet.encode(data, value.type); 1.1063 + EncodedStringValue.encode(data, value.text); 1.1064 + }, 1.1065 +}; 1.1066 + 1.1067 +/** 1.1068 + * MM-state-value = Draft | Sent | New | Retrieved | Forwarded 1.1069 + * Draft = <Octet 128> 1.1070 + * Sent = <Octet 129> 1.1071 + * New = <Octet 130> 1.1072 + * Retrieved = <Octet 131> 1.1073 + * Forwarded = <Octet 132> 1.1074 + * 1.1075 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33 1.1076 + */ 1.1077 +this.MmStateValue = new RangedValue("MM-state-value", 128, 132); 1.1078 + 1.1079 +/** 1.1080 + * Priority-value = Low | Normal | High 1.1081 + * Low = <Octet 128> 1.1082 + * Normal = <Octet 129> 1.1083 + * High = <Octet 130> 1.1084 + * 1.1085 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35 1.1086 + */ 1.1087 +this.PriorityValue = new RangedValue("Priority-value", 128, 130); 1.1088 + 1.1089 +/** 1.1090 + * Read-status-value = Read | Deleted without being read 1.1091 + * 1.1092 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.38 1.1093 + */ 1.1094 +this.ReadStatusValue = new RangedValue("Read-status-value", 128, 129); 1.1095 + 1.1096 +/** 1.1097 + * Recommended-Retrieval-Mode-value = Manual 1.1098 + * Manual = <Octet 128> 1.1099 + * 1.1100 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39 1.1101 + */ 1.1102 +this.RecommendedRetrievalModeValue = { 1.1103 + /** 1.1104 + * @param data 1.1105 + * A wrapped object containing raw PDU data. 1.1106 + * 1.1107 + * @return A decoded integer. 1.1108 + */ 1.1109 + decode: function(data) { 1.1110 + return WSP.Octet.decodeEqualTo(data, 128); 1.1111 + }, 1.1112 +}; 1.1113 + 1.1114 +/** 1.1115 + * Reply-charging-value = Requested | Requested text only | Accepted | 1.1116 + * Accepted text only 1.1117 + * Requested = <Octet 128> 1.1118 + * Requested text only = <Octet 129> 1.1119 + * Accepted = <Octet 130> 1.1120 + * Accepted text only = <Octet 131> 1.1121 + * 1.1122 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43 1.1123 + */ 1.1124 +this.ReplyChargingValue = new RangedValue("Reply-charging-value", 128, 131); 1.1125 + 1.1126 +/** 1.1127 + * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: 1.1128 + * 1.1129 + * Response-text-value = Encoded-string-value 1.1130 + * 1.1131 + * When used in the M-Mbox-Delete.conf and M-Delete.conf PDUs: 1.1132 + * 1.1133 + * Response-text-Del-value = Value-length Status-count-value Response-text-value 1.1134 + * 1.1135 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.49 1.1136 + */ 1.1137 +this.ResponseText = { 1.1138 + /** 1.1139 + * @param data 1.1140 + * A wrapped object containing raw PDU data. 1.1141 + * @param options 1.1142 + * Extra context for decoding. 1.1143 + * 1.1144 + * @return An object containing a string-typed `text` attribute and a 1.1145 + * integer-typed `statusCount` one. 1.1146 + */ 1.1147 + decode: function(data, options) { 1.1148 + let type = WSP.ensureHeader(options, "x-mms-message-type"); 1.1149 + 1.1150 + let result = {}; 1.1151 + if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF) 1.1152 + || (type == MMS_PDU_TYPE_DELETE_CONF)) { 1.1153 + let length = WSP.ValueLength.decode(data); 1.1154 + let end = data.offset + length; 1.1155 + 1.1156 + result.statusCount = WSP.IntegerValue.decode(data); 1.1157 + result.text = EncodedStringValue.decode(data); 1.1158 + 1.1159 + if (data.offset != end) { 1.1160 + data.offset = end; 1.1161 + } 1.1162 + } else { 1.1163 + result.text = EncodedStringValue.decode(data); 1.1164 + } 1.1165 + 1.1166 + return result; 1.1167 + }, 1.1168 +}; 1.1169 + 1.1170 +/** 1.1171 + * Retrieve-status-value = Ok | Error-transient-failure | 1.1172 + * Error-transient-message-not-found | 1.1173 + * Error-transient-network-problem | 1.1174 + * Error-permanent-failure | 1.1175 + * Error-permanent-service-denied | 1.1176 + * Error-permanent-message-not-found | 1.1177 + * Error-permanent-content-unsupported 1.1178 + * Ok = <Octet 128> 1.1179 + * Error-transient-failure = <Octet 192> 1.1180 + * Error-transient-message-not-found = <Octet 193> 1.1181 + * Error-transient-network-problem = <Octet 194> 1.1182 + * Error-permanent-failure = <Octet 224> 1.1183 + * Error-permanent-service-denied = <Octet 225> 1.1184 + * Error-permanent-message-not-found = <Octet 226> 1.1185 + * Error-permanent-content-unsupported = <Octet 227> 1.1186 + * 1.1187 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50 1.1188 + */ 1.1189 +this.RetrieveStatusValue = { 1.1190 + /** 1.1191 + * @param data 1.1192 + * A wrapped object containing raw PDU data. 1.1193 + * 1.1194 + * @return A decoded integer. 1.1195 + */ 1.1196 + decode: function(data) { 1.1197 + let value = WSP.Octet.decode(data); 1.1198 + if (value == MMS_PDU_ERROR_OK) { 1.1199 + return value; 1.1200 + } 1.1201 + 1.1202 + if ((value >= MMS_PDU_ERROR_TRANSIENT_FAILURE) && (value < 256)) { 1.1203 + return value; 1.1204 + } 1.1205 + 1.1206 + // Any other values SHALL NOT be used. They are reserved for future use. 1.1207 + // An MMS Client that receives such a reserved value MUST react the same 1.1208 + // as it does to the value 224 (Error-permanent-failure). 1.1209 + return MMS_PDU_ERROR_PERMANENT_FAILURE; 1.1210 + }, 1.1211 +}; 1.1212 + 1.1213 +/** 1.1214 + * Sender-visibility-value = Hide | Show 1.1215 + * 1.1216 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.52 1.1217 + */ 1.1218 +this.SenderVisibilityValue = new RangedValue("Sender-visibility-value", 128, 129); 1.1219 + 1.1220 +/** 1.1221 + * Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised | 1.1222 + * Indeterminate | Forwarded | Unreachable 1.1223 + * Expired = <Octet 128> 1.1224 + * Retrieved = <Octet 129> 1.1225 + * Rejected = <Octet 130> 1.1226 + * Deferred = <Octet 131> 1.1227 + * Unrecognised = <Octet 132> 1.1228 + * Indeterminate = <Octet 133> 1.1229 + * Forwarded = <Octet 134> 1.1230 + * Unreachable = <Octet 135> 1.1231 + * 1.1232 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54 1.1233 + */ 1.1234 +this.StatusValue = new RangedValue("Status-value", 128, 135); 1.1235 + 1.1236 +this.PduHelper = { 1.1237 + /** 1.1238 + * @param data 1.1239 + * A wrapped object containing raw PDU data. 1.1240 + * @param headers 1.1241 + * An optional object to store parsed header fields. Created 1.1242 + * automatically if undefined. 1.1243 + * 1.1244 + * @return A boolean value indicating whether it's followed by message body. 1.1245 + */ 1.1246 + parseHeaders: function(data, headers) { 1.1247 + if (!headers) { 1.1248 + headers = {}; 1.1249 + } 1.1250 + 1.1251 + let header; 1.1252 + while (data.offset < data.array.length) { 1.1253 + // There is no `header length` information in MMS PDU. If we just got 1.1254 + // something wrong in parsing header fields, we might not be able to 1.1255 + // determine the correct header-content boundary. 1.1256 + header = HeaderField.decode(data, headers); 1.1257 + 1.1258 + if (header) { 1.1259 + let orig = headers[header.name]; 1.1260 + if (Array.isArray(orig)) { 1.1261 + headers[header.name].push(header.value); 1.1262 + } else if (orig) { 1.1263 + headers[header.name] = [orig, header.value]; 1.1264 + } else { 1.1265 + headers[header.name] = header.value; 1.1266 + } 1.1267 + if (header.name == "content-type") { 1.1268 + // `... if the PDU contains a message body the Content Type MUST be 1.1269 + // the last header field, followed by message body.` See 1.1270 + // OMA-TS-MMS_ENC-V1_3-20110913-A section 7. 1.1271 + break; 1.1272 + } 1.1273 + } 1.1274 + } 1.1275 + 1.1276 + return headers; 1.1277 + }, 1.1278 + 1.1279 + /** 1.1280 + * @param data 1.1281 + * A wrapped object containing raw PDU data. 1.1282 + * @param msg 1.1283 + * A message object to store decoded multipart or octet array content. 1.1284 + */ 1.1285 + parseContent: function(data, msg) { 1.1286 + let contentType = msg.headers["content-type"].media; 1.1287 + if ((contentType == "application/vnd.wap.multipart.related") 1.1288 + || (contentType == "application/vnd.wap.multipart.mixed")) { 1.1289 + msg.parts = WSP.PduHelper.parseMultiPart(data); 1.1290 + return; 1.1291 + } 1.1292 + 1.1293 + if (data.offset >= data.array.length) { 1.1294 + return; 1.1295 + } 1.1296 + 1.1297 + msg.content = WSP.Octet.decodeMultiple(data, data.array.length); 1.1298 + if (false) { 1.1299 + for (let begin = 0; begin < msg.content.length; begin += 20) { 1.1300 + debug("content: " + JSON.stringify(msg.content.subarray(begin, begin + 20))); 1.1301 + } 1.1302 + } 1.1303 + }, 1.1304 + 1.1305 + /** 1.1306 + * Check existences of all mandatory fields of a MMS message. Also sets `type` 1.1307 + * for convenient access. 1.1308 + * 1.1309 + * @param msg 1.1310 + * A MMS message object. 1.1311 + * 1.1312 + * @return The corresponding entry in MMS_PDU_TYPES; 1.1313 + * 1.1314 + * @throws FatalCodeError if the PDU type is not supported yet. 1.1315 + */ 1.1316 + checkMandatoryFields: function(msg) { 1.1317 + let type = WSP.ensureHeader(msg.headers, "x-mms-message-type"); 1.1318 + let entry = MMS_PDU_TYPES[type]; 1.1319 + if (!entry) { 1.1320 + throw new WSP.FatalCodeError( 1.1321 + "checkMandatoryFields: unsupported message type " + type); 1.1322 + } 1.1323 + 1.1324 + entry.mandatoryFields.forEach(function(name) { 1.1325 + WSP.ensureHeader(msg.headers, name); 1.1326 + }); 1.1327 + 1.1328 + // Setup convenient alias that referenced frequently. 1.1329 + msg.type = type; 1.1330 + 1.1331 + return entry; 1.1332 + }, 1.1333 + 1.1334 + /** 1.1335 + * @param data 1.1336 + * A wrapped object containing raw PDU data. 1.1337 + * @param msg [optional] 1.1338 + * Optional target object for decoding. 1.1339 + * 1.1340 + * @return A MMS message object or null in case of errors found. 1.1341 + */ 1.1342 + parse: function(data, msg) { 1.1343 + if (!msg) { 1.1344 + msg = {}; 1.1345 + } 1.1346 + 1.1347 + try { 1.1348 + msg.headers = this.parseHeaders(data, msg.headers); 1.1349 + 1.1350 + // Validity checks 1.1351 + let typeinfo = this.checkMandatoryFields(msg); 1.1352 + if (typeinfo.hasContent) { 1.1353 + this.parseContent(data, msg); 1.1354 + } 1.1355 + } catch (e) { 1.1356 + debug("Failed to parse MMS message, error message: " + e.message); 1.1357 + return null; 1.1358 + } 1.1359 + 1.1360 + return msg; 1.1361 + }, 1.1362 + 1.1363 + /** 1.1364 + * @param data 1.1365 + * A wrapped object to store encoded raw data. 1.1366 + * @param headers 1.1367 + * A dictionary object containing multiple name/value mapping. 1.1368 + * @param name 1.1369 + * Name of the header field to be encoded. 1.1370 + */ 1.1371 + encodeHeader: function(data, headers, name) { 1.1372 + let value = headers[name]; 1.1373 + if (Array.isArray(value)) { 1.1374 + for (let i = 0; i < value.length; i++) { 1.1375 + HeaderField.encode(data, {name: name, value: value[i]}, headers); 1.1376 + } 1.1377 + } else { 1.1378 + HeaderField.encode(data, {name: name, value: value}, headers); 1.1379 + } 1.1380 + }, 1.1381 + 1.1382 + /** 1.1383 + * @param data 1.1384 + * A wrapped object to store encoded raw data. 1.1385 + * @param headers 1.1386 + * A dictionary object containing multiple name/value mapping. 1.1387 + */ 1.1388 + encodeHeaderIfExists: function(data, headers, name) { 1.1389 + // Header value could be zero or null. 1.1390 + if (headers[name] !== undefined) { 1.1391 + this.encodeHeader(data, headers, name); 1.1392 + } 1.1393 + }, 1.1394 + 1.1395 + /** 1.1396 + * @param data [optional] 1.1397 + * A wrapped object to store encoded raw data. Created if undefined. 1.1398 + * @param headers 1.1399 + * A dictionary object containing multiple name/value mapping. 1.1400 + * 1.1401 + * @return the passed data parameter or a created one. 1.1402 + */ 1.1403 + encodeHeaders: function(data, headers) { 1.1404 + if (!data) { 1.1405 + data = {array: [], offset: 0}; 1.1406 + } 1.1407 + 1.1408 + // `In the encoding of the header fields, the order of the fields is not 1.1409 + // significant, except that X-Mms-Message-Type, X-Mms-Transaction-ID (when 1.1410 + // present) and X-Mms-MMS-Version MUST be at the beginning of the message 1.1411 + // headers, in that order, and if the PDU contains a message body the 1.1412 + // Content Type MUST be the last header field, followed by message body.` 1.1413 + // ~ OMA-TS-MMS_ENC-V1_3-20110913-A section 7 1.1414 + this.encodeHeader(data, headers, "x-mms-message-type"); 1.1415 + this.encodeHeaderIfExists(data, headers, "x-mms-transaction-id"); 1.1416 + this.encodeHeaderIfExists(data, headers, "x-mms-mms-version"); 1.1417 + 1.1418 + for (let key in headers) { 1.1419 + if ((key == "x-mms-message-type") 1.1420 + || (key == "x-mms-transaction-id") 1.1421 + || (key == "x-mms-mms-version") 1.1422 + || (key == "content-type")) { 1.1423 + continue; 1.1424 + } 1.1425 + this.encodeHeader(data, headers, key); 1.1426 + } 1.1427 + 1.1428 + this.encodeHeaderIfExists(data, headers, "content-type"); 1.1429 + 1.1430 + return data; 1.1431 + }, 1.1432 + 1.1433 + /** 1.1434 + * @param multiStream 1.1435 + * An exsiting nsIMultiplexInputStream. 1.1436 + * @param msg 1.1437 + * A MMS message object. 1.1438 + * 1.1439 + * @return An instance of nsIMultiplexInputStream or null in case of errors. 1.1440 + */ 1.1441 + compose: function(multiStream, msg) { 1.1442 + if (!multiStream) { 1.1443 + multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"] 1.1444 + .createInstance(Ci.nsIMultiplexInputStream); 1.1445 + } 1.1446 + 1.1447 + try { 1.1448 + // Validity checks 1.1449 + let typeinfo = this.checkMandatoryFields(msg); 1.1450 + 1.1451 + let data = this.encodeHeaders(null, msg.headers); 1.1452 + debug("Composed PDU Header: " + JSON.stringify(data.array)); 1.1453 + WSP.PduHelper.appendArrayToMultiStream(multiStream, data.array, data.offset); 1.1454 + 1.1455 + if (msg.content) { 1.1456 + WSP.PduHelper.appendArrayToMultiStream(multiStream, msg.content, msg.content.length); 1.1457 + } else if (msg.parts) { 1.1458 + WSP.PduHelper.composeMultiPart(multiStream, msg.parts); 1.1459 + } else if (typeinfo.hasContent) { 1.1460 + throw new WSP.CodeError("Missing message content"); 1.1461 + } 1.1462 + 1.1463 + return multiStream; 1.1464 + } catch (e) { 1.1465 + debug("Failed to compose MMS message, error message: " + e.message); 1.1466 + return null; 1.1467 + } 1.1468 + }, 1.1469 +}; 1.1470 + 1.1471 +const MMS_PDU_TYPES = (function() { 1.1472 + let pdus = {}; 1.1473 + function add(number, hasContent, mandatoryFields) { 1.1474 + pdus[number] = { 1.1475 + number: number, 1.1476 + hasContent: hasContent, 1.1477 + mandatoryFields: mandatoryFields, 1.1478 + }; 1.1479 + } 1.1480 + 1.1481 + add(MMS_PDU_TYPE_SEND_REQ, true, ["x-mms-message-type", 1.1482 + "x-mms-transaction-id", 1.1483 + "x-mms-mms-version", 1.1484 + "from", 1.1485 + "content-type"]); 1.1486 + add(MMS_PDU_TYPE_SEND_CONF, false, ["x-mms-message-type", 1.1487 + "x-mms-transaction-id", 1.1488 + "x-mms-mms-version", 1.1489 + "x-mms-response-status"]); 1.1490 + add(MMS_PDU_TYPE_NOTIFICATION_IND, false, ["x-mms-message-type", 1.1491 + "x-mms-transaction-id", 1.1492 + "x-mms-mms-version", 1.1493 + "x-mms-message-class", 1.1494 + "x-mms-message-size", 1.1495 + "x-mms-expiry", 1.1496 + "x-mms-content-location"]); 1.1497 + add(MMS_PDU_TYPE_RETRIEVE_CONF, true, ["x-mms-message-type", 1.1498 + "x-mms-mms-version", 1.1499 + "date", 1.1500 + "content-type"]); 1.1501 + add(MMS_PDU_TYPE_NOTIFYRESP_IND, false, ["x-mms-message-type", 1.1502 + "x-mms-transaction-id", 1.1503 + "x-mms-mms-version", 1.1504 + "x-mms-status"]); 1.1505 + add(MMS_PDU_TYPE_DELIVERY_IND, false, ["x-mms-message-type", 1.1506 + "x-mms-mms-version", 1.1507 + "message-id", 1.1508 + "to", 1.1509 + "date", 1.1510 + "x-mms-status"]); 1.1511 + add(MMS_PDU_TYPE_ACKNOWLEDGE_IND, false, ["x-mms-message-type", 1.1512 + "x-mms-transaction-id", 1.1513 + "x-mms-mms-version"]); 1.1514 + add(MMS_PDU_TYPE_READ_REC_IND, false, ["x-mms-message-type", 1.1515 + "message-id", 1.1516 + "x-mms-mms-version", 1.1517 + "to", 1.1518 + "from", 1.1519 + "x-mms-read-status"]); 1.1520 + add(MMS_PDU_TYPE_READ_ORIG_IND, false, ["x-mms-message-type", 1.1521 + "x-mms-mms-version", 1.1522 + "message-id", 1.1523 + "to", 1.1524 + "from", 1.1525 + "date", 1.1526 + "x-mms-read-status"]); 1.1527 + 1.1528 + return pdus; 1.1529 +})(); 1.1530 + 1.1531 +/** 1.1532 + * Header field names and assigned numbers. 1.1533 + * 1.1534 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.4 1.1535 + */ 1.1536 +const MMS_HEADER_FIELDS = (function() { 1.1537 + let names = {}; 1.1538 + function add(name, number, coder) { 1.1539 + let entry = { 1.1540 + name: name, 1.1541 + number: number, 1.1542 + coder: coder, 1.1543 + }; 1.1544 + names[name] = names[number] = entry; 1.1545 + } 1.1546 + 1.1547 + add("bcc", 0x01, Address); 1.1548 + add("cc", 0x02, Address); 1.1549 + add("x-mms-content-location", 0x03, ContentLocationValue); 1.1550 + add("content-type", 0x04, WSP.ContentTypeValue); 1.1551 + add("date", 0x05, WSP.DateValue); 1.1552 + add("x-mms-delivery-report", 0x06, BooleanValue); 1.1553 + add("x-mms-delivery-time", 0x07, ExpiryValue); 1.1554 + add("x-mms-expiry", 0x08, ExpiryValue); 1.1555 + add("from", 0x09, FromValue); 1.1556 + add("x-mms-message-class", 0x0A, MessageClassValue); 1.1557 + add("message-id", 0x0B, WSP.TextString); 1.1558 + add("x-mms-message-type", 0x0C, MessageTypeValue); 1.1559 + add("x-mms-mms-version", 0x0D, WSP.ShortInteger); 1.1560 + add("x-mms-message-size", 0x0E, WSP.LongInteger); 1.1561 + add("x-mms-priority", 0x0F, PriorityValue); 1.1562 + add("x-mms-read-report", 0x10, BooleanValue); 1.1563 + add("x-mms-report-allowed", 0x11, BooleanValue); 1.1564 + add("x-mms-response-status", 0x12, RetrieveStatusValue); 1.1565 + add("x-mms-response-text", 0x13, ResponseText); 1.1566 + add("x-mms-sender-visibility", 0x14, SenderVisibilityValue); 1.1567 + add("x-mms-status", 0x15, StatusValue); 1.1568 + add("subject", 0x16, EncodedStringValue); 1.1569 + add("to", 0x17, Address); 1.1570 + add("x-mms-transaction-id", 0x18, WSP.TextString); 1.1571 + add("x-mms-retrieve-status", 0x19, RetrieveStatusValue); 1.1572 + add("x-mms-retrieve-text", 0x1A, EncodedStringValue); 1.1573 + add("x-mms-read-status", 0x1B, ReadStatusValue); 1.1574 + add("x-mms-reply-charging", 0x1C, ReplyChargingValue); 1.1575 + add("x-mms-reply-charging-deadline", 0x1D, ExpiryValue); 1.1576 + add("x-mms-reply-charging-id", 0x1E, WSP.TextString); 1.1577 + add("x-mms-reply-charging-size", 0x1F, WSP.LongInteger); 1.1578 + add("x-mms-previously-sent-by", 0x20, PreviouslySentByValue); 1.1579 + add("x-mms-previously-sent-date", 0x21, PreviouslySentDateValue); 1.1580 + add("x-mms-store", 0x22, BooleanValue); 1.1581 + add("x-mms-mm-state", 0x23, MmStateValue); 1.1582 + add("x-mms-mm-flags", 0x24, MmFlagsValue); 1.1583 + add("x-mms-store-status", 0x25, RetrieveStatusValue); 1.1584 + add("x-mms-store-status-text", 0x26, EncodedStringValue); 1.1585 + add("x-mms-stored", 0x27, BooleanValue); 1.1586 + //add("x-mms-attributes", 0x28); 1.1587 + add("x-mms-totals", 0x29, BooleanValue); 1.1588 + //add("x-mms-mbox-totals", 0x2A); 1.1589 + add("x-mms-quotas", 0x2B, BooleanValue); 1.1590 + //add("x-mms-mbox-quotas", 0x2C); 1.1591 + add("x-mms-message-count", 0x2D, WSP.IntegerValue); 1.1592 + //add("content", 0x2E); 1.1593 + add("x-mms-start", 0x2F, WSP.IntegerValue); 1.1594 + //add("additional-headers", 0x30); 1.1595 + add("x-mms-distribution-indicator", 0x31, BooleanValue); 1.1596 + add("x-mms-element-descriptor", 0x32, ElementDescriptorValue); 1.1597 + add("x-mms-limit", 0x33, WSP.IntegerValue); 1.1598 + add("x-mms-recommended-retrieval-mode", 0x34, RecommendedRetrievalModeValue); 1.1599 + add("x-mms-recommended-retrieval-mode-text", 0x35, EncodedStringValue); 1.1600 + //add("x-mms-status-text", 0x36); 1.1601 + add("x-mms-applic-id", 0x37, WSP.TextString); 1.1602 + add("x-mms-reply-applic-id", 0x38, WSP.TextString); 1.1603 + add("x-mms-aux-applic-id", 0x39, WSP.TextString); 1.1604 + add("x-mms-content-class", 0x3A, ContentClassValue); 1.1605 + add("x-mms-drm-content", 0x3B, BooleanValue); 1.1606 + add("x-mms-adaptation-allowed", 0x3C, BooleanValue); 1.1607 + add("x-mms-replace-id", 0x3D, WSP.TextString); 1.1608 + add("x-mms-cancel-id", 0x3E, WSP.TextString); 1.1609 + add("x-mms-cancel-status", 0x3F, CancelStatusValue); 1.1610 + 1.1611 + return names; 1.1612 +})(); 1.1613 + 1.1614 +// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments 1.1615 +const MMS_WELL_KNOWN_PARAMS = (function() { 1.1616 + let params = {}; 1.1617 + 1.1618 + function add(name, number, coder) { 1.1619 + let entry = { 1.1620 + name: name, 1.1621 + number: number, 1.1622 + coder: coder, 1.1623 + }; 1.1624 + params[name] = params[number] = entry; 1.1625 + } 1.1626 + 1.1627 + // Encoding Version: 1.2 1.1628 + add("type", 0x02, WSP.TypeValue); 1.1629 + 1.1630 + return params; 1.1631 +})(); 1.1632 + 1.1633 +let debug; 1.1634 +if (DEBUG) { 1.1635 + debug = function(s) { 1.1636 + dump("-$- MmsPduHelper: " + s + "\n"); 1.1637 + }; 1.1638 +} else { 1.1639 + debug = function(s) {}; 1.1640 +} 1.1641 + 1.1642 +this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ 1.1643 + // Constant values 1.1644 + "MMS_VERSION", 1.1645 + 1.1646 + // Utility functions 1.1647 + "translatePduErrorToStatus", 1.1648 + 1.1649 + // Decoders 1.1650 + "BooleanValue", 1.1651 + "Address", 1.1652 + "HeaderField", 1.1653 + "MmsHeader", 1.1654 + "CancelStatusValue", 1.1655 + "ContentClassValue", 1.1656 + "ContentLocationValue", 1.1657 + "ElementDescriptorValue", 1.1658 + "Parameter", 1.1659 + "EncodedStringValue", 1.1660 + "ExpiryValue", 1.1661 + "FromValue", 1.1662 + "PreviouslySentByValue", 1.1663 + "PreviouslySentDateValue", 1.1664 + "MessageClassValue", 1.1665 + "MessageTypeValue", 1.1666 + "MmFlagsValue", 1.1667 + "MmStateValue", 1.1668 + "PriorityValue", 1.1669 + "ReadStatusValue", 1.1670 + "RecommendedRetrievalModeValue", 1.1671 + "ReplyChargingValue", 1.1672 + "ResponseText", 1.1673 + "RetrieveStatusValue", 1.1674 + "SenderVisibilityValue", 1.1675 + "StatusValue", 1.1676 + 1.1677 + // Parser 1.1678 + "PduHelper", 1.1679 +]); 1.1680 +