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