Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
michael@0 | 8 | |
michael@0 | 9 | let WSP = {}; |
michael@0 | 10 | Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP); |
michael@0 | 11 | |
michael@0 | 12 | Cu.import("resource://gre/modules/mms_consts.js"); |
michael@0 | 13 | |
michael@0 | 14 | Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | let DEBUG; // set to true to see debug messages |
michael@0 | 17 | |
michael@0 | 18 | this.MMS_VERSION = (function() { |
michael@0 | 19 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 20 | |
michael@0 | 21 | try { |
michael@0 | 22 | return Services.prefs.getIntPref("dom.mms.version"); |
michael@0 | 23 | } catch(ex) {} |
michael@0 | 24 | |
michael@0 | 25 | return MMS_VERSION_1_3; |
michael@0 | 26 | })(); |
michael@0 | 27 | |
michael@0 | 28 | this.translatePduErrorToStatus = function translatePduErrorToStatus(error) { |
michael@0 | 29 | if (error == MMS_PDU_ERROR_OK) { |
michael@0 | 30 | return MMS_PDU_STATUS_RETRIEVED; |
michael@0 | 31 | } |
michael@0 | 32 | |
michael@0 | 33 | if ((error >= MMS_PDU_ERROR_TRANSIENT_FAILURE) |
michael@0 | 34 | && (error < MMS_PDU_ERROR_PERMANENT_FAILURE)) { |
michael@0 | 35 | return MMS_PDU_STATUS_DEFERRED; |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | return MMS_PDU_STATUS_UNRECOGNISED; |
michael@0 | 39 | } |
michael@0 | 40 | |
michael@0 | 41 | function defineLazyRegExp(obj, name, pattern) { |
michael@0 | 42 | obj.__defineGetter__(name, function() { |
michael@0 | 43 | delete obj[name]; |
michael@0 | 44 | return obj[name] = new RegExp(pattern); |
michael@0 | 45 | }); |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | function RangedValue(name, min, max) { |
michael@0 | 49 | this.name = name; |
michael@0 | 50 | this.min = min; |
michael@0 | 51 | this.max = max; |
michael@0 | 52 | } |
michael@0 | 53 | RangedValue.prototype = { |
michael@0 | 54 | name: null, |
michael@0 | 55 | min: null, |
michael@0 | 56 | max: null, |
michael@0 | 57 | |
michael@0 | 58 | /** |
michael@0 | 59 | * @param data |
michael@0 | 60 | * A wrapped object containing raw PDU data. |
michael@0 | 61 | * |
michael@0 | 62 | * @return A decoded integer. |
michael@0 | 63 | * |
michael@0 | 64 | * @throws CodeError if decoded value is not in the range [this.min, this.max]. |
michael@0 | 65 | */ |
michael@0 | 66 | decode: function(data) { |
michael@0 | 67 | let value = WSP.Octet.decode(data); |
michael@0 | 68 | if ((value >= this.min) && (value <= this.max)) { |
michael@0 | 69 | return value; |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | throw new WSP.CodeError(this.name + ": invalid value " + value); |
michael@0 | 73 | }, |
michael@0 | 74 | |
michael@0 | 75 | /** |
michael@0 | 76 | * @param data |
michael@0 | 77 | * A wrapped object to store encoded raw data. |
michael@0 | 78 | * @param value |
michael@0 | 79 | * An integer value within thr range [this.min, this.max]. |
michael@0 | 80 | */ |
michael@0 | 81 | encode: function(data, value) { |
michael@0 | 82 | if ((value < this.min) || (value > this.max)) { |
michael@0 | 83 | throw new WSP.CodeError(this.name + ": invalid value " + value); |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | WSP.Octet.encode(data, value); |
michael@0 | 87 | }, |
michael@0 | 88 | }; |
michael@0 | 89 | |
michael@0 | 90 | /** |
michael@0 | 91 | * Internal decoding function for boolean values. |
michael@0 | 92 | * |
michael@0 | 93 | * Boolean-value = Yes | No |
michael@0 | 94 | * Yes = <Octet 128> |
michael@0 | 95 | * No = <Octet 129> |
michael@0 | 96 | */ |
michael@0 | 97 | this.BooleanValue = { |
michael@0 | 98 | /** |
michael@0 | 99 | * @param data |
michael@0 | 100 | * A wrapped object containing raw PDU data. |
michael@0 | 101 | * |
michael@0 | 102 | * @return Boolean true or false. |
michael@0 | 103 | * |
michael@0 | 104 | * @throws CodeError if read octet equals to neither 128 nor 129. |
michael@0 | 105 | */ |
michael@0 | 106 | decode: function(data) { |
michael@0 | 107 | let value = WSP.Octet.decode(data); |
michael@0 | 108 | if ((value != 128) && (value != 129)) { |
michael@0 | 109 | throw new WSP.CodeError("Boolean-value: invalid value " + value); |
michael@0 | 110 | } |
michael@0 | 111 | |
michael@0 | 112 | return value == 128; |
michael@0 | 113 | }, |
michael@0 | 114 | |
michael@0 | 115 | /** |
michael@0 | 116 | * @param data |
michael@0 | 117 | * A wrapped object to store encoded raw data. |
michael@0 | 118 | * @param value |
michael@0 | 119 | * A boolean value to be encoded. |
michael@0 | 120 | */ |
michael@0 | 121 | encode: function(data, value) { |
michael@0 | 122 | WSP.Octet.encode(data, value ? 128 : 129); |
michael@0 | 123 | }, |
michael@0 | 124 | }; |
michael@0 | 125 | |
michael@0 | 126 | /** |
michael@0 | 127 | * MMS Address |
michael@0 | 128 | * |
michael@0 | 129 | * address = email | device-address | alphanum-shortcode | num-shortcode |
michael@0 | 130 | * |
michael@0 | 131 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 8 |
michael@0 | 132 | */ |
michael@0 | 133 | this.Address = { |
michael@0 | 134 | /** |
michael@0 | 135 | * @param data |
michael@0 | 136 | * A wrapped object to store encoded raw data. |
michael@0 | 137 | * |
michael@0 | 138 | * @return An object of two string-typed attributes: address and type. |
michael@0 | 139 | */ |
michael@0 | 140 | decode: function(data) { |
michael@0 | 141 | let str = EncodedStringValue.decode(data); |
michael@0 | 142 | |
michael@0 | 143 | let result; |
michael@0 | 144 | if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null) |
michael@0 | 145 | || ((result = str.match(this.REGEXP_DECODE_IPV4)) != null) |
michael@0 | 146 | || ((result = str.match(this.REGEXP_DECODE_IPV6)) != null) |
michael@0 | 147 | || ((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)) { |
michael@0 | 148 | return {address: result[1], type: result[2]}; |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | let type; |
michael@0 | 152 | if (str.match(this.REGEXP_NUM)) { |
michael@0 | 153 | type = "num"; |
michael@0 | 154 | } else if (str.match(this.REGEXP_ALPHANUM)) { |
michael@0 | 155 | type = "alphanum"; |
michael@0 | 156 | } else if (str.indexOf("@") > 0) { |
michael@0 | 157 | // E-mail should match the definition of `mailbox` as described in section |
michael@0 | 158 | // 3.4 of RFC2822, but excluding the obsolete definitions as indicated by |
michael@0 | 159 | // the "obs-" prefix. Here we match only a `@` character. |
michael@0 | 160 | type = "email"; |
michael@0 | 161 | } else { |
michael@0 | 162 | throw new WSP.CodeError("Address: invalid address"); |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | return {address: str, type: type}; |
michael@0 | 166 | }, |
michael@0 | 167 | |
michael@0 | 168 | /** |
michael@0 | 169 | * @param data |
michael@0 | 170 | * A wrapped object to store encoded raw data. |
michael@0 | 171 | * @param value |
michael@0 | 172 | * An object of two string-typed attributes: address and type. |
michael@0 | 173 | */ |
michael@0 | 174 | encode: function(data, value) { |
michael@0 | 175 | if (!value || !value.type || !value.address) { |
michael@0 | 176 | throw new WSP.CodeError("Address: invalid value"); |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | let str; |
michael@0 | 180 | switch (value.type) { |
michael@0 | 181 | case "email": |
michael@0 | 182 | if (value.address.indexOf("@") > 0) { |
michael@0 | 183 | str = value.address; |
michael@0 | 184 | } |
michael@0 | 185 | break; |
michael@0 | 186 | case "num": |
michael@0 | 187 | if (value.address.match(this.REGEXP_NUM)) { |
michael@0 | 188 | str = value.address; |
michael@0 | 189 | } |
michael@0 | 190 | break; |
michael@0 | 191 | case "alphanum": |
michael@0 | 192 | if (value.address.match(this.REGEXP_ALPHANUM)) { |
michael@0 | 193 | str = value.address; |
michael@0 | 194 | } |
michael@0 | 195 | break; |
michael@0 | 196 | case "IPv4": |
michael@0 | 197 | if (value.address.match(this.REGEXP_ENCODE_IPV4)) { |
michael@0 | 198 | str = value.address + "/TYPE=IPv4"; |
michael@0 | 199 | } |
michael@0 | 200 | break; |
michael@0 | 201 | case "IPv6": |
michael@0 | 202 | if (value.address.match(this.REGEXP_ENCODE_IPV6)) { |
michael@0 | 203 | str = value.address + "/TYPE=IPv6"; |
michael@0 | 204 | } |
michael@0 | 205 | break; |
michael@0 | 206 | case "PLMN": |
michael@0 | 207 | if (value.address.match(this.REGEXP_ENCODE_PLMN)) { |
michael@0 | 208 | str = value.address + "/TYPE=PLMN"; |
michael@0 | 209 | } |
michael@0 | 210 | break; |
michael@0 | 211 | default: |
michael@0 | 212 | if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE) |
michael@0 | 213 | && value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) { |
michael@0 | 214 | str = value.address + "/TYPE=" + value.type; |
michael@0 | 215 | } |
michael@0 | 216 | break; |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | if (!str) { |
michael@0 | 220 | throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value)); |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | EncodedStringValue.encode(data, str); |
michael@0 | 224 | }, |
michael@0 | 225 | |
michael@0 | 226 | /** |
michael@0 | 227 | * @param address |
michael@0 | 228 | * Address string which want to find the type. |
michael@0 | 229 | * |
michael@0 | 230 | * @return Address type. |
michael@0 | 231 | */ |
michael@0 | 232 | resolveType: function(address) { |
michael@0 | 233 | if (address.match(this.REGEXP_EMAIL)) { |
michael@0 | 234 | return "email"; |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | if (address.match(this.REGEXP_IPV4)) { |
michael@0 | 238 | return "IPv4"; |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | if (address.match(this.REGEXP_IPV6)) { |
michael@0 | 242 | return "IPv6"; |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | let normalizedAddress = PhoneNumberUtils.normalize(address, false); |
michael@0 | 246 | if (PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) { |
michael@0 | 247 | return "PLMN"; |
michael@0 | 248 | } |
michael@0 | 249 | |
michael@0 | 250 | return "Others"; |
michael@0 | 251 | }, |
michael@0 | 252 | }; |
michael@0 | 253 | |
michael@0 | 254 | defineLazyRegExp(Address, "REGEXP_DECODE_PLMN", "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$"); |
michael@0 | 255 | defineLazyRegExp(Address, "REGEXP_DECODE_IPV4", "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$"); |
michael@0 | 256 | defineLazyRegExp(Address, "REGEXP_DECODE_IPV6", "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$"); |
michael@0 | 257 | defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM", "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$"); |
michael@0 | 258 | defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN", "^\\+?[\\d.-]+$"); |
michael@0 | 259 | defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$"); |
michael@0 | 260 | defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$"); |
michael@0 | 261 | defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$"); |
michael@0 | 262 | defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$"); |
michael@0 | 263 | defineLazyRegExp(Address, "REGEXP_NUM", "^[\\+*#]\\d+$"); |
michael@0 | 264 | defineLazyRegExp(Address, "REGEXP_ALPHANUM", "^\\w+$"); |
michael@0 | 265 | defineLazyRegExp(Address, "REGEXP_PLMN", "^\\?[\\d.-]$"); |
michael@0 | 266 | defineLazyRegExp(Address, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$"); |
michael@0 | 267 | defineLazyRegExp(Address, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$"); |
michael@0 | 268 | defineLazyRegExp(Address, "REGEXP_EMAIL", "@"); |
michael@0 | 269 | |
michael@0 | 270 | /** |
michael@0 | 271 | * Header-field = MMS-header | Application-header |
michael@0 | 272 | * |
michael@0 | 273 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2 |
michael@0 | 274 | */ |
michael@0 | 275 | this.HeaderField = { |
michael@0 | 276 | /** |
michael@0 | 277 | * @param data |
michael@0 | 278 | * A wrapped object containing raw PDU data. |
michael@0 | 279 | * @param options |
michael@0 | 280 | * Extra context for decoding. |
michael@0 | 281 | * |
michael@0 | 282 | * @return A decoded object containing `name` and `value` properties or null |
michael@0 | 283 | * in case of a failed parsing. The `name` property must be a string, |
michael@0 | 284 | * but the `value` property can be many different types depending on |
michael@0 | 285 | * `name`. |
michael@0 | 286 | */ |
michael@0 | 287 | decode: function(data, options) { |
michael@0 | 288 | return WSP.decodeAlternatives(data, options, |
michael@0 | 289 | MmsHeader, WSP.ApplicationHeader); |
michael@0 | 290 | }, |
michael@0 | 291 | |
michael@0 | 292 | /** |
michael@0 | 293 | * @param data |
michael@0 | 294 | * A wrapped object to store encoded raw data. |
michael@0 | 295 | * @param octet |
michael@0 | 296 | * Octet value to be encoded. |
michael@0 | 297 | * @param options |
michael@0 | 298 | * Extra context for encoding. |
michael@0 | 299 | */ |
michael@0 | 300 | encode: function(data, value, options) { |
michael@0 | 301 | WSP.encodeAlternatives(data, value, options, |
michael@0 | 302 | MmsHeader, WSP.ApplicationHeader); |
michael@0 | 303 | }, |
michael@0 | 304 | }; |
michael@0 | 305 | |
michael@0 | 306 | /** |
michael@0 | 307 | * MMS-header = MMS-field-name MMS-value |
michael@0 | 308 | * MMS-field-name = Short-integer |
michael@0 | 309 | * |
michael@0 | 310 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2 |
michael@0 | 311 | */ |
michael@0 | 312 | this.MmsHeader = { |
michael@0 | 313 | /** |
michael@0 | 314 | * @param data |
michael@0 | 315 | * A wrapped object containing raw PDU data. |
michael@0 | 316 | * @param options |
michael@0 | 317 | * Extra context for decoding. |
michael@0 | 318 | * |
michael@0 | 319 | * @return A decoded object containing `name` and `value` properties or null |
michael@0 | 320 | * in case of a failed parsing. The `name` property must be a string, |
michael@0 | 321 | * but the `value` property can be many different types depending on |
michael@0 | 322 | * `name`. |
michael@0 | 323 | * |
michael@0 | 324 | * @throws NotWellKnownEncodingError if decoded well-known header field |
michael@0 | 325 | * number is not registered or supported. |
michael@0 | 326 | */ |
michael@0 | 327 | decode: function(data, options) { |
michael@0 | 328 | let index = WSP.ShortInteger.decode(data); |
michael@0 | 329 | |
michael@0 | 330 | let entry = MMS_HEADER_FIELDS[index]; |
michael@0 | 331 | if (!entry) { |
michael@0 | 332 | throw new WSP.NotWellKnownEncodingError( |
michael@0 | 333 | "MMS-header: not well known header " + index); |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | let cur = data.offset, value; |
michael@0 | 337 | try { |
michael@0 | 338 | value = entry.coder.decode(data, options); |
michael@0 | 339 | } catch (e) { |
michael@0 | 340 | data.offset = cur; |
michael@0 | 341 | |
michael@0 | 342 | value = WSP.skipValue(data); |
michael@0 | 343 | debug("Skip malformed well known header: " |
michael@0 | 344 | + JSON.stringify({name: entry.name, value: value})); |
michael@0 | 345 | |
michael@0 | 346 | return null; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | return { |
michael@0 | 350 | name: entry.name, |
michael@0 | 351 | value: value, |
michael@0 | 352 | }; |
michael@0 | 353 | }, |
michael@0 | 354 | |
michael@0 | 355 | /** |
michael@0 | 356 | * @param data |
michael@0 | 357 | * A wrapped object to store encoded raw data. |
michael@0 | 358 | * @param header |
michael@0 | 359 | * An object containing two attributes: a string-typed `name` and a |
michael@0 | 360 | * `value` of arbitrary type. |
michael@0 | 361 | * |
michael@0 | 362 | * @throws CodeError if got an empty header name. |
michael@0 | 363 | * @throws NotWellKnownEncodingError if the well-known header field number is |
michael@0 | 364 | * not registered or supported. |
michael@0 | 365 | */ |
michael@0 | 366 | encode: function(data, header) { |
michael@0 | 367 | if (!header.name) { |
michael@0 | 368 | throw new WSP.CodeError("MMS-header: empty header name"); |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | let entry = MMS_HEADER_FIELDS[header.name.toLowerCase()]; |
michael@0 | 372 | if (!entry) { |
michael@0 | 373 | throw new WSP.NotWellKnownEncodingError( |
michael@0 | 374 | "MMS-header: not well known header " + header.name); |
michael@0 | 375 | } |
michael@0 | 376 | |
michael@0 | 377 | WSP.ShortInteger.encode(data, entry.number); |
michael@0 | 378 | entry.coder.encode(data, header.value); |
michael@0 | 379 | }, |
michael@0 | 380 | }; |
michael@0 | 381 | |
michael@0 | 382 | /** |
michael@0 | 383 | * Cancel-status-value = Cancel Request Successfully received | |
michael@0 | 384 | * Cancel Request corrupted |
michael@0 | 385 | * |
michael@0 | 386 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.7 |
michael@0 | 387 | */ |
michael@0 | 388 | this.CancelStatusValue = new RangedValue("Cancel-status-value", 128, 129); |
michael@0 | 389 | |
michael@0 | 390 | /** |
michael@0 | 391 | * Content-class-value = text | image-basic| image-rich | video-basic | |
michael@0 | 392 | * video-rich | megapixel | content-basic | content-rich |
michael@0 | 393 | * |
michael@0 | 394 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9 |
michael@0 | 395 | */ |
michael@0 | 396 | this.ContentClassValue = new RangedValue("Content-class-value", 128, 135); |
michael@0 | 397 | |
michael@0 | 398 | /** |
michael@0 | 399 | * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: |
michael@0 | 400 | * |
michael@0 | 401 | * Content-location-value = Uri-value |
michael@0 | 402 | * |
michael@0 | 403 | * When used in the M-Mbox-Delete.conf and M-Delete.conf PDU: |
michael@0 | 404 | * |
michael@0 | 405 | * Content-location-Del-value = Value-length Status-count-value Content-location-value |
michael@0 | 406 | * Status-count-value = Integer-value |
michael@0 | 407 | * |
michael@0 | 408 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.10 |
michael@0 | 409 | */ |
michael@0 | 410 | this.ContentLocationValue = { |
michael@0 | 411 | /** |
michael@0 | 412 | * @param data |
michael@0 | 413 | * A wrapped object containing raw PDU data. |
michael@0 | 414 | * @param options |
michael@0 | 415 | * Extra context for decoding. |
michael@0 | 416 | * |
michael@0 | 417 | * @return A decoded object containing `uri` and conditional `statusCount` |
michael@0 | 418 | * properties. |
michael@0 | 419 | */ |
michael@0 | 420 | decode: function(data, options) { |
michael@0 | 421 | let type = WSP.ensureHeader(options, "x-mms-message-type"); |
michael@0 | 422 | |
michael@0 | 423 | let result = {}; |
michael@0 | 424 | if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF) |
michael@0 | 425 | || (type == MMS_PDU_TYPE_DELETE_CONF)) { |
michael@0 | 426 | let length = WSP.ValueLength.decode(data); |
michael@0 | 427 | let end = data.offset + length; |
michael@0 | 428 | |
michael@0 | 429 | result.statusCount = WSP.IntegerValue.decode(data); |
michael@0 | 430 | result.uri = WSP.UriValue.decode(data); |
michael@0 | 431 | |
michael@0 | 432 | if (data.offset != end) { |
michael@0 | 433 | data.offset = end; |
michael@0 | 434 | } |
michael@0 | 435 | } else { |
michael@0 | 436 | result.uri = WSP.UriValue.decode(data); |
michael@0 | 437 | } |
michael@0 | 438 | |
michael@0 | 439 | return result; |
michael@0 | 440 | }, |
michael@0 | 441 | }; |
michael@0 | 442 | |
michael@0 | 443 | /** |
michael@0 | 444 | * Element-Descriptor-value = Value-length Content-Reference-value *(Parameter) |
michael@0 | 445 | * Content-Reference-value = Text-string |
michael@0 | 446 | * |
michael@0 | 447 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18 |
michael@0 | 448 | */ |
michael@0 | 449 | this.ElementDescriptorValue = { |
michael@0 | 450 | /** |
michael@0 | 451 | * @param data |
michael@0 | 452 | * A wrapped object containing raw PDU data. |
michael@0 | 453 | * |
michael@0 | 454 | * @return A decoded object containing a string property `contentReference` |
michael@0 | 455 | * and an optinal `params` name-value map. |
michael@0 | 456 | */ |
michael@0 | 457 | decode: function(data) { |
michael@0 | 458 | let length = WSP.ValueLength.decode(data); |
michael@0 | 459 | let end = data.offset + length; |
michael@0 | 460 | |
michael@0 | 461 | let result = {}; |
michael@0 | 462 | result.contentReference = WSP.TextString.decode(data); |
michael@0 | 463 | if (data.offset < end) { |
michael@0 | 464 | result.params = Parameter.decodeMultiple(data, end); |
michael@0 | 465 | } |
michael@0 | 466 | |
michael@0 | 467 | if (data.offset != end) { |
michael@0 | 468 | // Explicitly seek to end in case of skipped parameters. |
michael@0 | 469 | data.offset = end; |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | return result; |
michael@0 | 473 | }, |
michael@0 | 474 | }; |
michael@0 | 475 | |
michael@0 | 476 | /** |
michael@0 | 477 | * OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18: |
michael@0 | 478 | * `For well-known parameter names binary tokens MUST be used as defined in |
michael@0 | 479 | * Table 27.` So we can't reuse that of WSP. |
michael@0 | 480 | * |
michael@0 | 481 | * Parameter = Parameter-name Parameter-value |
michael@0 | 482 | * Parameter-name = Short-integer | Text-string |
michael@0 | 483 | * Parameter-value = Constrained-encoding | Text-string |
michael@0 | 484 | * |
michael@0 | 485 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18 |
michael@0 | 486 | */ |
michael@0 | 487 | this.Parameter = { |
michael@0 | 488 | /** |
michael@0 | 489 | * @param data |
michael@0 | 490 | * A wrapped object containing raw PDU data. |
michael@0 | 491 | * |
michael@0 | 492 | * @return A decoded string. |
michael@0 | 493 | * |
michael@0 | 494 | * @throws NotWellKnownEncodingError if decoded well-known parameter number |
michael@0 | 495 | * is not registered or supported. |
michael@0 | 496 | */ |
michael@0 | 497 | decodeParameterName: function(data) { |
michael@0 | 498 | let begin = data.offset; |
michael@0 | 499 | let number; |
michael@0 | 500 | try { |
michael@0 | 501 | number = WSP.ShortInteger.decode(data); |
michael@0 | 502 | } catch (e) { |
michael@0 | 503 | data.offset = begin; |
michael@0 | 504 | return WSP.TextString.decode(data).toLowerCase(); |
michael@0 | 505 | } |
michael@0 | 506 | |
michael@0 | 507 | let entry = MMS_WELL_KNOWN_PARAMS[number]; |
michael@0 | 508 | if (!entry) { |
michael@0 | 509 | throw new WSP.NotWellKnownEncodingError( |
michael@0 | 510 | "Parameter-name: not well known parameter " + number); |
michael@0 | 511 | } |
michael@0 | 512 | |
michael@0 | 513 | return entry.name; |
michael@0 | 514 | }, |
michael@0 | 515 | |
michael@0 | 516 | /** |
michael@0 | 517 | * @param data |
michael@0 | 518 | * A wrapped object containing raw PDU data. |
michael@0 | 519 | * |
michael@0 | 520 | * @return A decoded object containing `name` and `value` properties or null |
michael@0 | 521 | * in case of a failed parsing. The `name` property must be a string, |
michael@0 | 522 | * but the `value` property can be many different types depending on |
michael@0 | 523 | * `name`. |
michael@0 | 524 | */ |
michael@0 | 525 | decode: function(data) { |
michael@0 | 526 | let name = this.decodeParameterName(data); |
michael@0 | 527 | let value = WSP.decodeAlternatives(data, null, |
michael@0 | 528 | WSP.ConstrainedEncoding, WSP.TextString); |
michael@0 | 529 | return { |
michael@0 | 530 | name: name, |
michael@0 | 531 | value: value, |
michael@0 | 532 | }; |
michael@0 | 533 | }, |
michael@0 | 534 | |
michael@0 | 535 | /** |
michael@0 | 536 | * @param data |
michael@0 | 537 | * A wrapped object containing raw PDU data. |
michael@0 | 538 | * @param end |
michael@0 | 539 | * Ending offset of following parameters. |
michael@0 | 540 | * |
michael@0 | 541 | * @return An array of decoded objects. |
michael@0 | 542 | */ |
michael@0 | 543 | decodeMultiple: function(data, end) { |
michael@0 | 544 | let params, param; |
michael@0 | 545 | |
michael@0 | 546 | while (data.offset < end) { |
michael@0 | 547 | try { |
michael@0 | 548 | param = this.decode(data); |
michael@0 | 549 | } catch (e) { |
michael@0 | 550 | break; |
michael@0 | 551 | } |
michael@0 | 552 | if (param) { |
michael@0 | 553 | if (!params) { |
michael@0 | 554 | params = {}; |
michael@0 | 555 | } |
michael@0 | 556 | params[param.name] = param.value; |
michael@0 | 557 | } |
michael@0 | 558 | } |
michael@0 | 559 | |
michael@0 | 560 | return params; |
michael@0 | 561 | }, |
michael@0 | 562 | |
michael@0 | 563 | /** |
michael@0 | 564 | * @param data |
michael@0 | 565 | * A wrapped object to store encoded raw data. |
michael@0 | 566 | * @param param |
michael@0 | 567 | * An object containing two attributes: `name` and `value`. |
michael@0 | 568 | * @param options |
michael@0 | 569 | * Extra context for encoding. |
michael@0 | 570 | */ |
michael@0 | 571 | encode: function(data, param, options) { |
michael@0 | 572 | if (!param || !param.name) { |
michael@0 | 573 | throw new WSP.CodeError("Parameter-name: empty param name"); |
michael@0 | 574 | } |
michael@0 | 575 | |
michael@0 | 576 | let entry = MMS_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; |
michael@0 | 577 | if (entry) { |
michael@0 | 578 | WSP.ShortInteger.encode(data, entry.number); |
michael@0 | 579 | } else { |
michael@0 | 580 | WSP.TextString.encode(data, param.name); |
michael@0 | 581 | } |
michael@0 | 582 | |
michael@0 | 583 | WSP.encodeAlternatives(data, param.value, options, |
michael@0 | 584 | WSP.ConstrainedEncoding, WSP.TextString); |
michael@0 | 585 | }, |
michael@0 | 586 | }; |
michael@0 | 587 | |
michael@0 | 588 | /** |
michael@0 | 589 | * The Char-set values are registered by IANA as MIBEnum value and SHALL be |
michael@0 | 590 | * encoded as Integer-value. |
michael@0 | 591 | * |
michael@0 | 592 | * Encoded-string-value = Text-string | Value-length Char-set Text-string |
michael@0 | 593 | * |
michael@0 | 594 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19 |
michael@0 | 595 | * @see OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1 |
michael@0 | 596 | */ |
michael@0 | 597 | this.EncodedStringValue = { |
michael@0 | 598 | /** |
michael@0 | 599 | * @param data |
michael@0 | 600 | * A wrapped object containing raw PDU data. |
michael@0 | 601 | * |
michael@0 | 602 | * @return Decoded string. |
michael@0 | 603 | * |
michael@0 | 604 | * @throws CodeError if the raw octets cannot be converted. |
michael@0 | 605 | * @throws NotWellKnownEncodingError if decoded well-known charset number is |
michael@0 | 606 | * not registered or supported. |
michael@0 | 607 | */ |
michael@0 | 608 | decodeCharsetEncodedString: function(data) { |
michael@0 | 609 | let length = WSP.ValueLength.decode(data); |
michael@0 | 610 | let end = data.offset + length; |
michael@0 | 611 | |
michael@0 | 612 | let charset = WSP.IntegerValue.decode(data); |
michael@0 | 613 | let entry = WSP.WSP_WELL_KNOWN_CHARSETS[charset]; |
michael@0 | 614 | if (!entry) { |
michael@0 | 615 | throw new WSP.NotWellKnownEncodingError( |
michael@0 | 616 | "Charset-encoded-string: not well known charset " + charset); |
michael@0 | 617 | } |
michael@0 | 618 | |
michael@0 | 619 | let str; |
michael@0 | 620 | if (entry.converter) { |
michael@0 | 621 | // Read a possible string quote(<Octet 127>). |
michael@0 | 622 | let begin = data.offset; |
michael@0 | 623 | if (WSP.Octet.decode(data) != 127) { |
michael@0 | 624 | data.offset = begin; |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | let raw = WSP.Octet.decodeMultiple(data, end - 1); |
michael@0 | 628 | // Read NUL character. |
michael@0 | 629 | WSP.Octet.decodeEqualTo(data, 0); |
michael@0 | 630 | |
michael@0 | 631 | if (!raw) { |
michael@0 | 632 | str = ""; |
michael@0 | 633 | } else { |
michael@0 | 634 | let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
michael@0 | 635 | .createInstance(Ci.nsIScriptableUnicodeConverter); |
michael@0 | 636 | conv.charset = entry.converter; |
michael@0 | 637 | try { |
michael@0 | 638 | str = conv.convertFromByteArray(raw, raw.length); |
michael@0 | 639 | } catch (e) { |
michael@0 | 640 | throw new WSP.CodeError("Charset-encoded-string: " + e.message); |
michael@0 | 641 | } |
michael@0 | 642 | } |
michael@0 | 643 | } else { |
michael@0 | 644 | str = WSP.TextString.decode(data); |
michael@0 | 645 | } |
michael@0 | 646 | |
michael@0 | 647 | if (data.offset != end) { |
michael@0 | 648 | data.offset = end; |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | return str; |
michael@0 | 652 | }, |
michael@0 | 653 | |
michael@0 | 654 | /** |
michael@0 | 655 | * @param data |
michael@0 | 656 | * A wrapped object containing raw PDU data. |
michael@0 | 657 | * |
michael@0 | 658 | * @return Decoded string. |
michael@0 | 659 | */ |
michael@0 | 660 | decode: function(data) { |
michael@0 | 661 | let begin = data.offset; |
michael@0 | 662 | try { |
michael@0 | 663 | return WSP.TextString.decode(data); |
michael@0 | 664 | } catch (e) { |
michael@0 | 665 | data.offset = begin; |
michael@0 | 666 | return this.decodeCharsetEncodedString(data); |
michael@0 | 667 | } |
michael@0 | 668 | }, |
michael@0 | 669 | |
michael@0 | 670 | /** |
michael@0 | 671 | * Always encode target string with UTF-8 encoding. |
michael@0 | 672 | * |
michael@0 | 673 | * @param data |
michael@0 | 674 | * A wrapped object to store encoded raw data. |
michael@0 | 675 | * @param str |
michael@0 | 676 | * A string. |
michael@0 | 677 | */ |
michael@0 | 678 | encodeCharsetEncodedString: function(data, str) { |
michael@0 | 679 | let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
michael@0 | 680 | .createInstance(Ci.nsIScriptableUnicodeConverter); |
michael@0 | 681 | // `When the text string cannot be represented as us-ascii, the character |
michael@0 | 682 | // set SHALL be encoded as utf-8(IANA MIBenum 106) which has unique byte |
michael@0 | 683 | // ordering.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1 |
michael@0 | 684 | conv.charset = "UTF-8"; |
michael@0 | 685 | |
michael@0 | 686 | let raw; |
michael@0 | 687 | try { |
michael@0 | 688 | raw = conv.convertToByteArray(str); |
michael@0 | 689 | } catch (e) { |
michael@0 | 690 | throw new WSP.CodeError("Charset-encoded-string: " + e.message); |
michael@0 | 691 | } |
michael@0 | 692 | |
michael@0 | 693 | let length = raw.length + 2; // Charset number and NUL character |
michael@0 | 694 | // Prepend <Octet 127> if necessary. |
michael@0 | 695 | if (raw[0] >= 128) { |
michael@0 | 696 | ++length; |
michael@0 | 697 | } |
michael@0 | 698 | |
michael@0 | 699 | WSP.ValueLength.encode(data, length); |
michael@0 | 700 | |
michael@0 | 701 | let entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]; |
michael@0 | 702 | WSP.IntegerValue.encode(data, entry.number); |
michael@0 | 703 | |
michael@0 | 704 | if (raw[0] >= 128) { |
michael@0 | 705 | WSP.Octet.encode(data, 127); |
michael@0 | 706 | } |
michael@0 | 707 | WSP.Octet.encodeMultiple(data, raw); |
michael@0 | 708 | WSP.Octet.encode(data, 0); |
michael@0 | 709 | }, |
michael@0 | 710 | |
michael@0 | 711 | /** |
michael@0 | 712 | * @param data |
michael@0 | 713 | * A wrapped object to store encoded raw data. |
michael@0 | 714 | * @param str |
michael@0 | 715 | * A string. |
michael@0 | 716 | */ |
michael@0 | 717 | encode: function(data, str) { |
michael@0 | 718 | let begin = data.offset; |
michael@0 | 719 | try { |
michael@0 | 720 | // Quoted from OMA-TS-MMS-CONF-V1_3-20110913-A: |
michael@0 | 721 | // Some of the MMS headers have been defined as "Encoded-string-value". |
michael@0 | 722 | // The character set IANA MIBEnum value in these headers SHALL be |
michael@0 | 723 | // encoded as Integer-value ([WAPWSP] section 8.4.2.3). The character |
michael@0 | 724 | // set us-ascii (IANA MIBenum 3) SHALL always be accepted. If the |
michael@0 | 725 | // character set is not specified (simple Text-string encoding) the |
michael@0 | 726 | // character set SHALL be identified as us-ascii (lower half of ISO |
michael@0 | 727 | // 8859-1 [ISO8859-1]). When the text string cannot be represented as |
michael@0 | 728 | // us-ascii, the character set SHALL be encoded as utf-8 (IANA MIBenum |
michael@0 | 729 | // 106) which has unique byte ordering. |
michael@0 | 730 | WSP.TextString.encode(data, str, true); |
michael@0 | 731 | } catch (e) { |
michael@0 | 732 | data.offset = begin; |
michael@0 | 733 | this.encodeCharsetEncodedString(data, str); |
michael@0 | 734 | } |
michael@0 | 735 | }, |
michael@0 | 736 | }; |
michael@0 | 737 | |
michael@0 | 738 | /** |
michael@0 | 739 | * Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value) |
michael@0 | 740 | * Absolute-token = <Octet 128> |
michael@0 | 741 | * Relative-token = <Octet 129> |
michael@0 | 742 | * |
michael@0 | 743 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.20 |
michael@0 | 744 | */ |
michael@0 | 745 | this.ExpiryValue = { |
michael@0 | 746 | /** |
michael@0 | 747 | * @param data |
michael@0 | 748 | * A wrapped object containing raw PDU data. |
michael@0 | 749 | * |
michael@0 | 750 | * @return A Date object for absolute expiry or an integer for relative one. |
michael@0 | 751 | * |
michael@0 | 752 | * @throws CodeError if decoded token equals to neither 128 nor 129. |
michael@0 | 753 | */ |
michael@0 | 754 | decode: function(data) { |
michael@0 | 755 | let length = WSP.ValueLength.decode(data); |
michael@0 | 756 | let end = data.offset + length; |
michael@0 | 757 | |
michael@0 | 758 | let token = WSP.Octet.decode(data); |
michael@0 | 759 | if ((token != 128) && (token != 129)) { |
michael@0 | 760 | throw new WSP.CodeError("Expiry-value: invalid token " + token); |
michael@0 | 761 | } |
michael@0 | 762 | |
michael@0 | 763 | let result; |
michael@0 | 764 | if (token == 128) { |
michael@0 | 765 | result = WSP.DateValue.decode(data); |
michael@0 | 766 | } else { |
michael@0 | 767 | result = WSP.DeltaSecondsValue.decode(data); |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | if (data.offset != end) { |
michael@0 | 771 | data.offset = end; |
michael@0 | 772 | } |
michael@0 | 773 | |
michael@0 | 774 | return result; |
michael@0 | 775 | }, |
michael@0 | 776 | |
michael@0 | 777 | /** |
michael@0 | 778 | * @param data |
michael@0 | 779 | * A wrapped object to store encoded raw data. |
michael@0 | 780 | * @param value |
michael@0 | 781 | * A Date object for absolute expiry or an integer for relative one. |
michael@0 | 782 | */ |
michael@0 | 783 | encode: function(data, value) { |
michael@0 | 784 | let isDate, begin = data.offset; |
michael@0 | 785 | if (value instanceof Date) { |
michael@0 | 786 | isDate = true; |
michael@0 | 787 | WSP.DateValue.encode(data, value); |
michael@0 | 788 | } else if (typeof value == "number") { |
michael@0 | 789 | isDate = false; |
michael@0 | 790 | WSP.DeltaSecondsValue.encode(data, value); |
michael@0 | 791 | } else { |
michael@0 | 792 | throw new CodeError("Expiry-value: invalid value type"); |
michael@0 | 793 | } |
michael@0 | 794 | |
michael@0 | 795 | // Calculate how much octets will be written and seek back. |
michael@0 | 796 | // TODO: use memmove, see bug 730873 |
michael@0 | 797 | let len = data.offset - begin; |
michael@0 | 798 | data.offset = begin; |
michael@0 | 799 | |
michael@0 | 800 | WSP.ValueLength.encode(data, len + 1); |
michael@0 | 801 | if (isDate) { |
michael@0 | 802 | WSP.Octet.encode(data, 128); |
michael@0 | 803 | WSP.DateValue.encode(data, value); |
michael@0 | 804 | } else { |
michael@0 | 805 | WSP.Octet.encode(data, 129); |
michael@0 | 806 | WSP.DeltaSecondsValue.encode(data, value); |
michael@0 | 807 | } |
michael@0 | 808 | }, |
michael@0 | 809 | }; |
michael@0 | 810 | |
michael@0 | 811 | /** |
michael@0 | 812 | * From-value = Value-length (Address-present-token Address | Insert-address-token) |
michael@0 | 813 | * Address-present-token = <Octet 128> |
michael@0 | 814 | * Insert-address-token = <Octet 129> |
michael@0 | 815 | * |
michael@0 | 816 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.21 |
michael@0 | 817 | */ |
michael@0 | 818 | this.FromValue = { |
michael@0 | 819 | /** |
michael@0 | 820 | * @param data |
michael@0 | 821 | * A wrapped object containing raw PDU data. |
michael@0 | 822 | * |
michael@0 | 823 | * @return A decoded Address-value or null for MMS Proxy-Relay Insert-Address |
michael@0 | 824 | * mode. |
michael@0 | 825 | * |
michael@0 | 826 | * @throws CodeError if decoded token equals to neither 128 nor 129. |
michael@0 | 827 | */ |
michael@0 | 828 | decode: function(data) { |
michael@0 | 829 | let length = WSP.ValueLength.decode(data); |
michael@0 | 830 | let end = data.offset + length; |
michael@0 | 831 | |
michael@0 | 832 | let token = WSP.Octet.decode(data); |
michael@0 | 833 | if ((token != 128) && (token != 129)) { |
michael@0 | 834 | throw new WSP.CodeError("From-value: invalid token " + token); |
michael@0 | 835 | } |
michael@0 | 836 | |
michael@0 | 837 | let result = null; |
michael@0 | 838 | if (token == 128) { |
michael@0 | 839 | result = Address.decode(data); |
michael@0 | 840 | } |
michael@0 | 841 | |
michael@0 | 842 | if (data.offset != end) { |
michael@0 | 843 | data.offset = end; |
michael@0 | 844 | } |
michael@0 | 845 | |
michael@0 | 846 | return result; |
michael@0 | 847 | }, |
michael@0 | 848 | |
michael@0 | 849 | /** |
michael@0 | 850 | * @param data |
michael@0 | 851 | * A wrapped object to store encoded raw data. |
michael@0 | 852 | * @param value |
michael@0 | 853 | * A Address-value or null for MMS Proxy-Relay Insert-Address mode. |
michael@0 | 854 | */ |
michael@0 | 855 | encode: function(data, value) { |
michael@0 | 856 | if (!value) { |
michael@0 | 857 | WSP.ValueLength.encode(data, 1); |
michael@0 | 858 | WSP.Octet.encode(data, 129); |
michael@0 | 859 | return; |
michael@0 | 860 | } |
michael@0 | 861 | |
michael@0 | 862 | // Calculate how much octets will be written and seek back. |
michael@0 | 863 | // TODO: use memmove, see bug 730873 |
michael@0 | 864 | let begin = data.offset; |
michael@0 | 865 | Address.encode(data, value); |
michael@0 | 866 | let len = data.offset - begin; |
michael@0 | 867 | data.offset = begin; |
michael@0 | 868 | |
michael@0 | 869 | WSP.ValueLength.encode(data, len + 1); |
michael@0 | 870 | WSP.Octet.encode(data, 128); |
michael@0 | 871 | Address.encode(data, value); |
michael@0 | 872 | }, |
michael@0 | 873 | }; |
michael@0 | 874 | |
michael@0 | 875 | /** |
michael@0 | 876 | * Previously-sent-by-value = Value-length Forwarded-count-value Address |
michael@0 | 877 | * Forwarded-count-value = Integer-value |
michael@0 | 878 | * |
michael@0 | 879 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23 |
michael@0 | 880 | */ |
michael@0 | 881 | this.PreviouslySentByValue = { |
michael@0 | 882 | /** |
michael@0 | 883 | * @param data |
michael@0 | 884 | * A wrapped object containing raw PDU data. |
michael@0 | 885 | * |
michael@0 | 886 | * @return Decoded object containing an integer `forwardedCount` and an |
michael@0 | 887 | * string-typed `originator` attributes. |
michael@0 | 888 | */ |
michael@0 | 889 | decode: function(data) { |
michael@0 | 890 | let length = WSP.ValueLength.decode(data); |
michael@0 | 891 | let end = data.offset + length; |
michael@0 | 892 | |
michael@0 | 893 | let result = {}; |
michael@0 | 894 | result.forwardedCount = WSP.IntegerValue.decode(data); |
michael@0 | 895 | result.originator = Address.decode(data); |
michael@0 | 896 | |
michael@0 | 897 | if (data.offset != end) { |
michael@0 | 898 | data.offset = end; |
michael@0 | 899 | } |
michael@0 | 900 | |
michael@0 | 901 | return result; |
michael@0 | 902 | }, |
michael@0 | 903 | }; |
michael@0 | 904 | |
michael@0 | 905 | /** |
michael@0 | 906 | * Previously-sent-date-value = Value-length Forwarded-count-value Date-value |
michael@0 | 907 | * |
michael@0 | 908 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23 |
michael@0 | 909 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.24 |
michael@0 | 910 | */ |
michael@0 | 911 | this.PreviouslySentDateValue = { |
michael@0 | 912 | /** |
michael@0 | 913 | * @param data |
michael@0 | 914 | * A wrapped object containing raw PDU data. |
michael@0 | 915 | * |
michael@0 | 916 | * @return Decoded object containing an integer `forwardedCount` and an |
michael@0 | 917 | * Date-typed `timestamp` attributes. |
michael@0 | 918 | */ |
michael@0 | 919 | decode: function(data) { |
michael@0 | 920 | let length = WSP.ValueLength.decode(data); |
michael@0 | 921 | let end = data.offset + length; |
michael@0 | 922 | |
michael@0 | 923 | let result = {}; |
michael@0 | 924 | result.forwardedCount = WSP.IntegerValue.decode(data); |
michael@0 | 925 | result.timestamp = WSP.DateValue.decode(data); |
michael@0 | 926 | |
michael@0 | 927 | if (data.offset != end) { |
michael@0 | 928 | data.offset = end; |
michael@0 | 929 | } |
michael@0 | 930 | |
michael@0 | 931 | return result; |
michael@0 | 932 | }, |
michael@0 | 933 | }; |
michael@0 | 934 | |
michael@0 | 935 | /** |
michael@0 | 936 | * Message-class-value = Class-identifier | Token-text |
michael@0 | 937 | * Class-identifier = Personal | Advertisement | Informational | Auto |
michael@0 | 938 | * Personal = <Octet 128> |
michael@0 | 939 | * Advertisement = <Octet 129> |
michael@0 | 940 | * Informational = <Octet 130> |
michael@0 | 941 | * Auto = <Octet 131> |
michael@0 | 942 | * |
michael@0 | 943 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27 |
michael@0 | 944 | */ |
michael@0 | 945 | this.MessageClassValue = { |
michael@0 | 946 | WELL_KNOWN_CLASSES: ["personal", "advertisement", "informational", "auto"], |
michael@0 | 947 | |
michael@0 | 948 | /** |
michael@0 | 949 | * @param data |
michael@0 | 950 | * A wrapped object containing raw PDU data. |
michael@0 | 951 | * |
michael@0 | 952 | * @return A decoded string. |
michael@0 | 953 | * |
michael@0 | 954 | * @throws CodeError if decoded value is not in the range 128..131. |
michael@0 | 955 | */ |
michael@0 | 956 | decodeClassIdentifier: function(data) { |
michael@0 | 957 | let value = WSP.Octet.decode(data); |
michael@0 | 958 | if ((value >= 128) && (value < (128 + this.WELL_KNOWN_CLASSES.length))) { |
michael@0 | 959 | return this.WELL_KNOWN_CLASSES[value - 128]; |
michael@0 | 960 | } |
michael@0 | 961 | |
michael@0 | 962 | throw new WSP.CodeError("Class-identifier: invalid id " + value); |
michael@0 | 963 | }, |
michael@0 | 964 | |
michael@0 | 965 | /** |
michael@0 | 966 | * @param data |
michael@0 | 967 | * A wrapped object containing raw PDU data. |
michael@0 | 968 | * |
michael@0 | 969 | * @return A decoded string. |
michael@0 | 970 | */ |
michael@0 | 971 | decode: function(data) { |
michael@0 | 972 | let begin = data.offset; |
michael@0 | 973 | try { |
michael@0 | 974 | return this.decodeClassIdentifier(data); |
michael@0 | 975 | } catch (e) { |
michael@0 | 976 | data.offset = begin; |
michael@0 | 977 | return WSP.TokenText.decode(data); |
michael@0 | 978 | } |
michael@0 | 979 | }, |
michael@0 | 980 | |
michael@0 | 981 | /** |
michael@0 | 982 | * @param data |
michael@0 | 983 | * A wrapped object to store encoded raw data. |
michael@0 | 984 | * @param klass |
michael@0 | 985 | */ |
michael@0 | 986 | encode: function(data, klass) { |
michael@0 | 987 | let index = this.WELL_KNOWN_CLASSES.indexOf(klass.toLowerCase()); |
michael@0 | 988 | if (index >= 0) { |
michael@0 | 989 | WSP.Octet.encode(data, index + 128); |
michael@0 | 990 | } else { |
michael@0 | 991 | WSP.TokenText.encode(data, klass); |
michael@0 | 992 | } |
michael@0 | 993 | }, |
michael@0 | 994 | }; |
michael@0 | 995 | |
michael@0 | 996 | /** |
michael@0 | 997 | * Message-type-value = <Octet 128..151> |
michael@0 | 998 | * |
michael@0 | 999 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30 |
michael@0 | 1000 | */ |
michael@0 | 1001 | this.MessageTypeValue = new RangedValue("Message-type-value", 128, 151); |
michael@0 | 1002 | |
michael@0 | 1003 | /** |
michael@0 | 1004 | * MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value |
michael@0 | 1005 | * Add-token = <Octet 128> |
michael@0 | 1006 | * Remove-token = <Octet 129> |
michael@0 | 1007 | * Filter-token = <Octet 130> |
michael@0 | 1008 | * |
michael@0 | 1009 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32 |
michael@0 | 1010 | */ |
michael@0 | 1011 | this.MmFlagsValue = { |
michael@0 | 1012 | /** |
michael@0 | 1013 | * @param data |
michael@0 | 1014 | * A wrapped object containing raw PDU data. |
michael@0 | 1015 | * |
michael@0 | 1016 | * @return Decoded object containing an integer `type` and an string-typed |
michael@0 | 1017 | * `text` attributes. |
michael@0 | 1018 | * |
michael@0 | 1019 | * @throws CodeError if decoded value is not in the range 128..130. |
michael@0 | 1020 | */ |
michael@0 | 1021 | decode: function(data) { |
michael@0 | 1022 | let length = WSP.ValueLength.decode(data); |
michael@0 | 1023 | let end = data.offset + length; |
michael@0 | 1024 | |
michael@0 | 1025 | let result = {}; |
michael@0 | 1026 | result.type = WSP.Octet.decode(data); |
michael@0 | 1027 | if ((result.type < 128) || (result.type > 130)) { |
michael@0 | 1028 | throw new WSP.CodeError("MM-flags-value: invalid type " + result.type); |
michael@0 | 1029 | } |
michael@0 | 1030 | result.text = EncodedStringValue.decode(data); |
michael@0 | 1031 | |
michael@0 | 1032 | if (data.offset != end) { |
michael@0 | 1033 | data.offset = end; |
michael@0 | 1034 | } |
michael@0 | 1035 | |
michael@0 | 1036 | return result; |
michael@0 | 1037 | }, |
michael@0 | 1038 | |
michael@0 | 1039 | /** |
michael@0 | 1040 | * @param data |
michael@0 | 1041 | * A wrapped object to store encoded raw data. |
michael@0 | 1042 | * @param value |
michael@0 | 1043 | * An object containing an integer `type` and an string-typed |
michael@0 | 1044 | * `text` attributes. |
michael@0 | 1045 | */ |
michael@0 | 1046 | encode: function(data, value) { |
michael@0 | 1047 | if ((value.type < 128) || (value.type > 130)) { |
michael@0 | 1048 | throw new WSP.CodeError("MM-flags-value: invalid type " + value.type); |
michael@0 | 1049 | } |
michael@0 | 1050 | |
michael@0 | 1051 | // Calculate how much octets will be written and seek back. |
michael@0 | 1052 | // TODO: use memmove, see bug 730873 |
michael@0 | 1053 | let begin = data.offset; |
michael@0 | 1054 | EncodedStringValue.encode(data, value.text); |
michael@0 | 1055 | let len = data.offset - begin; |
michael@0 | 1056 | data.offset = begin; |
michael@0 | 1057 | |
michael@0 | 1058 | WSP.ValueLength.encode(data, len + 1); |
michael@0 | 1059 | WSP.Octet.encode(data, value.type); |
michael@0 | 1060 | EncodedStringValue.encode(data, value.text); |
michael@0 | 1061 | }, |
michael@0 | 1062 | }; |
michael@0 | 1063 | |
michael@0 | 1064 | /** |
michael@0 | 1065 | * MM-state-value = Draft | Sent | New | Retrieved | Forwarded |
michael@0 | 1066 | * Draft = <Octet 128> |
michael@0 | 1067 | * Sent = <Octet 129> |
michael@0 | 1068 | * New = <Octet 130> |
michael@0 | 1069 | * Retrieved = <Octet 131> |
michael@0 | 1070 | * Forwarded = <Octet 132> |
michael@0 | 1071 | * |
michael@0 | 1072 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33 |
michael@0 | 1073 | */ |
michael@0 | 1074 | this.MmStateValue = new RangedValue("MM-state-value", 128, 132); |
michael@0 | 1075 | |
michael@0 | 1076 | /** |
michael@0 | 1077 | * Priority-value = Low | Normal | High |
michael@0 | 1078 | * Low = <Octet 128> |
michael@0 | 1079 | * Normal = <Octet 129> |
michael@0 | 1080 | * High = <Octet 130> |
michael@0 | 1081 | * |
michael@0 | 1082 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35 |
michael@0 | 1083 | */ |
michael@0 | 1084 | this.PriorityValue = new RangedValue("Priority-value", 128, 130); |
michael@0 | 1085 | |
michael@0 | 1086 | /** |
michael@0 | 1087 | * Read-status-value = Read | Deleted without being read |
michael@0 | 1088 | * |
michael@0 | 1089 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.38 |
michael@0 | 1090 | */ |
michael@0 | 1091 | this.ReadStatusValue = new RangedValue("Read-status-value", 128, 129); |
michael@0 | 1092 | |
michael@0 | 1093 | /** |
michael@0 | 1094 | * Recommended-Retrieval-Mode-value = Manual |
michael@0 | 1095 | * Manual = <Octet 128> |
michael@0 | 1096 | * |
michael@0 | 1097 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39 |
michael@0 | 1098 | */ |
michael@0 | 1099 | this.RecommendedRetrievalModeValue = { |
michael@0 | 1100 | /** |
michael@0 | 1101 | * @param data |
michael@0 | 1102 | * A wrapped object containing raw PDU data. |
michael@0 | 1103 | * |
michael@0 | 1104 | * @return A decoded integer. |
michael@0 | 1105 | */ |
michael@0 | 1106 | decode: function(data) { |
michael@0 | 1107 | return WSP.Octet.decodeEqualTo(data, 128); |
michael@0 | 1108 | }, |
michael@0 | 1109 | }; |
michael@0 | 1110 | |
michael@0 | 1111 | /** |
michael@0 | 1112 | * Reply-charging-value = Requested | Requested text only | Accepted | |
michael@0 | 1113 | * Accepted text only |
michael@0 | 1114 | * Requested = <Octet 128> |
michael@0 | 1115 | * Requested text only = <Octet 129> |
michael@0 | 1116 | * Accepted = <Octet 130> |
michael@0 | 1117 | * Accepted text only = <Octet 131> |
michael@0 | 1118 | * |
michael@0 | 1119 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43 |
michael@0 | 1120 | */ |
michael@0 | 1121 | this.ReplyChargingValue = new RangedValue("Reply-charging-value", 128, 131); |
michael@0 | 1122 | |
michael@0 | 1123 | /** |
michael@0 | 1124 | * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: |
michael@0 | 1125 | * |
michael@0 | 1126 | * Response-text-value = Encoded-string-value |
michael@0 | 1127 | * |
michael@0 | 1128 | * When used in the M-Mbox-Delete.conf and M-Delete.conf PDUs: |
michael@0 | 1129 | * |
michael@0 | 1130 | * Response-text-Del-value = Value-length Status-count-value Response-text-value |
michael@0 | 1131 | * |
michael@0 | 1132 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.49 |
michael@0 | 1133 | */ |
michael@0 | 1134 | this.ResponseText = { |
michael@0 | 1135 | /** |
michael@0 | 1136 | * @param data |
michael@0 | 1137 | * A wrapped object containing raw PDU data. |
michael@0 | 1138 | * @param options |
michael@0 | 1139 | * Extra context for decoding. |
michael@0 | 1140 | * |
michael@0 | 1141 | * @return An object containing a string-typed `text` attribute and a |
michael@0 | 1142 | * integer-typed `statusCount` one. |
michael@0 | 1143 | */ |
michael@0 | 1144 | decode: function(data, options) { |
michael@0 | 1145 | let type = WSP.ensureHeader(options, "x-mms-message-type"); |
michael@0 | 1146 | |
michael@0 | 1147 | let result = {}; |
michael@0 | 1148 | if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF) |
michael@0 | 1149 | || (type == MMS_PDU_TYPE_DELETE_CONF)) { |
michael@0 | 1150 | let length = WSP.ValueLength.decode(data); |
michael@0 | 1151 | let end = data.offset + length; |
michael@0 | 1152 | |
michael@0 | 1153 | result.statusCount = WSP.IntegerValue.decode(data); |
michael@0 | 1154 | result.text = EncodedStringValue.decode(data); |
michael@0 | 1155 | |
michael@0 | 1156 | if (data.offset != end) { |
michael@0 | 1157 | data.offset = end; |
michael@0 | 1158 | } |
michael@0 | 1159 | } else { |
michael@0 | 1160 | result.text = EncodedStringValue.decode(data); |
michael@0 | 1161 | } |
michael@0 | 1162 | |
michael@0 | 1163 | return result; |
michael@0 | 1164 | }, |
michael@0 | 1165 | }; |
michael@0 | 1166 | |
michael@0 | 1167 | /** |
michael@0 | 1168 | * Retrieve-status-value = Ok | Error-transient-failure | |
michael@0 | 1169 | * Error-transient-message-not-found | |
michael@0 | 1170 | * Error-transient-network-problem | |
michael@0 | 1171 | * Error-permanent-failure | |
michael@0 | 1172 | * Error-permanent-service-denied | |
michael@0 | 1173 | * Error-permanent-message-not-found | |
michael@0 | 1174 | * Error-permanent-content-unsupported |
michael@0 | 1175 | * Ok = <Octet 128> |
michael@0 | 1176 | * Error-transient-failure = <Octet 192> |
michael@0 | 1177 | * Error-transient-message-not-found = <Octet 193> |
michael@0 | 1178 | * Error-transient-network-problem = <Octet 194> |
michael@0 | 1179 | * Error-permanent-failure = <Octet 224> |
michael@0 | 1180 | * Error-permanent-service-denied = <Octet 225> |
michael@0 | 1181 | * Error-permanent-message-not-found = <Octet 226> |
michael@0 | 1182 | * Error-permanent-content-unsupported = <Octet 227> |
michael@0 | 1183 | * |
michael@0 | 1184 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50 |
michael@0 | 1185 | */ |
michael@0 | 1186 | this.RetrieveStatusValue = { |
michael@0 | 1187 | /** |
michael@0 | 1188 | * @param data |
michael@0 | 1189 | * A wrapped object containing raw PDU data. |
michael@0 | 1190 | * |
michael@0 | 1191 | * @return A decoded integer. |
michael@0 | 1192 | */ |
michael@0 | 1193 | decode: function(data) { |
michael@0 | 1194 | let value = WSP.Octet.decode(data); |
michael@0 | 1195 | if (value == MMS_PDU_ERROR_OK) { |
michael@0 | 1196 | return value; |
michael@0 | 1197 | } |
michael@0 | 1198 | |
michael@0 | 1199 | if ((value >= MMS_PDU_ERROR_TRANSIENT_FAILURE) && (value < 256)) { |
michael@0 | 1200 | return value; |
michael@0 | 1201 | } |
michael@0 | 1202 | |
michael@0 | 1203 | // Any other values SHALL NOT be used. They are reserved for future use. |
michael@0 | 1204 | // An MMS Client that receives such a reserved value MUST react the same |
michael@0 | 1205 | // as it does to the value 224 (Error-permanent-failure). |
michael@0 | 1206 | return MMS_PDU_ERROR_PERMANENT_FAILURE; |
michael@0 | 1207 | }, |
michael@0 | 1208 | }; |
michael@0 | 1209 | |
michael@0 | 1210 | /** |
michael@0 | 1211 | * Sender-visibility-value = Hide | Show |
michael@0 | 1212 | * |
michael@0 | 1213 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.52 |
michael@0 | 1214 | */ |
michael@0 | 1215 | this.SenderVisibilityValue = new RangedValue("Sender-visibility-value", 128, 129); |
michael@0 | 1216 | |
michael@0 | 1217 | /** |
michael@0 | 1218 | * Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised | |
michael@0 | 1219 | * Indeterminate | Forwarded | Unreachable |
michael@0 | 1220 | * Expired = <Octet 128> |
michael@0 | 1221 | * Retrieved = <Octet 129> |
michael@0 | 1222 | * Rejected = <Octet 130> |
michael@0 | 1223 | * Deferred = <Octet 131> |
michael@0 | 1224 | * Unrecognised = <Octet 132> |
michael@0 | 1225 | * Indeterminate = <Octet 133> |
michael@0 | 1226 | * Forwarded = <Octet 134> |
michael@0 | 1227 | * Unreachable = <Octet 135> |
michael@0 | 1228 | * |
michael@0 | 1229 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54 |
michael@0 | 1230 | */ |
michael@0 | 1231 | this.StatusValue = new RangedValue("Status-value", 128, 135); |
michael@0 | 1232 | |
michael@0 | 1233 | this.PduHelper = { |
michael@0 | 1234 | /** |
michael@0 | 1235 | * @param data |
michael@0 | 1236 | * A wrapped object containing raw PDU data. |
michael@0 | 1237 | * @param headers |
michael@0 | 1238 | * An optional object to store parsed header fields. Created |
michael@0 | 1239 | * automatically if undefined. |
michael@0 | 1240 | * |
michael@0 | 1241 | * @return A boolean value indicating whether it's followed by message body. |
michael@0 | 1242 | */ |
michael@0 | 1243 | parseHeaders: function(data, headers) { |
michael@0 | 1244 | if (!headers) { |
michael@0 | 1245 | headers = {}; |
michael@0 | 1246 | } |
michael@0 | 1247 | |
michael@0 | 1248 | let header; |
michael@0 | 1249 | while (data.offset < data.array.length) { |
michael@0 | 1250 | // There is no `header length` information in MMS PDU. If we just got |
michael@0 | 1251 | // something wrong in parsing header fields, we might not be able to |
michael@0 | 1252 | // determine the correct header-content boundary. |
michael@0 | 1253 | header = HeaderField.decode(data, headers); |
michael@0 | 1254 | |
michael@0 | 1255 | if (header) { |
michael@0 | 1256 | let orig = headers[header.name]; |
michael@0 | 1257 | if (Array.isArray(orig)) { |
michael@0 | 1258 | headers[header.name].push(header.value); |
michael@0 | 1259 | } else if (orig) { |
michael@0 | 1260 | headers[header.name] = [orig, header.value]; |
michael@0 | 1261 | } else { |
michael@0 | 1262 | headers[header.name] = header.value; |
michael@0 | 1263 | } |
michael@0 | 1264 | if (header.name == "content-type") { |
michael@0 | 1265 | // `... if the PDU contains a message body the Content Type MUST be |
michael@0 | 1266 | // the last header field, followed by message body.` See |
michael@0 | 1267 | // OMA-TS-MMS_ENC-V1_3-20110913-A section 7. |
michael@0 | 1268 | break; |
michael@0 | 1269 | } |
michael@0 | 1270 | } |
michael@0 | 1271 | } |
michael@0 | 1272 | |
michael@0 | 1273 | return headers; |
michael@0 | 1274 | }, |
michael@0 | 1275 | |
michael@0 | 1276 | /** |
michael@0 | 1277 | * @param data |
michael@0 | 1278 | * A wrapped object containing raw PDU data. |
michael@0 | 1279 | * @param msg |
michael@0 | 1280 | * A message object to store decoded multipart or octet array content. |
michael@0 | 1281 | */ |
michael@0 | 1282 | parseContent: function(data, msg) { |
michael@0 | 1283 | let contentType = msg.headers["content-type"].media; |
michael@0 | 1284 | if ((contentType == "application/vnd.wap.multipart.related") |
michael@0 | 1285 | || (contentType == "application/vnd.wap.multipart.mixed")) { |
michael@0 | 1286 | msg.parts = WSP.PduHelper.parseMultiPart(data); |
michael@0 | 1287 | return; |
michael@0 | 1288 | } |
michael@0 | 1289 | |
michael@0 | 1290 | if (data.offset >= data.array.length) { |
michael@0 | 1291 | return; |
michael@0 | 1292 | } |
michael@0 | 1293 | |
michael@0 | 1294 | msg.content = WSP.Octet.decodeMultiple(data, data.array.length); |
michael@0 | 1295 | if (false) { |
michael@0 | 1296 | for (let begin = 0; begin < msg.content.length; begin += 20) { |
michael@0 | 1297 | debug("content: " + JSON.stringify(msg.content.subarray(begin, begin + 20))); |
michael@0 | 1298 | } |
michael@0 | 1299 | } |
michael@0 | 1300 | }, |
michael@0 | 1301 | |
michael@0 | 1302 | /** |
michael@0 | 1303 | * Check existences of all mandatory fields of a MMS message. Also sets `type` |
michael@0 | 1304 | * for convenient access. |
michael@0 | 1305 | * |
michael@0 | 1306 | * @param msg |
michael@0 | 1307 | * A MMS message object. |
michael@0 | 1308 | * |
michael@0 | 1309 | * @return The corresponding entry in MMS_PDU_TYPES; |
michael@0 | 1310 | * |
michael@0 | 1311 | * @throws FatalCodeError if the PDU type is not supported yet. |
michael@0 | 1312 | */ |
michael@0 | 1313 | checkMandatoryFields: function(msg) { |
michael@0 | 1314 | let type = WSP.ensureHeader(msg.headers, "x-mms-message-type"); |
michael@0 | 1315 | let entry = MMS_PDU_TYPES[type]; |
michael@0 | 1316 | if (!entry) { |
michael@0 | 1317 | throw new WSP.FatalCodeError( |
michael@0 | 1318 | "checkMandatoryFields: unsupported message type " + type); |
michael@0 | 1319 | } |
michael@0 | 1320 | |
michael@0 | 1321 | entry.mandatoryFields.forEach(function(name) { |
michael@0 | 1322 | WSP.ensureHeader(msg.headers, name); |
michael@0 | 1323 | }); |
michael@0 | 1324 | |
michael@0 | 1325 | // Setup convenient alias that referenced frequently. |
michael@0 | 1326 | msg.type = type; |
michael@0 | 1327 | |
michael@0 | 1328 | return entry; |
michael@0 | 1329 | }, |
michael@0 | 1330 | |
michael@0 | 1331 | /** |
michael@0 | 1332 | * @param data |
michael@0 | 1333 | * A wrapped object containing raw PDU data. |
michael@0 | 1334 | * @param msg [optional] |
michael@0 | 1335 | * Optional target object for decoding. |
michael@0 | 1336 | * |
michael@0 | 1337 | * @return A MMS message object or null in case of errors found. |
michael@0 | 1338 | */ |
michael@0 | 1339 | parse: function(data, msg) { |
michael@0 | 1340 | if (!msg) { |
michael@0 | 1341 | msg = {}; |
michael@0 | 1342 | } |
michael@0 | 1343 | |
michael@0 | 1344 | try { |
michael@0 | 1345 | msg.headers = this.parseHeaders(data, msg.headers); |
michael@0 | 1346 | |
michael@0 | 1347 | // Validity checks |
michael@0 | 1348 | let typeinfo = this.checkMandatoryFields(msg); |
michael@0 | 1349 | if (typeinfo.hasContent) { |
michael@0 | 1350 | this.parseContent(data, msg); |
michael@0 | 1351 | } |
michael@0 | 1352 | } catch (e) { |
michael@0 | 1353 | debug("Failed to parse MMS message, error message: " + e.message); |
michael@0 | 1354 | return null; |
michael@0 | 1355 | } |
michael@0 | 1356 | |
michael@0 | 1357 | return msg; |
michael@0 | 1358 | }, |
michael@0 | 1359 | |
michael@0 | 1360 | /** |
michael@0 | 1361 | * @param data |
michael@0 | 1362 | * A wrapped object to store encoded raw data. |
michael@0 | 1363 | * @param headers |
michael@0 | 1364 | * A dictionary object containing multiple name/value mapping. |
michael@0 | 1365 | * @param name |
michael@0 | 1366 | * Name of the header field to be encoded. |
michael@0 | 1367 | */ |
michael@0 | 1368 | encodeHeader: function(data, headers, name) { |
michael@0 | 1369 | let value = headers[name]; |
michael@0 | 1370 | if (Array.isArray(value)) { |
michael@0 | 1371 | for (let i = 0; i < value.length; i++) { |
michael@0 | 1372 | HeaderField.encode(data, {name: name, value: value[i]}, headers); |
michael@0 | 1373 | } |
michael@0 | 1374 | } else { |
michael@0 | 1375 | HeaderField.encode(data, {name: name, value: value}, headers); |
michael@0 | 1376 | } |
michael@0 | 1377 | }, |
michael@0 | 1378 | |
michael@0 | 1379 | /** |
michael@0 | 1380 | * @param data |
michael@0 | 1381 | * A wrapped object to store encoded raw data. |
michael@0 | 1382 | * @param headers |
michael@0 | 1383 | * A dictionary object containing multiple name/value mapping. |
michael@0 | 1384 | */ |
michael@0 | 1385 | encodeHeaderIfExists: function(data, headers, name) { |
michael@0 | 1386 | // Header value could be zero or null. |
michael@0 | 1387 | if (headers[name] !== undefined) { |
michael@0 | 1388 | this.encodeHeader(data, headers, name); |
michael@0 | 1389 | } |
michael@0 | 1390 | }, |
michael@0 | 1391 | |
michael@0 | 1392 | /** |
michael@0 | 1393 | * @param data [optional] |
michael@0 | 1394 | * A wrapped object to store encoded raw data. Created if undefined. |
michael@0 | 1395 | * @param headers |
michael@0 | 1396 | * A dictionary object containing multiple name/value mapping. |
michael@0 | 1397 | * |
michael@0 | 1398 | * @return the passed data parameter or a created one. |
michael@0 | 1399 | */ |
michael@0 | 1400 | encodeHeaders: function(data, headers) { |
michael@0 | 1401 | if (!data) { |
michael@0 | 1402 | data = {array: [], offset: 0}; |
michael@0 | 1403 | } |
michael@0 | 1404 | |
michael@0 | 1405 | // `In the encoding of the header fields, the order of the fields is not |
michael@0 | 1406 | // significant, except that X-Mms-Message-Type, X-Mms-Transaction-ID (when |
michael@0 | 1407 | // present) and X-Mms-MMS-Version MUST be at the beginning of the message |
michael@0 | 1408 | // headers, in that order, and if the PDU contains a message body the |
michael@0 | 1409 | // Content Type MUST be the last header field, followed by message body.` |
michael@0 | 1410 | // ~ OMA-TS-MMS_ENC-V1_3-20110913-A section 7 |
michael@0 | 1411 | this.encodeHeader(data, headers, "x-mms-message-type"); |
michael@0 | 1412 | this.encodeHeaderIfExists(data, headers, "x-mms-transaction-id"); |
michael@0 | 1413 | this.encodeHeaderIfExists(data, headers, "x-mms-mms-version"); |
michael@0 | 1414 | |
michael@0 | 1415 | for (let key in headers) { |
michael@0 | 1416 | if ((key == "x-mms-message-type") |
michael@0 | 1417 | || (key == "x-mms-transaction-id") |
michael@0 | 1418 | || (key == "x-mms-mms-version") |
michael@0 | 1419 | || (key == "content-type")) { |
michael@0 | 1420 | continue; |
michael@0 | 1421 | } |
michael@0 | 1422 | this.encodeHeader(data, headers, key); |
michael@0 | 1423 | } |
michael@0 | 1424 | |
michael@0 | 1425 | this.encodeHeaderIfExists(data, headers, "content-type"); |
michael@0 | 1426 | |
michael@0 | 1427 | return data; |
michael@0 | 1428 | }, |
michael@0 | 1429 | |
michael@0 | 1430 | /** |
michael@0 | 1431 | * @param multiStream |
michael@0 | 1432 | * An exsiting nsIMultiplexInputStream. |
michael@0 | 1433 | * @param msg |
michael@0 | 1434 | * A MMS message object. |
michael@0 | 1435 | * |
michael@0 | 1436 | * @return An instance of nsIMultiplexInputStream or null in case of errors. |
michael@0 | 1437 | */ |
michael@0 | 1438 | compose: function(multiStream, msg) { |
michael@0 | 1439 | if (!multiStream) { |
michael@0 | 1440 | multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"] |
michael@0 | 1441 | .createInstance(Ci.nsIMultiplexInputStream); |
michael@0 | 1442 | } |
michael@0 | 1443 | |
michael@0 | 1444 | try { |
michael@0 | 1445 | // Validity checks |
michael@0 | 1446 | let typeinfo = this.checkMandatoryFields(msg); |
michael@0 | 1447 | |
michael@0 | 1448 | let data = this.encodeHeaders(null, msg.headers); |
michael@0 | 1449 | debug("Composed PDU Header: " + JSON.stringify(data.array)); |
michael@0 | 1450 | WSP.PduHelper.appendArrayToMultiStream(multiStream, data.array, data.offset); |
michael@0 | 1451 | |
michael@0 | 1452 | if (msg.content) { |
michael@0 | 1453 | WSP.PduHelper.appendArrayToMultiStream(multiStream, msg.content, msg.content.length); |
michael@0 | 1454 | } else if (msg.parts) { |
michael@0 | 1455 | WSP.PduHelper.composeMultiPart(multiStream, msg.parts); |
michael@0 | 1456 | } else if (typeinfo.hasContent) { |
michael@0 | 1457 | throw new WSP.CodeError("Missing message content"); |
michael@0 | 1458 | } |
michael@0 | 1459 | |
michael@0 | 1460 | return multiStream; |
michael@0 | 1461 | } catch (e) { |
michael@0 | 1462 | debug("Failed to compose MMS message, error message: " + e.message); |
michael@0 | 1463 | return null; |
michael@0 | 1464 | } |
michael@0 | 1465 | }, |
michael@0 | 1466 | }; |
michael@0 | 1467 | |
michael@0 | 1468 | const MMS_PDU_TYPES = (function() { |
michael@0 | 1469 | let pdus = {}; |
michael@0 | 1470 | function add(number, hasContent, mandatoryFields) { |
michael@0 | 1471 | pdus[number] = { |
michael@0 | 1472 | number: number, |
michael@0 | 1473 | hasContent: hasContent, |
michael@0 | 1474 | mandatoryFields: mandatoryFields, |
michael@0 | 1475 | }; |
michael@0 | 1476 | } |
michael@0 | 1477 | |
michael@0 | 1478 | add(MMS_PDU_TYPE_SEND_REQ, true, ["x-mms-message-type", |
michael@0 | 1479 | "x-mms-transaction-id", |
michael@0 | 1480 | "x-mms-mms-version", |
michael@0 | 1481 | "from", |
michael@0 | 1482 | "content-type"]); |
michael@0 | 1483 | add(MMS_PDU_TYPE_SEND_CONF, false, ["x-mms-message-type", |
michael@0 | 1484 | "x-mms-transaction-id", |
michael@0 | 1485 | "x-mms-mms-version", |
michael@0 | 1486 | "x-mms-response-status"]); |
michael@0 | 1487 | add(MMS_PDU_TYPE_NOTIFICATION_IND, false, ["x-mms-message-type", |
michael@0 | 1488 | "x-mms-transaction-id", |
michael@0 | 1489 | "x-mms-mms-version", |
michael@0 | 1490 | "x-mms-message-class", |
michael@0 | 1491 | "x-mms-message-size", |
michael@0 | 1492 | "x-mms-expiry", |
michael@0 | 1493 | "x-mms-content-location"]); |
michael@0 | 1494 | add(MMS_PDU_TYPE_RETRIEVE_CONF, true, ["x-mms-message-type", |
michael@0 | 1495 | "x-mms-mms-version", |
michael@0 | 1496 | "date", |
michael@0 | 1497 | "content-type"]); |
michael@0 | 1498 | add(MMS_PDU_TYPE_NOTIFYRESP_IND, false, ["x-mms-message-type", |
michael@0 | 1499 | "x-mms-transaction-id", |
michael@0 | 1500 | "x-mms-mms-version", |
michael@0 | 1501 | "x-mms-status"]); |
michael@0 | 1502 | add(MMS_PDU_TYPE_DELIVERY_IND, false, ["x-mms-message-type", |
michael@0 | 1503 | "x-mms-mms-version", |
michael@0 | 1504 | "message-id", |
michael@0 | 1505 | "to", |
michael@0 | 1506 | "date", |
michael@0 | 1507 | "x-mms-status"]); |
michael@0 | 1508 | add(MMS_PDU_TYPE_ACKNOWLEDGE_IND, false, ["x-mms-message-type", |
michael@0 | 1509 | "x-mms-transaction-id", |
michael@0 | 1510 | "x-mms-mms-version"]); |
michael@0 | 1511 | add(MMS_PDU_TYPE_READ_REC_IND, false, ["x-mms-message-type", |
michael@0 | 1512 | "message-id", |
michael@0 | 1513 | "x-mms-mms-version", |
michael@0 | 1514 | "to", |
michael@0 | 1515 | "from", |
michael@0 | 1516 | "x-mms-read-status"]); |
michael@0 | 1517 | add(MMS_PDU_TYPE_READ_ORIG_IND, false, ["x-mms-message-type", |
michael@0 | 1518 | "x-mms-mms-version", |
michael@0 | 1519 | "message-id", |
michael@0 | 1520 | "to", |
michael@0 | 1521 | "from", |
michael@0 | 1522 | "date", |
michael@0 | 1523 | "x-mms-read-status"]); |
michael@0 | 1524 | |
michael@0 | 1525 | return pdus; |
michael@0 | 1526 | })(); |
michael@0 | 1527 | |
michael@0 | 1528 | /** |
michael@0 | 1529 | * Header field names and assigned numbers. |
michael@0 | 1530 | * |
michael@0 | 1531 | * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.4 |
michael@0 | 1532 | */ |
michael@0 | 1533 | const MMS_HEADER_FIELDS = (function() { |
michael@0 | 1534 | let names = {}; |
michael@0 | 1535 | function add(name, number, coder) { |
michael@0 | 1536 | let entry = { |
michael@0 | 1537 | name: name, |
michael@0 | 1538 | number: number, |
michael@0 | 1539 | coder: coder, |
michael@0 | 1540 | }; |
michael@0 | 1541 | names[name] = names[number] = entry; |
michael@0 | 1542 | } |
michael@0 | 1543 | |
michael@0 | 1544 | add("bcc", 0x01, Address); |
michael@0 | 1545 | add("cc", 0x02, Address); |
michael@0 | 1546 | add("x-mms-content-location", 0x03, ContentLocationValue); |
michael@0 | 1547 | add("content-type", 0x04, WSP.ContentTypeValue); |
michael@0 | 1548 | add("date", 0x05, WSP.DateValue); |
michael@0 | 1549 | add("x-mms-delivery-report", 0x06, BooleanValue); |
michael@0 | 1550 | add("x-mms-delivery-time", 0x07, ExpiryValue); |
michael@0 | 1551 | add("x-mms-expiry", 0x08, ExpiryValue); |
michael@0 | 1552 | add("from", 0x09, FromValue); |
michael@0 | 1553 | add("x-mms-message-class", 0x0A, MessageClassValue); |
michael@0 | 1554 | add("message-id", 0x0B, WSP.TextString); |
michael@0 | 1555 | add("x-mms-message-type", 0x0C, MessageTypeValue); |
michael@0 | 1556 | add("x-mms-mms-version", 0x0D, WSP.ShortInteger); |
michael@0 | 1557 | add("x-mms-message-size", 0x0E, WSP.LongInteger); |
michael@0 | 1558 | add("x-mms-priority", 0x0F, PriorityValue); |
michael@0 | 1559 | add("x-mms-read-report", 0x10, BooleanValue); |
michael@0 | 1560 | add("x-mms-report-allowed", 0x11, BooleanValue); |
michael@0 | 1561 | add("x-mms-response-status", 0x12, RetrieveStatusValue); |
michael@0 | 1562 | add("x-mms-response-text", 0x13, ResponseText); |
michael@0 | 1563 | add("x-mms-sender-visibility", 0x14, SenderVisibilityValue); |
michael@0 | 1564 | add("x-mms-status", 0x15, StatusValue); |
michael@0 | 1565 | add("subject", 0x16, EncodedStringValue); |
michael@0 | 1566 | add("to", 0x17, Address); |
michael@0 | 1567 | add("x-mms-transaction-id", 0x18, WSP.TextString); |
michael@0 | 1568 | add("x-mms-retrieve-status", 0x19, RetrieveStatusValue); |
michael@0 | 1569 | add("x-mms-retrieve-text", 0x1A, EncodedStringValue); |
michael@0 | 1570 | add("x-mms-read-status", 0x1B, ReadStatusValue); |
michael@0 | 1571 | add("x-mms-reply-charging", 0x1C, ReplyChargingValue); |
michael@0 | 1572 | add("x-mms-reply-charging-deadline", 0x1D, ExpiryValue); |
michael@0 | 1573 | add("x-mms-reply-charging-id", 0x1E, WSP.TextString); |
michael@0 | 1574 | add("x-mms-reply-charging-size", 0x1F, WSP.LongInteger); |
michael@0 | 1575 | add("x-mms-previously-sent-by", 0x20, PreviouslySentByValue); |
michael@0 | 1576 | add("x-mms-previously-sent-date", 0x21, PreviouslySentDateValue); |
michael@0 | 1577 | add("x-mms-store", 0x22, BooleanValue); |
michael@0 | 1578 | add("x-mms-mm-state", 0x23, MmStateValue); |
michael@0 | 1579 | add("x-mms-mm-flags", 0x24, MmFlagsValue); |
michael@0 | 1580 | add("x-mms-store-status", 0x25, RetrieveStatusValue); |
michael@0 | 1581 | add("x-mms-store-status-text", 0x26, EncodedStringValue); |
michael@0 | 1582 | add("x-mms-stored", 0x27, BooleanValue); |
michael@0 | 1583 | //add("x-mms-attributes", 0x28); |
michael@0 | 1584 | add("x-mms-totals", 0x29, BooleanValue); |
michael@0 | 1585 | //add("x-mms-mbox-totals", 0x2A); |
michael@0 | 1586 | add("x-mms-quotas", 0x2B, BooleanValue); |
michael@0 | 1587 | //add("x-mms-mbox-quotas", 0x2C); |
michael@0 | 1588 | add("x-mms-message-count", 0x2D, WSP.IntegerValue); |
michael@0 | 1589 | //add("content", 0x2E); |
michael@0 | 1590 | add("x-mms-start", 0x2F, WSP.IntegerValue); |
michael@0 | 1591 | //add("additional-headers", 0x30); |
michael@0 | 1592 | add("x-mms-distribution-indicator", 0x31, BooleanValue); |
michael@0 | 1593 | add("x-mms-element-descriptor", 0x32, ElementDescriptorValue); |
michael@0 | 1594 | add("x-mms-limit", 0x33, WSP.IntegerValue); |
michael@0 | 1595 | add("x-mms-recommended-retrieval-mode", 0x34, RecommendedRetrievalModeValue); |
michael@0 | 1596 | add("x-mms-recommended-retrieval-mode-text", 0x35, EncodedStringValue); |
michael@0 | 1597 | //add("x-mms-status-text", 0x36); |
michael@0 | 1598 | add("x-mms-applic-id", 0x37, WSP.TextString); |
michael@0 | 1599 | add("x-mms-reply-applic-id", 0x38, WSP.TextString); |
michael@0 | 1600 | add("x-mms-aux-applic-id", 0x39, WSP.TextString); |
michael@0 | 1601 | add("x-mms-content-class", 0x3A, ContentClassValue); |
michael@0 | 1602 | add("x-mms-drm-content", 0x3B, BooleanValue); |
michael@0 | 1603 | add("x-mms-adaptation-allowed", 0x3C, BooleanValue); |
michael@0 | 1604 | add("x-mms-replace-id", 0x3D, WSP.TextString); |
michael@0 | 1605 | add("x-mms-cancel-id", 0x3E, WSP.TextString); |
michael@0 | 1606 | add("x-mms-cancel-status", 0x3F, CancelStatusValue); |
michael@0 | 1607 | |
michael@0 | 1608 | return names; |
michael@0 | 1609 | })(); |
michael@0 | 1610 | |
michael@0 | 1611 | // @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments |
michael@0 | 1612 | const MMS_WELL_KNOWN_PARAMS = (function() { |
michael@0 | 1613 | let params = {}; |
michael@0 | 1614 | |
michael@0 | 1615 | function add(name, number, coder) { |
michael@0 | 1616 | let entry = { |
michael@0 | 1617 | name: name, |
michael@0 | 1618 | number: number, |
michael@0 | 1619 | coder: coder, |
michael@0 | 1620 | }; |
michael@0 | 1621 | params[name] = params[number] = entry; |
michael@0 | 1622 | } |
michael@0 | 1623 | |
michael@0 | 1624 | // Encoding Version: 1.2 |
michael@0 | 1625 | add("type", 0x02, WSP.TypeValue); |
michael@0 | 1626 | |
michael@0 | 1627 | return params; |
michael@0 | 1628 | })(); |
michael@0 | 1629 | |
michael@0 | 1630 | let debug; |
michael@0 | 1631 | if (DEBUG) { |
michael@0 | 1632 | debug = function(s) { |
michael@0 | 1633 | dump("-$- MmsPduHelper: " + s + "\n"); |
michael@0 | 1634 | }; |
michael@0 | 1635 | } else { |
michael@0 | 1636 | debug = function(s) {}; |
michael@0 | 1637 | } |
michael@0 | 1638 | |
michael@0 | 1639 | this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ |
michael@0 | 1640 | // Constant values |
michael@0 | 1641 | "MMS_VERSION", |
michael@0 | 1642 | |
michael@0 | 1643 | // Utility functions |
michael@0 | 1644 | "translatePduErrorToStatus", |
michael@0 | 1645 | |
michael@0 | 1646 | // Decoders |
michael@0 | 1647 | "BooleanValue", |
michael@0 | 1648 | "Address", |
michael@0 | 1649 | "HeaderField", |
michael@0 | 1650 | "MmsHeader", |
michael@0 | 1651 | "CancelStatusValue", |
michael@0 | 1652 | "ContentClassValue", |
michael@0 | 1653 | "ContentLocationValue", |
michael@0 | 1654 | "ElementDescriptorValue", |
michael@0 | 1655 | "Parameter", |
michael@0 | 1656 | "EncodedStringValue", |
michael@0 | 1657 | "ExpiryValue", |
michael@0 | 1658 | "FromValue", |
michael@0 | 1659 | "PreviouslySentByValue", |
michael@0 | 1660 | "PreviouslySentDateValue", |
michael@0 | 1661 | "MessageClassValue", |
michael@0 | 1662 | "MessageTypeValue", |
michael@0 | 1663 | "MmFlagsValue", |
michael@0 | 1664 | "MmStateValue", |
michael@0 | 1665 | "PriorityValue", |
michael@0 | 1666 | "ReadStatusValue", |
michael@0 | 1667 | "RecommendedRetrievalModeValue", |
michael@0 | 1668 | "ReplyChargingValue", |
michael@0 | 1669 | "ResponseText", |
michael@0 | 1670 | "RetrieveStatusValue", |
michael@0 | 1671 | "SenderVisibilityValue", |
michael@0 | 1672 | "StatusValue", |
michael@0 | 1673 | |
michael@0 | 1674 | // Parser |
michael@0 | 1675 | "PduHelper", |
michael@0 | 1676 | ]); |
michael@0 | 1677 |