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.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 sts=2 et filetype=javascript
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
9 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/NetUtil.jsm");
15 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
17 const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1";
18 const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}");
20 let DEBUG = false;
21 function debug(s) {
22 dump("-@- MmsService: " + s + "\n");
23 };
25 // Read debug setting from pref.
26 try {
27 let debugPref = Services.prefs.getBoolPref("mms.debugging.enabled");
28 DEBUG = DEBUG || debugPref;
29 } catch (e) {}
31 const kSmsSendingObserverTopic = "sms-sending";
32 const kSmsSentObserverTopic = "sms-sent";
33 const kSmsFailedObserverTopic = "sms-failed";
34 const kSmsReceivedObserverTopic = "sms-received";
35 const kSmsRetrievingObserverTopic = "sms-retrieving";
36 const kSmsDeliverySuccessObserverTopic = "sms-delivery-success";
37 const kSmsDeliveryErrorObserverTopic = "sms-delivery-error";
38 const kSmsReadSuccessObserverTopic = "sms-read-success";
39 const kSmsReadErrorObserverTopic = "sms-read-error";
41 const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
42 const kNetworkConnStateChangedTopic = "network-connection-state-changed";
43 const kMobileMessageDeletedObserverTopic = "mobile-message-deleted";
45 const kPrefRilRadioDisabled = "ril.radio.disabled";
47 // HTTP status codes:
48 // @see http://tools.ietf.org/html/rfc2616#page-39
49 const HTTP_STATUS_OK = 200;
51 // Non-standard HTTP status for internal use.
52 const _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS = 0;
53 const _HTTP_STATUS_USER_CANCELLED = -1;
54 const _HTTP_STATUS_RADIO_DISABLED = -2;
55 const _HTTP_STATUS_NO_SIM_CARD = -3;
56 const _HTTP_STATUS_ACQUIRE_TIMEOUT = -4;
58 // Non-standard MMS status for internal use.
59 const _MMS_ERROR_MESSAGE_DELETED = -1;
60 const _MMS_ERROR_RADIO_DISABLED = -2;
61 const _MMS_ERROR_NO_SIM_CARD = -3;
62 const _MMS_ERROR_SIM_CARD_CHANGED = -4;
63 const _MMS_ERROR_SHUTDOWN = -5;
64 const _MMS_ERROR_USER_CANCELLED_NO_REASON = -6;
65 const _MMS_ERROR_SIM_NOT_MATCHED = -7;
67 const CONFIG_SEND_REPORT_NEVER = 0;
68 const CONFIG_SEND_REPORT_DEFAULT_NO = 1;
69 const CONFIG_SEND_REPORT_DEFAULT_YES = 2;
70 const CONFIG_SEND_REPORT_ALWAYS = 3;
72 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
74 const TIME_TO_BUFFER_MMS_REQUESTS = 30000;
75 const PREF_TIME_TO_RELEASE_MMS_CONNECTION =
76 Services.prefs.getIntPref("network.gonk.ms-release-mms-connection");
78 const kPrefRetrievalMode = 'dom.mms.retrieval_mode';
79 const RETRIEVAL_MODE_MANUAL = "manual";
80 const RETRIEVAL_MODE_AUTOMATIC = "automatic";
81 const RETRIEVAL_MODE_AUTOMATIC_HOME = "automatic-home";
82 const RETRIEVAL_MODE_NEVER = "never";
84 //Internal const values.
85 const DELIVERY_RECEIVED = "received";
86 const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
87 const DELIVERY_SENDING = "sending";
88 const DELIVERY_SENT = "sent";
89 const DELIVERY_ERROR = "error";
91 const DELIVERY_STATUS_SUCCESS = "success";
92 const DELIVERY_STATUS_PENDING = "pending";
93 const DELIVERY_STATUS_ERROR = "error";
94 const DELIVERY_STATUS_REJECTED = "rejected";
95 const DELIVERY_STATUS_MANUAL = "manual";
96 const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
98 const PREF_SEND_RETRY_COUNT =
99 Services.prefs.getIntPref("dom.mms.sendRetryCount");
101 const PREF_SEND_RETRY_INTERVAL =
102 Services.prefs.getIntPref("dom.mms.sendRetryInterval");
104 const PREF_RETRIEVAL_RETRY_COUNT =
105 Services.prefs.getIntPref("dom.mms.retrievalRetryCount");
107 const PREF_RETRIEVAL_RETRY_INTERVALS = (function() {
108 let intervals =
109 Services.prefs.getCharPref("dom.mms.retrievalRetryIntervals").split(",");
110 for (let i = 0; i < PREF_RETRIEVAL_RETRY_COUNT; ++i) {
111 intervals[i] = parseInt(intervals[i], 10);
112 // If one of the intervals isn't valid (e.g., 0 or NaN),
113 // assign a 10-minute interval to it as a default.
114 if (!intervals[i]) {
115 intervals[i] = 600000;
116 }
117 }
118 intervals.length = PREF_RETRIEVAL_RETRY_COUNT;
119 return intervals;
120 })();
122 const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
123 const kPrefDefaultServiceId = "dom.mms.defaultServiceId";
125 XPCOMUtils.defineLazyServiceGetter(this, "gpps",
126 "@mozilla.org/network/protocol-proxy-service;1",
127 "nsIProtocolProxyService");
129 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
130 "@mozilla.org/uuid-generator;1",
131 "nsIUUIDGenerator");
133 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService",
134 "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1",
135 "nsIRilMobileMessageDatabaseService");
137 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
138 "@mozilla.org/mobilemessage/mobilemessageservice;1",
139 "nsIMobileMessageService");
141 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
142 "@mozilla.org/system-message-internal;1",
143 "nsISystemMessagesInternal");
145 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
146 "@mozilla.org/ril;1",
147 "nsIRadioInterfaceLayer");
149 XPCOMUtils.defineLazyGetter(this, "MMS", function() {
150 let MMS = {};
151 Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
152 return MMS;
153 });
155 // Internal Utilities
157 /**
158 * Return default service Id for MMS.
159 */
160 function getDefaultServiceId() {
161 let id = Services.prefs.getIntPref(kPrefDefaultServiceId);
162 let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
164 if (id >= numRil || id < 0) {
165 id = 0;
166 }
168 return id;
169 }
171 /**
172 * Return Radio disabled state.
173 */
174 function getRadioDisabledState() {
175 let state;
176 try {
177 state = Services.prefs.getBoolPref(kPrefRilRadioDisabled);
178 } catch (e) {
179 if (DEBUG) debug("Getting preference 'ril.radio.disabled' fails.");
180 state = false;
181 }
183 return state;
184 }
186 /**
187 * Helper Class to control MMS Data Connection.
188 */
189 function MmsConnection(aServiceId) {
190 this.serviceId = aServiceId;
191 this.radioInterface = gRil.getRadioInterface(aServiceId);
192 };
194 MmsConnection.prototype = {
195 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
197 /** MMS proxy settings. */
198 mmsc: "",
199 mmsProxy: "",
200 mmsPort: -1,
202 setApnSetting: function(network) {
203 this.mmsc = network.mmsc;
204 this.mmsProxy = network.mmsProxy;
205 this.mmsPort = network.mmsPort;
206 },
208 get proxyInfo() {
209 if (!this.mmsProxy) {
210 if (DEBUG) debug("getProxyInfo: MMS proxy is not available.");
211 return null;
212 }
214 let port = this.mmsPort;
216 if (port <= 0) {
217 port = 80;
218 if (DEBUG) debug("getProxyInfo: port is not valid. Set to defult (80).");
219 }
221 let proxyInfo =
222 gpps.newProxyInfo("http", this.mmsProxy, port,
223 Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
224 -1, null);
225 if (DEBUG) debug("getProxyInfo: " + JSON.stringify(proxyInfo));
227 return proxyInfo;
228 },
230 connected: false,
232 //A queue to buffer the MMS HTTP requests when the MMS network
233 //is not yet connected. The buffered requests will be cleared
234 //if the MMS network fails to be connected within a timer.
235 pendingCallbacks: [],
237 /** MMS network connection reference count. */
238 refCount: 0,
240 connectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
242 disconnectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
244 /**
245 * Callback when |connectTimer| is timeout or cancelled by shutdown.
246 */
247 flushPendingCallbacks: function(status) {
248 if (DEBUG) debug("flushPendingCallbacks: " + this.pendingCallbacks.length
249 + " pending callbacks with status: " + status);
250 while (this.pendingCallbacks.length) {
251 let callback = this.pendingCallbacks.shift();
252 let connected = (status == _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS);
253 callback(connected, status);
254 }
255 },
257 /**
258 * Callback when |disconnectTimer| is timeout or cancelled by shutdown.
259 */
260 onDisconnectTimerTimeout: function() {
261 if (DEBUG) debug("onDisconnectTimerTimeout: deactivate the MMS data call.");
262 if (this.connected) {
263 this.radioInterface.deactivateDataCallByType("mms");
264 }
265 },
267 init: function() {
268 Services.obs.addObserver(this, kNetworkConnStateChangedTopic,
269 false);
270 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
272 this.connected = this.radioInterface.getDataCallStateByType("mms") ==
273 Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
274 // If the MMS network is connected during the initialization, it means the
275 // MMS network must share the same APN with the mobile network by default.
276 // Under this case, |networkManager.active| should keep the mobile network,
277 // which is supposed be an instance of |nsIRilNetworkInterface| for sure.
278 if (this.connected) {
279 let networkManager =
280 Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
281 let activeNetwork = networkManager.active;
283 let rilNetwork = activeNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
284 if (rilNetwork.serviceId != this.serviceId) {
285 if (DEBUG) debug("Sevice ID between active/MMS network doesn't match.");
286 return;
287 }
289 // Set up the MMS APN setting based on the connected MMS network,
290 // which is going to be used for the HTTP requests later.
291 this.setApnSetting(rilNetwork);
292 }
293 },
295 /**
296 * Return the roaming status of voice call.
297 *
298 * @return true if voice call is roaming.
299 */
300 isVoiceRoaming: function() {
301 let isRoaming = this.radioInterface.rilContext.voice.roaming;
302 if (DEBUG) debug("isVoiceRoaming = " + isRoaming);
303 return isRoaming;
304 },
306 /**
307 * Get phone number from iccInfo.
308 *
309 * If the icc card is gsm card, the phone number is in msisdn.
310 * @see nsIDOMMozGsmIccInfo
311 *
312 * Otherwise, the phone number is in mdn.
313 * @see nsIDOMMozCdmaIccInfo
314 */
315 getPhoneNumber: function() {
316 let iccInfo = this.radioInterface.rilContext.iccInfo;
318 if (!iccInfo) {
319 return null;
320 }
322 let number = (iccInfo instanceof Ci.nsIDOMMozGsmIccInfo)
323 ? iccInfo.msisdn : iccInfo.mdn;
325 // Workaround an xpconnect issue with undefined string objects.
326 // See bug 808220
327 if (number === undefined || number === "undefined") {
328 return null;
329 }
331 return number;
332 },
334 /**
335 * A utility function to get the ICC ID of the SIM card (if installed).
336 */
337 getIccId: function() {
338 let iccInfo = this.radioInterface.rilContext.iccInfo;
340 if (!iccInfo) {
341 return null;
342 }
344 let iccId = iccInfo.iccid;
346 // Workaround an xpconnect issue with undefined string objects.
347 // See bug 808220
348 if (iccId === undefined || iccId === "undefined") {
349 return null;
350 }
352 return iccId;
353 },
355 /**
356 * Acquire the MMS network connection.
357 *
358 * @param callback
359 * Callback function when either the connection setup is done,
360 * timeout, or failed. Parameters are:
361 * - A boolean value indicates whether the connection is ready.
362 * - Acquire connection status: _HTTP_STATUS_ACQUIRE_*.
363 *
364 * @return true if the callback for MMS network connection is done; false
365 * otherwise.
366 */
367 acquire: function(callback) {
368 this.refCount++;
369 this.connectTimer.cancel();
370 this.disconnectTimer.cancel();
372 // If the MMS network is not yet connected, buffer the
373 // MMS request and try to setup the MMS network first.
374 if (!this.connected) {
375 this.pendingCallbacks.push(callback);
377 let errorStatus;
378 if (getRadioDisabledState()) {
379 if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
380 errorStatus = _HTTP_STATUS_RADIO_DISABLED;
381 } else if (this.radioInterface.rilContext.cardState != "ready") {
382 if (DEBUG) debug("Error! SIM card is not ready when sending MMS.");
383 errorStatus = _HTTP_STATUS_NO_SIM_CARD;
384 }
385 if (errorStatus != null) {
386 this.flushPendingCallbacks(errorStatus);
387 return true;
388 }
390 if (DEBUG) debug("acquire: buffer the MMS request and setup the MMS data call.");
391 this.radioInterface.setupDataCallByType("mms");
393 // Set a timer to clear the buffered MMS requests if the
394 // MMS network fails to be connected within a time period.
395 this.connectTimer.
396 initWithCallback(this.flushPendingCallbacks.bind(this, _HTTP_STATUS_ACQUIRE_TIMEOUT),
397 TIME_TO_BUFFER_MMS_REQUESTS,
398 Ci.nsITimer.TYPE_ONE_SHOT);
399 return false;
400 }
402 callback(true, _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS);
403 return true;
404 },
406 /**
407 * Release the MMS network connection.
408 */
409 release: function() {
410 this.refCount--;
411 if (this.refCount <= 0) {
412 this.refCount = 0;
414 // The waiting is too small, just skip the timer creation.
415 if (PREF_TIME_TO_RELEASE_MMS_CONNECTION < 1000) {
416 this.onDisconnectTimerTimeout();
417 return;
418 }
420 // Set a timer to delay the release of MMS network connection,
421 // since the MMS requests often come consecutively in a short time.
422 this.disconnectTimer.
423 initWithCallback(this.onDisconnectTimerTimeout.bind(this),
424 PREF_TIME_TO_RELEASE_MMS_CONNECTION,
425 Ci.nsITimer.TYPE_ONE_SHOT);
426 }
427 },
429 shutdown: function() {
430 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
431 Services.obs.removeObserver(this, kNetworkConnStateChangedTopic);
433 this.connectTimer.cancel();
434 this.flushPendingCallbacks(_HTTP_STATUS_RADIO_DISABLED);
435 this.disconnectTimer.cancel();
436 this.onDisconnectTimerTimeout();
437 },
439 // nsIObserver
441 observe: function(subject, topic, data) {
442 switch (topic) {
443 case kNetworkConnStateChangedTopic: {
444 // The network for MMS connection must be nsIRilNetworkInterface.
445 if (!(subject instanceof Ci.nsIRilNetworkInterface)) {
446 return;
447 }
449 // Check if the network state change belongs to this service.
450 let network = subject.QueryInterface(Ci.nsIRilNetworkInterface);
451 if (network.serviceId != this.serviceId) {
452 return;
453 }
455 // We only need to capture the state change of MMS network. Using
456 // |network.state| isn't reliable due to the possibilty of shared APN.
457 let connected =
458 this.radioInterface.getDataCallStateByType("mms") ==
459 Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
461 // Return if the MMS network state doesn't change, where the network
462 // state change can come from other non-MMS networks.
463 if (connected == this.connected) {
464 return;
465 }
467 this.connected = connected;
468 if (!this.connected) {
469 return;
470 }
472 // Set up the MMS APN setting based on the connected MMS network,
473 // which is going to be used for the HTTP requests later.
474 this.setApnSetting(network);
476 if (DEBUG) debug("Got the MMS network connected! Resend the buffered " +
477 "MMS requests: number: " + this.pendingCallbacks.length);
478 this.connectTimer.cancel();
479 this.flushPendingCallbacks(_HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS)
480 break;
481 }
482 case NS_XPCOM_SHUTDOWN_OBSERVER_ID: {
483 this.shutdown();
484 }
485 }
486 }
487 };
489 XPCOMUtils.defineLazyGetter(this, "gMmsConnections", function() {
490 return {
491 _connections: null,
492 getConnByServiceId: function(id) {
493 if (!this._connections) {
494 this._connections = [];
495 }
497 let conn = this._connections[id];
498 if (conn) {
499 return conn;
500 }
502 conn = this._connections[id] = new MmsConnection(id);
503 conn.init();
504 return conn;
505 },
506 getConnByIccId: function(aIccId) {
507 if (!aIccId) {
508 // If the ICC ID isn't available, it means the MMS has been received
509 // during the previous version that didn't take the DSDS scenario
510 // into consideration. Tentatively, get connection from serviceId(0) by
511 // default is better than nothing. Although it might use the wrong
512 // SIM to download the desired MMS, eventually it would still fail to
513 // download due to the wrong MMSC and proxy settings.
514 return this.getConnByServiceId(0);
515 }
517 let numCardAbsent = 0;
518 let numRadioInterfaces = gRil.numRadioInterfaces;
519 for (let clientId = 0; clientId < numRadioInterfaces; clientId++) {
520 let mmsConnection = this.getConnByServiceId(clientId);
521 let iccId = mmsConnection.getIccId();
522 if (iccId === null) {
523 numCardAbsent++;
524 continue;
525 }
527 if (iccId === aIccId) {
528 return mmsConnection;
529 }
530 }
532 throw ((numCardAbsent === numRadioInterfaces)?
533 _MMS_ERROR_NO_SIM_CARD: _MMS_ERROR_SIM_NOT_MATCHED);
534 },
535 };
536 });
538 /**
539 * Implementation of nsIProtocolProxyFilter for MMS Proxy
540 */
541 function MmsProxyFilter(mmsConnection, url) {
542 this.mmsConnection = mmsConnection;
543 this.uri = Services.io.newURI(url, null, null);
544 }
545 MmsProxyFilter.prototype = {
547 QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolProxyFilter]),
549 // nsIProtocolProxyFilter
551 applyFilter: function(proxyService, uri, proxyInfo) {
552 if (!this.uri.equals(uri)) {
553 if (DEBUG) debug("applyFilter: content uri = " + JSON.stringify(this.uri) +
554 " is not matched with uri = " + JSON.stringify(uri) + " .");
555 return proxyInfo;
556 }
558 // Fall-through, reutrn the MMS proxy info.
559 let mmsProxyInfo = this.mmsConnection.proxyInfo;
561 if (DEBUG) {
562 debug("applyFilter: MMSC/Content Location is matched with: " +
563 JSON.stringify({ uri: JSON.stringify(this.uri),
564 mmsProxyInfo: mmsProxyInfo }));
565 }
567 return mmsProxyInfo ? mmsProxyInfo : proxyInfo;
568 }
569 };
571 XPCOMUtils.defineLazyGetter(this, "gMmsTransactionHelper", function() {
572 let helper = {
573 /**
574 * Send MMS request to MMSC.
575 *
576 * @param mmsConnection
577 * The MMS connection.
578 * @param method
579 * "GET" or "POST".
580 * @param url
581 * Target url string or null to be replaced by mmsc url.
582 * @param istream
583 * An nsIInputStream instance as data source to be sent or null.
584 * @param callback
585 * A callback function that takes two arguments: one for http
586 * status, the other for wrapped PDU data for further parsing.
587 */
588 sendRequest: function(mmsConnection, method, url, istream, callback) {
589 // TODO: bug 810226 - Support GPRS bearer for MMS transmission and reception.
590 let cancellable = {
591 callback: callback,
593 isDone: false,
594 isCancelled: false,
596 cancel: function() {
597 if (this.isDone) {
598 // It's too late to cancel.
599 return;
600 }
602 this.isCancelled = true;
603 if (this.isAcquiringConn) {
604 // We cannot cancel data connection setup here, so we invoke done()
605 // here and handle |cancellable.isDone| in callback function of
606 // |mmsConnection.acquire|.
607 this.done(_HTTP_STATUS_USER_CANCELLED, null);
608 } else if (this.xhr) {
609 // Client has already sent the HTTP request. Try to abort it.
610 this.xhr.abort();
611 }
612 },
614 done: function(httpStatus, data) {
615 this.isDone = true;
616 if (!this.callback) {
617 return;
618 }
620 if (this.isCancelled) {
621 this.callback(_HTTP_STATUS_USER_CANCELLED, null);
622 } else {
623 this.callback(httpStatus, data);
624 }
625 }
626 };
628 cancellable.isAcquiringConn =
629 !mmsConnection.acquire((function(connected, errorCode) {
631 cancellable.isAcquiringConn = false;
633 if (!connected || cancellable.isCancelled) {
634 mmsConnection.release();
636 if (!cancellable.isDone) {
637 cancellable.done(cancellable.isCancelled ?
638 _HTTP_STATUS_USER_CANCELLED : errorCode, null);
639 }
640 return;
641 }
643 // MMSC is available after an MMS connection is successfully acquired.
644 if (!url) {
645 url = mmsConnection.mmsc;
646 }
648 if (DEBUG) debug("sendRequest: register proxy filter to " + url);
649 let proxyFilter = new MmsProxyFilter(mmsConnection, url);
650 gpps.registerFilter(proxyFilter, 0);
652 cancellable.xhr = this.sendHttpRequest(mmsConnection, method,
653 url, istream, proxyFilter,
654 cancellable.done.bind(cancellable));
655 }).bind(this));
657 return cancellable;
658 },
660 sendHttpRequest: function(mmsConnection, method, url, istream, proxyFilter,
661 callback) {
662 let releaseMmsConnectionAndCallback = function(httpStatus, data) {
663 gpps.unregisterFilter(proxyFilter);
664 // Always release the MMS network connection before callback.
665 mmsConnection.release();
666 callback(httpStatus, data);
667 };
669 try {
670 let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
671 .createInstance(Ci.nsIXMLHttpRequest);
673 // Basic setups
674 xhr.open(method, url, true);
675 xhr.responseType = "arraybuffer";
676 if (istream) {
677 xhr.setRequestHeader("Content-Type",
678 "application/vnd.wap.mms-message");
679 xhr.setRequestHeader("Content-Length", istream.available());
680 }
682 // UAProf headers.
683 let uaProfUrl, uaProfTagname = "x-wap-profile";
684 try {
685 uaProfUrl = Services.prefs.getCharPref('wap.UAProf.url');
686 uaProfTagname = Services.prefs.getCharPref('wap.UAProf.tagname');
687 } catch (e) {}
689 if (uaProfUrl) {
690 xhr.setRequestHeader(uaProfTagname, uaProfUrl);
691 }
693 // Setup event listeners
694 xhr.onreadystatechange = function() {
695 if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) {
696 return;
697 }
698 let data = null;
699 switch (xhr.status) {
700 case HTTP_STATUS_OK: {
701 if (DEBUG) debug("xhr success, response headers: "
702 + xhr.getAllResponseHeaders());
703 let array = new Uint8Array(xhr.response);
704 if (false) {
705 for (let begin = 0; begin < array.length; begin += 20) {
706 let partial = array.subarray(begin, begin + 20);
707 if (DEBUG) debug("res: " + JSON.stringify(partial));
708 }
709 }
711 data = {array: array, offset: 0};
712 break;
713 }
715 default: {
716 if (DEBUG) debug("xhr done, but status = " + xhr.status +
717 ", statusText = " + xhr.statusText);
718 break;
719 }
720 }
721 releaseMmsConnectionAndCallback(xhr.status, data);
722 };
723 // Send request
724 xhr.send(istream);
725 return xhr;
726 } catch (e) {
727 if (DEBUG) debug("xhr error, can't send: " + e.message);
728 releaseMmsConnectionAndCallback(0, null);
729 return null;
730 }
731 },
733 /**
734 * Count number of recipients(to, cc, bcc fields).
735 *
736 * @param recipients
737 * The recipients in MMS message object.
738 * @return the number of recipients
739 * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5
740 */
741 countRecipients: function(recipients) {
742 if (recipients && recipients.address) {
743 return 1;
744 }
745 let totalRecipients = 0;
746 if (!Array.isArray(recipients)) {
747 return 0;
748 }
749 totalRecipients += recipients.length;
750 for (let ix = 0; ix < recipients.length; ++ix) {
751 if (recipients[ix].address.length > MMS.MMS_MAX_LENGTH_RECIPIENT) {
752 throw new Error("MMS_MAX_LENGTH_RECIPIENT error");
753 }
754 if (recipients[ix].type === "email") {
755 let found = recipients[ix].address.indexOf("<");
756 let lenMailbox = recipients[ix].address.length - found;
757 if(lenMailbox > MMS.MMS_MAX_LENGTH_MAILBOX_PORTION) {
758 throw new Error("MMS_MAX_LENGTH_MAILBOX_PORTION error");
759 }
760 }
761 }
762 return totalRecipients;
763 },
765 /**
766 * Check maximum values of MMS parameters.
767 *
768 * @param msg
769 * The MMS message object.
770 * @return true if the lengths are less than the maximum values of MMS
771 * parameters.
772 * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5
773 */
774 checkMaxValuesParameters: function(msg) {
775 let subject = msg.headers["subject"];
776 if (subject && subject.length > MMS.MMS_MAX_LENGTH_SUBJECT) {
777 return false;
778 }
780 let totalRecipients = 0;
781 try {
782 totalRecipients += this.countRecipients(msg.headers["to"]);
783 totalRecipients += this.countRecipients(msg.headers["cc"]);
784 totalRecipients += this.countRecipients(msg.headers["bcc"]);
785 } catch (ex) {
786 if (DEBUG) debug("Exception caught : " + ex);
787 return false;
788 }
790 if (totalRecipients < 1 ||
791 totalRecipients > MMS.MMS_MAX_TOTAL_RECIPIENTS) {
792 return false;
793 }
795 if (!Array.isArray(msg.parts)) {
796 return true;
797 }
798 for (let i = 0; i < msg.parts.length; i++) {
799 if (msg.parts[i].headers["content-type"] &&
800 msg.parts[i].headers["content-type"].params) {
801 let name = msg.parts[i].headers["content-type"].params["name"];
802 if (name && name.length > MMS.MMS_MAX_LENGTH_NAME_CONTENT_TYPE) {
803 return false;
804 }
805 }
806 }
807 return true;
808 },
810 translateHttpStatusToMmsStatus: function(httpStatus, cancelledReason,
811 defaultStatus) {
812 switch(httpStatus) {
813 case _HTTP_STATUS_USER_CANCELLED:
814 return cancelledReason;
815 case _HTTP_STATUS_RADIO_DISABLED:
816 return _MMS_ERROR_RADIO_DISABLED;
817 case _HTTP_STATUS_NO_SIM_CARD:
818 return _MMS_ERROR_NO_SIM_CARD;
819 case HTTP_STATUS_OK:
820 return MMS.MMS_PDU_ERROR_OK;
821 default:
822 return defaultStatus;
823 }
824 }
825 };
827 return helper;
828 });
830 /**
831 * Send M-NotifyResp.ind back to MMSC.
832 *
833 * @param mmsConnection
834 * The MMS connection.
835 * @param transactionId
836 * X-Mms-Transaction-ID of the message.
837 * @param status
838 * X-Mms-Status of the response.
839 * @param reportAllowed
840 * X-Mms-Report-Allowed of the response.
841 *
842 * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.2
843 */
844 function NotifyResponseTransaction(mmsConnection, transactionId, status,
845 reportAllowed) {
846 this.mmsConnection = mmsConnection;
847 let headers = {};
849 // Mandatory fields
850 headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_NOTIFYRESP_IND;
851 headers["x-mms-transaction-id"] = transactionId;
852 headers["x-mms-mms-version"] = MMS.MMS_VERSION;
853 headers["x-mms-status"] = status;
854 // Optional fields
855 headers["x-mms-report-allowed"] = reportAllowed;
857 this.istream = MMS.PduHelper.compose(null, {headers: headers});
858 }
859 NotifyResponseTransaction.prototype = {
860 /**
861 * @param callback [optional]
862 * A callback function that takes one argument -- the http status.
863 */
864 run: function(callback) {
865 let requestCallback;
866 if (callback) {
867 requestCallback = function(httpStatus, data) {
868 // `The MMS Client SHOULD ignore the associated HTTP POST response
869 // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A
870 // section 8.2.2 "Notification".
871 callback(httpStatus);
872 };
873 }
874 gMmsTransactionHelper.sendRequest(this.mmsConnection,
875 "POST",
876 null,
877 this.istream,
878 requestCallback);
879 }
880 };
882 /**
883 * CancellableTransaction - base class inherited by [Send|Retrieve]Transaction.
884 * We can call |cancelRunning(reason)| to cancel the on-going transaction.
885 * @param cancellableId
886 * An ID used to keep track of if an message is deleted from DB.
887 * @param serviceId
888 * An ID used to keep track of if the primary SIM service is changed.
889 */
890 function CancellableTransaction(cancellableId, serviceId) {
891 this.cancellableId = cancellableId;
892 this.serviceId = serviceId;
893 this.isCancelled = false;
894 }
895 CancellableTransaction.prototype = {
896 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
898 // The timer for retrying sending or retrieving process.
899 timer: null,
901 // Keep a reference to the callback when calling
902 // |[Send|Retrieve]Transaction.run(callback)|.
903 runCallback: null,
905 isObserversAdded: false,
907 cancelledReason: _MMS_ERROR_USER_CANCELLED_NO_REASON,
909 registerRunCallback: function(callback) {
910 if (!this.isObserversAdded) {
911 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
912 Services.obs.addObserver(this, kMobileMessageDeletedObserverTopic, false);
913 Services.prefs.addObserver(kPrefRilRadioDisabled, this, false);
914 Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
915 this.isObserversAdded = true;
916 }
918 this.runCallback = callback;
919 this.isCancelled = false;
920 },
922 removeObservers: function() {
923 if (this.isObserversAdded) {
924 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
925 Services.obs.removeObserver(this, kMobileMessageDeletedObserverTopic);
926 Services.prefs.removeObserver(kPrefRilRadioDisabled, this);
927 Services.prefs.removeObserver(kPrefDefaultServiceId, this);
928 this.isObserversAdded = false;
929 }
930 },
932 runCallbackIfValid: function(mmsStatus, msg) {
933 this.removeObservers();
935 if (this.runCallback) {
936 this.runCallback(mmsStatus, msg);
937 this.runCallback = null;
938 }
939 },
941 // Keep a reference to the cancellable when calling
942 // |gMmsTransactionHelper.sendRequest(...)|.
943 cancellable: null,
945 cancelRunning: function(reason) {
946 this.isCancelled = true;
947 this.cancelledReason = reason;
949 if (this.timer) {
950 // The sending or retrieving process is waiting for the next retry.
951 // What we only need to do is to cancel the timer.
952 this.timer.cancel();
953 this.timer = null;
954 this.runCallbackIfValid(reason, null);
955 return;
956 }
958 if (this.cancellable) {
959 // The sending or retrieving process is still running. We attempt to
960 // abort the HTTP request.
961 this.cancellable.cancel();
962 this.cancellable = null;
963 }
964 },
966 // nsIObserver
968 observe: function(subject, topic, data) {
969 switch (topic) {
970 case NS_XPCOM_SHUTDOWN_OBSERVER_ID: {
971 this.cancelRunning(_MMS_ERROR_SHUTDOWN);
972 break;
973 }
974 case kMobileMessageDeletedObserverTopic: {
975 data = JSON.parse(data);
976 if (data.id != this.cancellableId) {
977 return;
978 }
980 this.cancelRunning(_MMS_ERROR_MESSAGE_DELETED);
981 break;
982 }
983 case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: {
984 if (data == kPrefRilRadioDisabled) {
985 if (getRadioDisabledState()) {
986 this.cancelRunning(_MMS_ERROR_RADIO_DISABLED);
987 }
988 } else if (data === kPrefDefaultServiceId &&
989 this.serviceId != getDefaultServiceId()) {
990 this.cancelRunning(_MMS_ERROR_SIM_CARD_CHANGED);
991 }
992 break;
993 }
994 }
995 }
996 };
998 /**
999 * Class for retrieving message from MMSC, which inherits CancellableTransaction.
1000 *
1001 * @param contentLocation
1002 * X-Mms-Content-Location of the message.
1003 */
1004 function RetrieveTransaction(mmsConnection, cancellableId, contentLocation) {
1005 this.mmsConnection = mmsConnection;
1007 // Call |CancellableTransaction| constructor.
1008 CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId);
1010 this.contentLocation = contentLocation;
1011 }
1012 RetrieveTransaction.prototype = Object.create(CancellableTransaction.prototype, {
1013 /**
1014 * @param callback [optional]
1015 * A callback function that takes two arguments: one for X-Mms-Status,
1016 * the other for the parsed M-Retrieve.conf message.
1017 */
1018 run: {
1019 value: function(callback) {
1020 this.registerRunCallback(callback);
1022 this.retryCount = 0;
1023 let retryCallback = (function(mmsStatus, msg) {
1024 if (MMS.MMS_PDU_STATUS_DEFERRED == mmsStatus &&
1025 this.retryCount < PREF_RETRIEVAL_RETRY_COUNT) {
1026 let time = PREF_RETRIEVAL_RETRY_INTERVALS[this.retryCount];
1027 if (DEBUG) debug("Fail to retrieve. Will retry after: " + time);
1029 if (this.timer == null) {
1030 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1031 }
1033 this.timer.initWithCallback(this.retrieve.bind(this, retryCallback),
1034 time, Ci.nsITimer.TYPE_ONE_SHOT);
1035 this.retryCount++;
1036 return;
1037 }
1038 this.runCallbackIfValid(mmsStatus, msg);
1039 }).bind(this);
1041 this.retrieve(retryCallback);
1042 },
1043 enumerable: true,
1044 configurable: true,
1045 writable: true
1046 },
1048 /**
1049 * @param callback
1050 * A callback function that takes two arguments: one for X-Mms-Status,
1051 * the other for the parsed M-Retrieve.conf message.
1052 */
1053 retrieve: {
1054 value: function(callback) {
1055 this.timer = null;
1057 this.cancellable =
1058 gMmsTransactionHelper.sendRequest(this.mmsConnection,
1059 "GET", this.contentLocation, null,
1060 (function(httpStatus, data) {
1061 let mmsStatus = gMmsTransactionHelper
1062 .translateHttpStatusToMmsStatus(httpStatus,
1063 this.cancelledReason,
1064 MMS.MMS_PDU_STATUS_DEFERRED);
1065 if (mmsStatus != MMS.MMS_PDU_ERROR_OK) {
1066 callback(mmsStatus, null);
1067 return;
1068 }
1069 if (!data) {
1070 callback(MMS.MMS_PDU_STATUS_DEFERRED, null);
1071 return;
1072 }
1074 let retrieved = MMS.PduHelper.parse(data, null);
1075 if (!retrieved || (retrieved.type != MMS.MMS_PDU_TYPE_RETRIEVE_CONF)) {
1076 callback(MMS.MMS_PDU_STATUS_UNRECOGNISED, null);
1077 return;
1078 }
1080 // Fix default header field values.
1081 if (retrieved.headers["x-mms-delivery-report"] == null) {
1082 retrieved.headers["x-mms-delivery-report"] = false;
1083 }
1085 let retrieveStatus = retrieved.headers["x-mms-retrieve-status"];
1086 if ((retrieveStatus != null) &&
1087 (retrieveStatus != MMS.MMS_PDU_ERROR_OK)) {
1088 callback(MMS.translatePduErrorToStatus(retrieveStatus), retrieved);
1089 return;
1090 }
1092 callback(MMS.MMS_PDU_STATUS_RETRIEVED, retrieved);
1093 }).bind(this));
1094 },
1095 enumerable: true,
1096 configurable: true,
1097 writable: true
1098 }
1099 });
1101 /**
1102 * SendTransaction.
1103 * Class for sending M-Send.req to MMSC, which inherits CancellableTransaction.
1104 * @throws Error("Check max values parameters fail.")
1105 */
1106 function SendTransaction(mmsConnection, cancellableId, msg, requestDeliveryReport) {
1107 this.mmsConnection = mmsConnection;
1109 // Call |CancellableTransaction| constructor.
1110 CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId);
1112 msg.headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_SEND_REQ;
1113 if (!msg.headers["x-mms-transaction-id"]) {
1114 // Create an unique transaction id
1115 let tid = gUUIDGenerator.generateUUID().toString();
1116 msg.headers["x-mms-transaction-id"] = tid;
1117 }
1118 msg.headers["x-mms-mms-version"] = MMS.MMS_VERSION;
1120 // Let MMS Proxy Relay insert from address automatically for us
1121 msg.headers["from"] = null;
1123 msg.headers["date"] = new Date();
1124 msg.headers["x-mms-message-class"] = "personal";
1125 msg.headers["x-mms-expiry"] = 7 * 24 * 60 * 60;
1126 msg.headers["x-mms-priority"] = 129;
1127 try {
1128 msg.headers["x-mms-read-report"] =
1129 Services.prefs.getBoolPref("dom.mms.requestReadReport");
1130 } catch (e) {
1131 msg.headers["x-mms-read-report"] = true;
1132 }
1133 msg.headers["x-mms-delivery-report"] = requestDeliveryReport;
1135 if (!gMmsTransactionHelper.checkMaxValuesParameters(msg)) {
1136 //We should notify end user that the header format is wrong.
1137 if (DEBUG) debug("Check max values parameters fail.");
1138 throw new Error("Check max values parameters fail.");
1139 }
1141 if (msg.parts) {
1142 let contentType = {
1143 params: {
1144 // `The type parameter must be specified and its value is the MIME
1145 // media type of the "root" body part.` ~ RFC 2387 clause 3.1
1146 type: msg.parts[0].headers["content-type"].media,
1147 },
1148 };
1150 // `The Content-Type in M-Send.req and M-Retrieve.conf SHALL be
1151 // application/vnd.wap.multipart.mixed when there is no presentation, and
1152 // application/vnd.wap.multipart.related SHALL be used when there is SMIL
1153 // presentation available.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
1154 if (contentType.params.type === "application/smil") {
1155 contentType.media = "application/vnd.wap.multipart.related";
1157 // `The start parameter, if given, is the content-ID of the compound
1158 // object's "root".` ~ RFC 2387 clause 3.2
1159 contentType.params.start = msg.parts[0].headers["content-id"];
1160 } else {
1161 contentType.media = "application/vnd.wap.multipart.mixed";
1162 }
1164 // Assign to Content-Type
1165 msg.headers["content-type"] = contentType;
1166 }
1168 if (DEBUG) debug("msg: " + JSON.stringify(msg));
1170 this.msg = msg;
1171 }
1172 SendTransaction.prototype = Object.create(CancellableTransaction.prototype, {
1173 istreamComposed: {
1174 value: false,
1175 enumerable: true,
1176 configurable: true,
1177 writable: true
1178 },
1180 /**
1181 * @param parts
1182 * 'parts' property of a parsed MMS message.
1183 * @param callback [optional]
1184 * A callback function that takes zero argument.
1185 */
1186 loadBlobs: {
1187 value: function(parts, callback) {
1188 let callbackIfValid = function callbackIfValid() {
1189 if (DEBUG) debug("All parts loaded: " + JSON.stringify(parts));
1190 if (callback) {
1191 callback();
1192 }
1193 }
1195 if (!parts || !parts.length) {
1196 callbackIfValid();
1197 return;
1198 }
1200 let numPartsToLoad = parts.length;
1201 for each (let part in parts) {
1202 if (!(part.content instanceof Ci.nsIDOMBlob)) {
1203 numPartsToLoad--;
1204 if (!numPartsToLoad) {
1205 callbackIfValid();
1206 return;
1207 }
1208 continue;
1209 }
1210 let fileReader = Cc["@mozilla.org/files/filereader;1"]
1211 .createInstance(Ci.nsIDOMFileReader);
1212 fileReader.addEventListener("loadend",
1213 (function onloadend(part, event) {
1214 let arrayBuffer = event.target.result;
1215 part.content = new Uint8Array(arrayBuffer);
1216 numPartsToLoad--;
1217 if (!numPartsToLoad) {
1218 callbackIfValid();
1219 }
1220 }).bind(null, part));
1221 fileReader.readAsArrayBuffer(part.content);
1222 };
1223 },
1224 enumerable: true,
1225 configurable: true,
1226 writable: true
1227 },
1229 /**
1230 * @param callback [optional]
1231 * A callback function that takes two arguments: one for
1232 * X-Mms-Response-Status, the other for the parsed M-Send.conf message.
1233 */
1234 run: {
1235 value: function(callback) {
1236 this.registerRunCallback(callback);
1238 if (!this.istreamComposed) {
1239 this.loadBlobs(this.msg.parts, (function() {
1240 this.istream = MMS.PduHelper.compose(null, this.msg);
1241 this.istreamSize = this.istream.available();
1242 this.istreamComposed = true;
1243 if (this.isCancelled) {
1244 this.runCallbackIfValid(_MMS_ERROR_MESSAGE_DELETED, null);
1245 } else {
1246 this.run(callback);
1247 }
1248 }).bind(this));
1249 return;
1250 }
1252 if (!this.istream) {
1253 this.runCallbackIfValid(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
1254 return;
1255 }
1257 this.retryCount = 0;
1258 let retryCallback = (function(mmsStatus, msg) {
1259 if ((MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE == mmsStatus ||
1260 MMS.MMS_PDU_ERROR_PERMANENT_FAILURE == mmsStatus) &&
1261 this.retryCount < PREF_SEND_RETRY_COUNT) {
1262 if (DEBUG) {
1263 debug("Fail to send. Will retry after: " + PREF_SEND_RETRY_INTERVAL);
1264 }
1266 if (this.timer == null) {
1267 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1268 }
1270 this.retryCount++;
1272 // the input stream may be read in the previous failure request so
1273 // we have to re-compose it.
1274 if (this.istreamSize == null ||
1275 this.istreamSize != this.istream.available()) {
1276 this.istream = MMS.PduHelper.compose(null, this.msg);
1277 }
1279 this.timer.initWithCallback(this.send.bind(this, retryCallback),
1280 PREF_SEND_RETRY_INTERVAL,
1281 Ci.nsITimer.TYPE_ONE_SHOT);
1282 return;
1283 }
1285 this.runCallbackIfValid(mmsStatus, msg);
1286 }).bind(this);
1288 // This is the entry point to start sending.
1289 this.send(retryCallback);
1290 },
1291 enumerable: true,
1292 configurable: true,
1293 writable: true
1294 },
1296 /**
1297 * @param callback
1298 * A callback function that takes two arguments: one for
1299 * X-Mms-Response-Status, the other for the parsed M-Send.conf message.
1300 */
1301 send: {
1302 value: function(callback) {
1303 this.timer = null;
1305 this.cancellable =
1306 gMmsTransactionHelper.sendRequest(this.mmsConnection,
1307 "POST",
1308 null,
1309 this.istream,
1310 (function(httpStatus, data) {
1311 let mmsStatus = gMmsTransactionHelper.
1312 translateHttpStatusToMmsStatus(
1313 httpStatus,
1314 this.cancelledReason,
1315 MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE);
1316 if (httpStatus != HTTP_STATUS_OK) {
1317 callback(mmsStatus, null);
1318 return;
1319 }
1321 if (!data) {
1322 callback(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
1323 return;
1324 }
1326 let response = MMS.PduHelper.parse(data, null);
1327 if (!response || (response.type != MMS.MMS_PDU_TYPE_SEND_CONF)) {
1328 callback(MMS.MMS_PDU_RESPONSE_ERROR_UNSUPPORTED_MESSAGE, null);
1329 return;
1330 }
1332 let responseStatus = response.headers["x-mms-response-status"];
1333 callback(responseStatus, response);
1334 }).bind(this));
1335 },
1336 enumerable: true,
1337 configurable: true,
1338 writable: true
1339 }
1340 });
1342 /**
1343 * Send M-acknowledge.ind back to MMSC.
1344 *
1345 * @param mmsConnection
1346 * The MMS connection.
1347 * @param transactionId
1348 * X-Mms-Transaction-ID of the message.
1349 * @param reportAllowed
1350 * X-Mms-Report-Allowed of the response.
1351 *
1352 * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.4
1353 */
1354 function AcknowledgeTransaction(mmsConnection, transactionId, reportAllowed) {
1355 this.mmsConnection = mmsConnection;
1356 let headers = {};
1358 // Mandatory fields
1359 headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_ACKNOWLEDGE_IND;
1360 headers["x-mms-transaction-id"] = transactionId;
1361 headers["x-mms-mms-version"] = MMS.MMS_VERSION;
1362 // Optional fields
1363 headers["x-mms-report-allowed"] = reportAllowed;
1365 this.istream = MMS.PduHelper.compose(null, {headers: headers});
1366 }
1367 AcknowledgeTransaction.prototype = {
1368 /**
1369 * @param callback [optional]
1370 * A callback function that takes one argument -- the http status.
1371 */
1372 run: function(callback) {
1373 let requestCallback;
1374 if (callback) {
1375 requestCallback = function(httpStatus, data) {
1376 // `The MMS Client SHOULD ignore the associated HTTP POST response
1377 // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A
1378 // section 8.2.3 "Retrieving an MM".
1379 callback(httpStatus);
1380 };
1381 }
1382 gMmsTransactionHelper.sendRequest(this.mmsConnection,
1383 "POST",
1384 null,
1385 this.istream,
1386 requestCallback);
1387 }
1388 };
1390 /**
1391 * Return M-Read-Rec.ind back to MMSC
1392 *
1393 * @param messageID
1394 * Message-ID of the message.
1395 * @param toAddress
1396 * The address of the recipient of the Read Report, i.e. the originator
1397 * of the original multimedia message.
1398 *
1399 * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.7.2
1400 */
1401 function ReadRecTransaction(mmsConnection, messageID, toAddress) {
1402 this.mmsConnection = mmsConnection;
1403 let headers = {};
1405 // Mandatory fields
1406 headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_READ_REC_IND;
1407 headers["x-mms-mms-version"] = MMS.MMS_VERSION;
1408 headers["message-id"] = messageID;
1409 let type = MMS.Address.resolveType(toAddress);
1410 let to = {address: toAddress,
1411 type: type}
1412 headers["to"] = to;
1413 headers["from"] = null;
1414 headers["x-mms-read-status"] = MMS.MMS_PDU_READ_STATUS_READ;
1416 this.istream = MMS.PduHelper.compose(null, {headers: headers});
1417 if (!this.istream) {
1418 throw Cr.NS_ERROR_FAILURE;
1419 }
1420 }
1421 ReadRecTransaction.prototype = {
1422 run: function() {
1423 gMmsTransactionHelper.sendRequest(this.mmsConnection,
1424 "POST",
1425 null,
1426 this.istream,
1427 null);
1428 }
1429 };
1431 /**
1432 * MmsService
1433 */
1434 function MmsService() {
1435 if (DEBUG) {
1436 let macro = (MMS.MMS_VERSION >> 4) & 0x0f;
1437 let minor = MMS.MMS_VERSION & 0x0f;
1438 debug("Running protocol version: " + macro + "." + minor);
1439 }
1441 Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
1442 this.mmsDefaultServiceId = getDefaultServiceId();
1444 // TODO: bug 810084 - support application identifier
1445 }
1446 MmsService.prototype = {
1448 classID: RIL_MMSSERVICE_CID,
1449 QueryInterface: XPCOMUtils.generateQI([Ci.nsIMmsService,
1450 Ci.nsIWapPushApplication,
1451 Ci.nsIObserver]),
1452 /*
1453 * Whether or not should we enable X-Mms-Report-Allowed in M-NotifyResp.ind
1454 * and M-Acknowledge.ind PDU.
1455 */
1456 confSendDeliveryReport: CONFIG_SEND_REPORT_DEFAULT_YES,
1458 /**
1459 * Calculate Whether or not should we enable X-Mms-Report-Allowed.
1460 *
1461 * @param config
1462 * Current configure value.
1463 * @param wish
1464 * Sender wish. Could be undefined, false, or true.
1465 */
1466 getReportAllowed: function(config, wish) {
1467 if ((config == CONFIG_SEND_REPORT_DEFAULT_NO)
1468 || (config == CONFIG_SEND_REPORT_DEFAULT_YES)) {
1469 if (wish != null) {
1470 config += (wish ? 1 : -1);
1471 }
1472 }
1473 return config >= CONFIG_SEND_REPORT_DEFAULT_YES;
1474 },
1476 /**
1477 * Convert intermediate message to indexedDB savable object.
1478 *
1479 * @param mmsConnection
1480 * The MMS connection.
1481 * @param retrievalMode
1482 * Retrieval mode for MMS receiving setting.
1483 * @param intermediate
1484 * Intermediate MMS message parsed from PDU.
1485 */
1486 convertIntermediateToSavable: function(mmsConnection, intermediate,
1487 retrievalMode) {
1488 intermediate.type = "mms";
1489 intermediate.delivery = DELIVERY_NOT_DOWNLOADED;
1491 let deliveryStatus;
1492 switch (retrievalMode) {
1493 case RETRIEVAL_MODE_MANUAL:
1494 deliveryStatus = DELIVERY_STATUS_MANUAL;
1495 break;
1496 case RETRIEVAL_MODE_NEVER:
1497 deliveryStatus = DELIVERY_STATUS_REJECTED;
1498 break;
1499 case RETRIEVAL_MODE_AUTOMATIC:
1500 deliveryStatus = DELIVERY_STATUS_PENDING;
1501 break;
1502 case RETRIEVAL_MODE_AUTOMATIC_HOME:
1503 if (mmsConnection.isVoiceRoaming()) {
1504 deliveryStatus = DELIVERY_STATUS_MANUAL;
1505 } else {
1506 deliveryStatus = DELIVERY_STATUS_PENDING;
1507 }
1508 break;
1509 default:
1510 deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
1511 break;
1512 }
1513 // |intermediate.deliveryStatus| will be deleted after being stored in db.
1514 intermediate.deliveryStatus = deliveryStatus;
1516 intermediate.timestamp = Date.now();
1517 intermediate.receivers = [];
1518 intermediate.phoneNumber = mmsConnection.getPhoneNumber();
1519 intermediate.iccId = mmsConnection.getIccId();
1520 return intermediate;
1521 },
1523 /**
1524 * Merge the retrieval confirmation into the savable message.
1525 *
1526 * @param mmsConnection
1527 * The MMS connection.
1528 * @param intermediate
1529 * Intermediate MMS message parsed from PDU, which carries
1530 * the retrieval confirmation.
1531 * @param savable
1532 * The indexedDB savable MMS message, which is going to be
1533 * merged with the extra retrieval confirmation.
1534 */
1535 mergeRetrievalConfirmation: function(mmsConnection, intermediate, savable) {
1536 // Prepare timestamp/sentTimestamp.
1537 savable.timestamp = Date.now();
1538 savable.sentTimestamp = intermediate.headers["date"].getTime();
1540 savable.receivers = [];
1541 // We don't have Bcc in recevied MMS message.
1542 for each (let type in ["cc", "to"]) {
1543 if (intermediate.headers[type]) {
1544 if (intermediate.headers[type] instanceof Array) {
1545 for (let index in intermediate.headers[type]) {
1546 savable.receivers.push(intermediate.headers[type][index].address);
1547 }
1548 } else {
1549 savable.receivers.push(intermediate.headers[type].address);
1550 }
1551 }
1552 }
1554 savable.delivery = DELIVERY_RECEIVED;
1555 // |savable.deliveryStatus| will be deleted after being stored in db.
1556 savable.deliveryStatus = DELIVERY_STATUS_SUCCESS;
1557 for (let field in intermediate.headers) {
1558 savable.headers[field] = intermediate.headers[field];
1559 }
1560 if (intermediate.parts) {
1561 savable.parts = intermediate.parts;
1562 }
1563 if (intermediate.content) {
1564 savable.content = intermediate.content;
1565 }
1566 return savable;
1567 },
1569 /**
1570 * @param aMmsConnection
1571 * The MMS connection.
1572 * @param aContentLocation
1573 * X-Mms-Content-Location of the message.
1574 * @param aCallback [optional]
1575 * A callback function that takes two arguments: one for X-Mms-Status,
1576 * the other parsed MMS message.
1577 * @param aDomMessage
1578 * The nsIDOMMozMmsMessage object.
1579 */
1580 retrieveMessage: function(aMmsConnection, aContentLocation, aCallback,
1581 aDomMessage) {
1582 // Notifying observers an MMS message is retrieving.
1583 Services.obs.notifyObservers(aDomMessage, kSmsRetrievingObserverTopic, null);
1585 let transaction = new RetrieveTransaction(aMmsConnection,
1586 aDomMessage.id,
1587 aContentLocation);
1588 transaction.run(aCallback);
1589 },
1591 /**
1592 * A helper to broadcast the system message to launch registered apps
1593 * like Costcontrol, Notification and Message app... etc.
1594 *
1595 * @param aName
1596 * The system message name.
1597 * @param aDomMessage
1598 * The nsIDOMMozMmsMessage object.
1599 */
1600 broadcastMmsSystemMessage: function(aName, aDomMessage) {
1601 if (DEBUG) debug("Broadcasting the MMS system message: " + aName);
1603 // Sadly we cannot directly broadcast the aDomMessage object
1604 // because the system message mechamism will rewrap the object
1605 // based on the content window, which needs to know the properties.
1606 gSystemMessenger.broadcastMessage(aName, {
1607 iccId: aDomMessage.iccId,
1608 type: aDomMessage.type,
1609 id: aDomMessage.id,
1610 threadId: aDomMessage.threadId,
1611 delivery: aDomMessage.delivery,
1612 deliveryInfo: aDomMessage.deliveryInfo,
1613 sender: aDomMessage.sender,
1614 receivers: aDomMessage.receivers,
1615 timestamp: aDomMessage.timestamp,
1616 sentTimestamp: aDomMessage.sentTimestamp,
1617 read: aDomMessage.read,
1618 subject: aDomMessage.subject,
1619 smil: aDomMessage.smil,
1620 attachments: aDomMessage.attachments,
1621 expiryDate: aDomMessage.expiryDate,
1622 readReportRequested: aDomMessage.readReportRequested
1623 });
1624 },
1626 /**
1627 * A helper function to broadcast system message and notify observers that
1628 * an MMS is sent.
1629 *
1630 * @params aDomMessage
1631 * The nsIDOMMozMmsMessage object.
1632 */
1633 broadcastSentMessageEvent: function(aDomMessage) {
1634 // Broadcasting a 'sms-sent' system message to open apps.
1635 this.broadcastMmsSystemMessage(kSmsSentObserverTopic, aDomMessage);
1637 // Notifying observers an MMS message is sent.
1638 Services.obs.notifyObservers(aDomMessage, kSmsSentObserverTopic, null);
1639 },
1641 /**
1642 * A helper function to broadcast system message and notify observers that
1643 * an MMS is received.
1644 *
1645 * @params aDomMessage
1646 * The nsIDOMMozMmsMessage object.
1647 */
1648 broadcastReceivedMessageEvent :function broadcastReceivedMessageEvent(aDomMessage) {
1649 // Broadcasting a 'sms-received' system message to open apps.
1650 this.broadcastMmsSystemMessage(kSmsReceivedObserverTopic, aDomMessage);
1652 // Notifying observers an MMS message is received.
1653 Services.obs.notifyObservers(aDomMessage, kSmsReceivedObserverTopic, null);
1654 },
1656 /**
1657 * Callback for retrieveMessage.
1658 */
1659 retrieveMessageCallback: function(mmsConnection, wish, savableMessage,
1660 mmsStatus, retrievedMessage) {
1661 if (DEBUG) debug("retrievedMessage = " + JSON.stringify(retrievedMessage));
1663 let transactionId = savableMessage.headers["x-mms-transaction-id"];
1665 // The absence of the field does not indicate any default
1666 // value. So we go check the same field in the retrieved
1667 // message instead.
1668 if (wish == null && retrievedMessage) {
1669 wish = retrievedMessage.headers["x-mms-delivery-report"];
1670 }
1672 let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
1673 wish);
1674 // If the mmsStatus isn't MMS_PDU_STATUS_RETRIEVED after retrieving,
1675 // something must be wrong with MMSC, so stop updating the DB record.
1676 // We could send a message to content to notify the user the MMS
1677 // retrieving failed. The end user has to retrieve the MMS again.
1678 if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
1679 if (mmsStatus != _MMS_ERROR_RADIO_DISABLED &&
1680 mmsStatus != _MMS_ERROR_NO_SIM_CARD &&
1681 mmsStatus != _MMS_ERROR_SIM_CARD_CHANGED) {
1682 let transaction = new NotifyResponseTransaction(mmsConnection,
1683 transactionId,
1684 mmsStatus,
1685 reportAllowed);
1686 transaction.run();
1687 }
1688 // Retrieved fail after retry, so we update the delivery status in DB and
1689 // notify this domMessage that error happen.
1690 gMobileMessageDatabaseService
1691 .setMessageDeliveryByMessageId(savableMessage.id,
1692 null,
1693 null,
1694 DELIVERY_STATUS_ERROR,
1695 null,
1696 (function(rv, domMessage) {
1697 this.broadcastReceivedMessageEvent(domMessage);
1698 }).bind(this));
1699 return;
1700 }
1702 savableMessage = this.mergeRetrievalConfirmation(mmsConnection,
1703 retrievedMessage,
1704 savableMessage);
1705 gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
1706 (function(rv, domMessage) {
1707 let success = Components.isSuccessCode(rv);
1709 // Cite 6.2.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
1710 // The M-NotifyResp.ind response PDU SHALL provide a message retrieval
1711 // status code. The status ‘retrieved’ SHALL be used only if the MMS
1712 // Client has successfully retrieved the MM prior to sending the
1713 // NotifyResp.ind response PDU.
1714 let transaction =
1715 new NotifyResponseTransaction(mmsConnection,
1716 transactionId,
1717 success ? MMS.MMS_PDU_STATUS_RETRIEVED
1718 : MMS.MMS_PDU_STATUS_DEFERRED,
1719 reportAllowed);
1720 transaction.run();
1722 if (!success) {
1723 // At this point we could send a message to content to notify the user
1724 // that storing an incoming MMS failed, most likely due to a full disk.
1725 // The end user has to retrieve the MMS again.
1726 if (DEBUG) debug("Could not store MMS , error code " + rv);
1727 return;
1728 }
1730 this.broadcastReceivedMessageEvent(domMessage);
1731 }).bind(this));
1732 },
1734 /**
1735 * Callback for saveReceivedMessage.
1736 */
1737 saveReceivedMessageCallback: function(mmsConnection, retrievalMode,
1738 savableMessage, rv, domMessage) {
1739 let success = Components.isSuccessCode(rv);
1740 if (!success) {
1741 // At this point we could send a message to content to notify the
1742 // user that storing an incoming MMS notification indication failed,
1743 // ost likely due to a full disk.
1744 if (DEBUG) debug("Could not store MMS " + JSON.stringify(savableMessage) +
1745 ", error code " + rv);
1746 // Because MMSC will resend the notification indication once we don't
1747 // response the notification. Hope the end user will clean some space
1748 // for the resent notification indication.
1749 return;
1750 }
1752 // For X-Mms-Report-Allowed and X-Mms-Transaction-Id
1753 let wish = savableMessage.headers["x-mms-delivery-report"];
1754 let transactionId = savableMessage.headers["x-mms-transaction-id"];
1756 this.broadcastReceivedMessageEvent(domMessage);
1758 // To avoid costing money, we only send notify response when it's under
1759 // the "automatic" retrieval mode or it's not in the roaming environment.
1760 if (retrievalMode !== RETRIEVAL_MODE_AUTOMATIC &&
1761 mmsConnection.isVoiceRoaming()) {
1762 return;
1763 }
1765 if (RETRIEVAL_MODE_MANUAL === retrievalMode ||
1766 RETRIEVAL_MODE_NEVER === retrievalMode) {
1767 let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode
1768 ? MMS.MMS_PDU_STATUS_REJECTED
1769 : MMS.MMS_PDU_STATUS_DEFERRED;
1771 // For X-Mms-Report-Allowed
1772 let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
1773 wish);
1775 let transaction = new NotifyResponseTransaction(mmsConnection,
1776 transactionId,
1777 mmsStatus,
1778 reportAllowed);
1779 transaction.run();
1780 return;
1781 }
1783 let url = savableMessage.headers["x-mms-content-location"].uri;
1785 // For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not
1786 // roaming, proceed to retrieve MMS.
1787 this.retrieveMessage(mmsConnection,
1788 url,
1789 this.retrieveMessageCallback.bind(this,
1790 mmsConnection,
1791 wish,
1792 savableMessage),
1793 domMessage);
1794 },
1796 /**
1797 * Handle incoming M-Notification.ind PDU.
1798 *
1799 * @param serviceId
1800 * The ID of the service for receiving the PDU data.
1801 * @param notification
1802 * The parsed MMS message object.
1803 */
1804 handleNotificationIndication: function(serviceId, notification) {
1805 let transactionId = notification.headers["x-mms-transaction-id"];
1806 gMobileMessageDatabaseService.getMessageRecordByTransactionId(transactionId,
1807 (function(aRv, aMessageRecord) {
1808 if (Components.isSuccessCode(aRv) && aMessageRecord) {
1809 if (DEBUG) debug("We already got the NotificationIndication with transactionId = "
1810 + transactionId + " before.");
1811 return;
1812 }
1814 let retrievalMode = RETRIEVAL_MODE_MANUAL;
1815 try {
1816 retrievalMode = Services.prefs.getCharPref(kPrefRetrievalMode);
1817 } catch (e) {}
1819 // Under the "automatic"/"automatic-home" retrieval mode, we switch to
1820 // the "manual" retrieval mode to download MMS for non-active SIM.
1821 if ((retrievalMode == RETRIEVAL_MODE_AUTOMATIC ||
1822 retrievalMode == RETRIEVAL_MODE_AUTOMATIC_HOME) &&
1823 serviceId != this.mmsDefaultServiceId) {
1824 if (DEBUG) {
1825 debug("Switch to 'manual' mode to download MMS for non-active SIM: " +
1826 "serviceId = " + serviceId + " doesn't equal to " +
1827 "mmsDefaultServiceId = " + this.mmsDefaultServiceId);
1828 }
1830 retrievalMode = RETRIEVAL_MODE_MANUAL;
1831 }
1833 let mmsConnection = gMmsConnections.getConnByServiceId(serviceId);
1835 let savableMessage = this.convertIntermediateToSavable(mmsConnection,
1836 notification,
1837 retrievalMode);
1839 gMobileMessageDatabaseService
1840 .saveReceivedMessage(savableMessage,
1841 this.saveReceivedMessageCallback
1842 .bind(this,
1843 mmsConnection,
1844 retrievalMode,
1845 savableMessage));
1846 }).bind(this));
1847 },
1849 /**
1850 * Handle incoming M-Delivery.ind PDU.
1851 *
1852 * @param aMsg
1853 * The MMS message object.
1854 */
1855 handleDeliveryIndication: function(aMsg) {
1856 let headers = aMsg.headers;
1857 let envelopeId = headers["message-id"];
1858 let address = headers.to.address;
1859 let mmsStatus = headers["x-mms-status"];
1860 if (DEBUG) {
1861 debug("Start updating the delivery status for envelopeId: " + envelopeId +
1862 " address: " + address + " mmsStatus: " + mmsStatus);
1863 }
1865 // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.3 "X-Mms-Status",
1866 // in the M-Delivery.ind the X-Mms-Status could be MMS.MMS_PDU_STATUS_{
1867 // EXPIRED, RETRIEVED, REJECTED, DEFERRED, UNRECOGNISED, INDETERMINATE,
1868 // FORWARDED, UNREACHABLE }.
1869 let deliveryStatus;
1870 switch (mmsStatus) {
1871 case MMS.MMS_PDU_STATUS_RETRIEVED:
1872 deliveryStatus = DELIVERY_STATUS_SUCCESS;
1873 break;
1874 case MMS.MMS_PDU_STATUS_EXPIRED:
1875 case MMS.MMS_PDU_STATUS_REJECTED:
1876 case MMS.MMS_PDU_STATUS_UNRECOGNISED:
1877 case MMS.MMS_PDU_STATUS_UNREACHABLE:
1878 deliveryStatus = DELIVERY_STATUS_REJECTED;
1879 break;
1880 case MMS.MMS_PDU_STATUS_DEFERRED:
1881 deliveryStatus = DELIVERY_STATUS_PENDING;
1882 break;
1883 case MMS.MMS_PDU_STATUS_INDETERMINATE:
1884 deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
1885 break;
1886 default:
1887 if (DEBUG) debug("Cannot handle this MMS status. Returning.");
1888 return;
1889 }
1891 if (DEBUG) debug("Updating the delivery status to: " + deliveryStatus);
1892 gMobileMessageDatabaseService
1893 .setMessageDeliveryStatusByEnvelopeId(envelopeId, address, deliveryStatus,
1894 (function(aRv, aDomMessage) {
1895 if (DEBUG) debug("Marking the delivery status is done.");
1896 // TODO bug 832140 handle !Components.isSuccessCode(aRv)
1898 let topic;
1899 if (mmsStatus === MMS.MMS_PDU_STATUS_RETRIEVED) {
1900 topic = kSmsDeliverySuccessObserverTopic;
1902 // Broadcasting a 'sms-delivery-success' system message to open apps.
1903 this.broadcastMmsSystemMessage(topic, aDomMessage);
1904 } else if (mmsStatus === MMS.MMS_PDU_STATUS_REJECTED) {
1905 topic = kSmsDeliveryErrorObserverTopic;
1906 } else {
1907 if (DEBUG) debug("Needn't fire event for this MMS status. Returning.");
1908 return;
1909 }
1911 // Notifying observers the delivery status is updated.
1912 Services.obs.notifyObservers(aDomMessage, topic, null);
1913 }).bind(this));
1914 },
1916 /**
1917 * Handle incoming M-Read-Orig.ind PDU.
1918 *
1919 * @param aIndication
1920 * The MMS message object.
1921 */
1922 handleReadOriginateIndication: function(aIndication) {
1924 let headers = aIndication.headers;
1925 let envelopeId = headers["message-id"];
1926 let address = headers.from.address;
1927 let mmsReadStatus = headers["x-mms-read-status"];
1928 if (DEBUG) {
1929 debug("Start updating the read status for envelopeId: " + envelopeId +
1930 ", address: " + address + ", mmsReadStatus: " + mmsReadStatus);
1931 }
1933 // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.4 "X-Mms-Read-Status",
1934 // in M-Read-Rec-Orig.ind the X-Mms-Read-Status could be
1935 // MMS.MMS_READ_STATUS_{ READ, DELETED_WITHOUT_BEING_READ }.
1936 let readStatus = mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ
1937 ? MMS.DOM_READ_STATUS_SUCCESS
1938 : MMS.DOM_READ_STATUS_ERROR;
1939 if (DEBUG) debug("Updating the read status to: " + readStatus);
1941 gMobileMessageDatabaseService
1942 .setMessageReadStatusByEnvelopeId(envelopeId, address, readStatus,
1943 (function(aRv, aDomMessage) {
1944 if (!Components.isSuccessCode(aRv)) {
1945 // Notifying observers the read status is error.
1946 Services.obs.notifyObservers(aDomMessage, kSmsReadSuccessObserverTopic, null);
1947 return;
1948 }
1950 if (DEBUG) debug("Marking the read status is done.");
1951 let topic;
1952 if (mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ) {
1953 topic = kSmsReadSuccessObserverTopic;
1955 // Broadcasting a 'sms-read-success' system message to open apps.
1956 this.broadcastMmsSystemMessage(topic, aDomMessage);
1957 } else {
1958 topic = kSmsReadErrorObserverTopic;
1959 }
1961 // Notifying observers the read status is updated.
1962 Services.obs.notifyObservers(aDomMessage, topic, null);
1963 }).bind(this));
1964 },
1966 /**
1967 * A utility function to convert the MmsParameters dictionary object
1968 * to a database-savable message.
1969 *
1970 * @param aMmsConnection
1971 * The MMS connection.
1972 * @param aParams
1973 * The MmsParameters dictionay object.
1974 * @param aMessage (output)
1975 * The database-savable message.
1976 * Return the error code by veryfying if the |aParams| is valid or not.
1977 *
1978 * Notes:
1979 *
1980 * OMA-TS-MMS-CONF-V1_3-20110913-A section 10.2.2 "Message Content Encoding":
1981 *
1982 * A name for multipart object SHALL be encoded using name-parameter for Content-Type
1983 * header in WSP multipart headers. In decoding, name-parameter of Content-Type SHALL
1984 * be used if available. If name-parameter of Content-Type is not available, filename
1985 * parameter of Content-Disposition header SHALL be used if available. If neither
1986 * name-parameter of Content-Type header nor filename parameter of Content-Disposition
1987 * header is available, Content-Location header SHALL be used if available.
1988 */
1989 createSavableFromParams: function(aMmsConnection, aParams, aMessage) {
1990 if (DEBUG) debug("createSavableFromParams: aParams: " + JSON.stringify(aParams));
1992 let isAddrValid = true;
1993 let smil = aParams.smil;
1995 // |aMessage.headers|
1996 let headers = aMessage["headers"] = {};
1997 let receivers = aParams.receivers;
1998 if (receivers.length != 0) {
1999 let headersTo = headers["to"] = [];
2000 for (let i = 0; i < receivers.length; i++) {
2001 let receiver = receivers[i];
2002 let type = MMS.Address.resolveType(receiver);
2003 let address;
2004 if (type == "PLMN") {
2005 address = PhoneNumberUtils.normalize(receiver, false);
2006 if (!PhoneNumberUtils.isPlainPhoneNumber(address)) {
2007 isAddrValid = false;
2008 }
2009 if (DEBUG) debug("createSavableFromParams: normalize phone number " +
2010 "from " + receiver + " to " + address);
2011 } else {
2012 address = receiver;
2013 isAddrValid = false;
2014 if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
2015 }
2016 headersTo.push({"address": address, "type": type});
2017 }
2018 }
2019 if (aParams.subject) {
2020 headers["subject"] = aParams.subject;
2021 }
2023 // |aMessage.parts|
2024 let attachments = aParams.attachments;
2025 if (attachments.length != 0 || smil) {
2026 let parts = aMessage["parts"] = [];
2028 // Set the SMIL part if needed.
2029 if (smil) {
2030 let part = {
2031 "headers": {
2032 "content-type": {
2033 "media": "application/smil",
2034 "params": {
2035 "name": "smil.xml",
2036 "charset": {
2037 "charset": "utf-8"
2038 }
2039 }
2040 },
2041 "content-location": "smil.xml",
2042 "content-id": "<smil>"
2043 },
2044 "content": smil
2045 };
2046 parts.push(part);
2047 }
2049 // Set other parts for attachments if needed.
2050 for (let i = 0; i < attachments.length; i++) {
2051 let attachment = attachments[i];
2052 let content = attachment.content;
2053 let location = attachment.location;
2055 let params = {
2056 "name": location
2057 };
2059 if (content.type && content.type.indexOf("text/") == 0) {
2060 params.charset = {
2061 "charset": "utf-8"
2062 };
2063 }
2065 let part = {
2066 "headers": {
2067 "content-type": {
2068 "media": content.type,
2069 "params": params
2070 },
2071 "content-location": location,
2072 "content-id": attachment.id
2073 },
2074 "content": content
2075 };
2076 parts.push(part);
2077 }
2078 }
2080 // The following attributes are needed for saving message into DB.
2081 aMessage["type"] = "mms";
2082 aMessage["timestamp"] = Date.now();
2083 aMessage["receivers"] = receivers;
2084 aMessage["sender"] = aMmsConnection.getPhoneNumber();
2085 aMessage["iccId"] = aMmsConnection.getIccId();
2086 try {
2087 aMessage["deliveryStatusRequested"] =
2088 Services.prefs.getBoolPref("dom.mms.requestStatusReport");
2089 } catch (e) {
2090 aMessage["deliveryStatusRequested"] = false;
2091 }
2093 if (DEBUG) debug("createSavableFromParams: aMessage: " +
2094 JSON.stringify(aMessage));
2096 return isAddrValid ? Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR
2097 : Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR;
2098 },
2100 // nsIMmsService
2102 mmsDefaultServiceId: 0,
2104 send: function(aServiceId, aParams, aRequest) {
2105 if (DEBUG) debug("send: aParams: " + JSON.stringify(aParams));
2107 // Note that the following sanity checks for |aParams| should be consistent
2108 // with the checks in SmsIPCService.GetSendMmsMessageRequestFromParams.
2110 // Check if |aParams| is valid.
2111 if (aParams == null || typeof aParams != "object") {
2112 if (DEBUG) debug("Error! 'aParams' should be a non-null object.");
2113 throw Cr.NS_ERROR_INVALID_ARG;
2114 return;
2115 }
2117 // Check if |receivers| is valid.
2118 if (!Array.isArray(aParams.receivers)) {
2119 if (DEBUG) debug("Error! 'receivers' should be an array.");
2120 throw Cr.NS_ERROR_INVALID_ARG;
2121 return;
2122 }
2124 // Check if |subject| is valid.
2125 if (aParams.subject != null && typeof aParams.subject != "string") {
2126 if (DEBUG) debug("Error! 'subject' should be a string if passed.");
2127 throw Cr.NS_ERROR_INVALID_ARG;
2128 return;
2129 }
2131 // Check if |smil| is valid.
2132 if (aParams.smil != null && typeof aParams.smil != "string") {
2133 if (DEBUG) debug("Error! 'smil' should be a string if passed.");
2134 throw Cr.NS_ERROR_INVALID_ARG;
2135 return;
2136 }
2138 // Check if |attachments| is valid.
2139 if (!Array.isArray(aParams.attachments)) {
2140 if (DEBUG) debug("Error! 'attachments' should be an array.");
2141 throw Cr.NS_ERROR_INVALID_ARG;
2142 return;
2143 }
2145 let self = this;
2147 let sendTransactionCb = function sendTransactionCb(aDomMessage,
2148 aErrorCode,
2149 aEnvelopeId) {
2150 if (DEBUG) {
2151 debug("The returned status of sending transaction: " +
2152 "aErrorCode: " + aErrorCode + " aEnvelopeId: " + aEnvelopeId);
2153 }
2155 // If the messsage has been deleted (because the sending process is
2156 // cancelled), we don't need to reset the its delievery state/status.
2157 if (aErrorCode == Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR) {
2158 aRequest.notifySendMessageFailed(aErrorCode);
2159 Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
2160 return;
2161 }
2163 let isSentSuccess = (aErrorCode == Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR);
2164 gMobileMessageDatabaseService
2165 .setMessageDeliveryByMessageId(aDomMessage.id,
2166 null,
2167 isSentSuccess ? DELIVERY_SENT : DELIVERY_ERROR,
2168 isSentSuccess ? null : DELIVERY_STATUS_ERROR,
2169 aEnvelopeId,
2170 function notifySetDeliveryResult(aRv, aDomMessage) {
2171 if (DEBUG) debug("Marking the delivery state/staus is done. Notify sent or failed.");
2172 // TODO bug 832140 handle !Components.isSuccessCode(aRv)
2173 if (!isSentSuccess) {
2174 if (DEBUG) debug("Sending MMS failed.");
2175 aRequest.notifySendMessageFailed(aErrorCode);
2176 Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
2177 return;
2178 }
2180 if (DEBUG) debug("Sending MMS succeeded.");
2182 // Notifying observers the MMS message is sent.
2183 self.broadcastSentMessageEvent(aDomMessage);
2185 // Return the request after sending the MMS message successfully.
2186 aRequest.notifyMessageSent(aDomMessage);
2187 });
2188 };
2190 let mmsConnection = gMmsConnections.getConnByServiceId(aServiceId);
2192 let savableMessage = {};
2193 let errorCode = this.createSavableFromParams(mmsConnection, aParams,
2194 savableMessage);
2195 gMobileMessageDatabaseService
2196 .saveSendingMessage(savableMessage,
2197 function notifySendingResult(aRv, aDomMessage) {
2198 if (!Components.isSuccessCode(aRv)) {
2199 if (DEBUG) debug("Error! Fail to save sending message! rv = " + aRv);
2200 aRequest.notifySendMessageFailed(
2201 gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv));
2202 Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
2203 return;
2204 }
2206 if (DEBUG) debug("Saving sending message is done. Start to send.");
2208 Services.obs.notifyObservers(aDomMessage, kSmsSendingObserverTopic, null);
2210 if (errorCode !== Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR) {
2211 if (DEBUG) debug("Error! The params for sending MMS are invalid.");
2212 sendTransactionCb(aDomMessage, errorCode, null);
2213 return;
2214 }
2216 // Check radio state in prior to default service Id.
2217 if (getRadioDisabledState()) {
2218 if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
2219 sendTransactionCb(aDomMessage,
2220 Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR,
2221 null);
2222 return;
2223 }
2225 // To support DSDS, we have to stop users sending MMS when the selected
2226 // SIM is not active, thus avoiding the data disconnection of the current
2227 // SIM. Users have to manually swith the default SIM before sending.
2228 if (mmsConnection.serviceId != self.mmsDefaultServiceId) {
2229 if (DEBUG) debug("RIL service is not active to send MMS.");
2230 sendTransactionCb(aDomMessage,
2231 Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR,
2232 null);
2233 return;
2234 }
2236 // This is the entry point starting to send MMS.
2237 let sendTransaction;
2238 try {
2239 sendTransaction =
2240 new SendTransaction(mmsConnection, aDomMessage.id, savableMessage,
2241 savableMessage["deliveryStatusRequested"]);
2242 } catch (e) {
2243 if (DEBUG) debug("Exception: fail to create a SendTransaction instance.");
2244 sendTransactionCb(aDomMessage,
2245 Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null);
2246 return;
2247 }
2248 sendTransaction.run(function callback(aMmsStatus, aMsg) {
2249 if (DEBUG) debug("The sending status of sendTransaction.run(): " + aMmsStatus);
2250 let errorCode;
2251 if (aMmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
2252 errorCode = Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR;
2253 } else if (aMmsStatus == _MMS_ERROR_RADIO_DISABLED) {
2254 errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
2255 } else if (aMmsStatus == _MMS_ERROR_NO_SIM_CARD) {
2256 errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
2257 } else if (aMmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) {
2258 errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR;
2259 } else if (aMmsStatus != MMS.MMS_PDU_ERROR_OK) {
2260 errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
2261 } else {
2262 errorCode = Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR;
2263 }
2264 let envelopeId =
2265 aMsg && aMsg.headers && aMsg.headers["message-id"] || null;
2266 sendTransactionCb(aDomMessage, errorCode, envelopeId);
2267 });
2268 });
2269 },
2271 retrieve: function(aMessageId, aRequest) {
2272 if (DEBUG) debug("Retrieving message with ID " + aMessageId);
2273 gMobileMessageDatabaseService.getMessageRecordById(aMessageId,
2274 (function notifyResult(aRv, aMessageRecord, aDomMessage) {
2275 if (!Components.isSuccessCode(aRv)) {
2276 if (DEBUG) debug("Function getMessageRecordById() return error: " + aRv);
2277 aRequest.notifyGetMessageFailed(
2278 gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv));
2279 return;
2280 }
2281 if ("mms" != aMessageRecord.type) {
2282 if (DEBUG) debug("Type of message record is not 'mms'.");
2283 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
2284 return;
2285 }
2286 if (!aMessageRecord.headers) {
2287 if (DEBUG) debug("Must need the MMS' headers to proceed the retrieve.");
2288 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
2289 return;
2290 }
2291 if (!aMessageRecord.headers["x-mms-content-location"]) {
2292 if (DEBUG) debug("Can't find mms content url in database.");
2293 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
2294 return;
2295 }
2296 if (DELIVERY_NOT_DOWNLOADED != aMessageRecord.delivery) {
2297 if (DEBUG) debug("Delivery of message record is not 'not-downloaded'.");
2298 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
2299 return;
2300 }
2301 let deliveryStatus = aMessageRecord.deliveryInfo[0].deliveryStatus;
2302 if (DELIVERY_STATUS_PENDING == deliveryStatus) {
2303 if (DEBUG) debug("Delivery status of message record is 'pending'.");
2304 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
2305 return;
2306 }
2308 // Cite 6.2 "Multimedia Message Notification" in OMA-TS-MMS_ENC-V1_3-20110913-A:
2309 // The field has only one format, relative. The recipient client calculates
2310 // this length of time relative to the time it receives the notification.
2311 if (aMessageRecord.headers["x-mms-expiry"] != undefined) {
2312 let expiryDate = aMessageRecord.timestamp +
2313 aMessageRecord.headers["x-mms-expiry"] * 1000;
2314 if (expiryDate < Date.now()) {
2315 if (DEBUG) debug("The message to be retrieved is expired.");
2316 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
2317 return;
2318 }
2319 }
2321 // IccInfo in RadioInterface is not available when radio is off and
2322 // NO_SIM_CARD_ERROR will be replied instead of RADIO_DISABLED_ERROR.
2323 // Hence, for manual retrieving, instead of checking radio state later
2324 // in MmsConnection.acquire(), We have to check radio state in prior to
2325 // iccId to return the error correctly.
2326 if (getRadioDisabledState()) {
2327 if (DEBUG) debug("Error! Radio is disabled when retrieving MMS.");
2328 aRequest.notifyGetMessageFailed(
2329 Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR);
2330 return;
2331 }
2333 // Get MmsConnection based on the saved MMS message record's ICC ID,
2334 // which could fail when the corresponding SIM card isn't installed.
2335 let mmsConnection;
2336 try {
2337 mmsConnection = gMmsConnections.getConnByIccId(aMessageRecord.iccId);
2338 } catch (e) {
2339 if (DEBUG) debug("Failed to get connection by IccId. e= " + e);
2340 let error = (e === _MMS_ERROR_SIM_NOT_MATCHED) ?
2341 Ci.nsIMobileMessageCallback.SIM_NOT_MATCHED_ERROR :
2342 Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
2343 aRequest.notifyGetMessageFailed(error);
2344 return;
2345 }
2347 // To support DSDS, we have to stop users retrieving MMS when the needed
2348 // SIM is not active, thus avoiding the data disconnection of the current
2349 // SIM. Users have to manually swith the default SIM before retrieving.
2350 if (mmsConnection.serviceId != this.mmsDefaultServiceId) {
2351 if (DEBUG) debug("RIL service is not active to retrieve MMS.");
2352 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR);
2353 return;
2354 }
2356 let url = aMessageRecord.headers["x-mms-content-location"].uri;
2357 // For X-Mms-Report-Allowed
2358 let wish = aMessageRecord.headers["x-mms-delivery-report"];
2359 let responseNotify = function responseNotify(mmsStatus, retrievedMsg) {
2360 // If the messsage has been deleted (because the retrieving process is
2361 // cancelled), we don't need to reset the its delievery state/status.
2362 if (mmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
2363 aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
2364 return;
2365 }
2367 // If the mmsStatus is still MMS_PDU_STATUS_DEFERRED after retry,
2368 // we should not store it into database and update its delivery
2369 // status to 'error'.
2370 if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
2371 if (DEBUG) debug("RetrieveMessage fail after retry.");
2372 let errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
2373 if (mmsStatus == _MMS_ERROR_RADIO_DISABLED) {
2374 errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
2375 } else if (mmsStatus == _MMS_ERROR_NO_SIM_CARD) {
2376 errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
2377 } else if (mmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) {
2378 errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR;
2379 }
2380 gMobileMessageDatabaseService
2381 .setMessageDeliveryByMessageId(aMessageId,
2382 null,
2383 null,
2384 DELIVERY_STATUS_ERROR,
2385 null,
2386 function() {
2387 aRequest.notifyGetMessageFailed(errorCode);
2388 });
2389 return;
2390 }
2391 // In OMA-TS-MMS_ENC-V1_3, Table 5 in page 25. This header field
2392 // (x-mms-transaction-id) SHALL be present when the MMS Proxy relay
2393 // seeks an acknowledgement for the MM delivered though M-Retrieve.conf
2394 // PDU during deferred retrieval. This transaction ID is used by the MMS
2395 // Client and MMS Proxy-Relay to provide linkage between the originated
2396 // M-Retrieve.conf and the response M-Acknowledge.ind PDUs.
2397 let transactionId = retrievedMsg.headers["x-mms-transaction-id"];
2399 // The absence of the field does not indicate any default
2400 // value. So we go checking the same field in retrieved
2401 // message instead.
2402 if (wish == null && retrievedMsg) {
2403 wish = retrievedMsg.headers["x-mms-delivery-report"];
2404 }
2405 let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
2406 wish);
2408 if (DEBUG) debug("retrievedMsg = " + JSON.stringify(retrievedMsg));
2409 aMessageRecord = this.mergeRetrievalConfirmation(mmsConnection,
2410 retrievedMsg,
2411 aMessageRecord);
2413 gMobileMessageDatabaseService.saveReceivedMessage(aMessageRecord,
2414 (function(rv, domMessage) {
2415 let success = Components.isSuccessCode(rv);
2416 if (!success) {
2417 // At this point we could send a message to content to
2418 // notify the user that storing an incoming MMS failed, most
2419 // likely due to a full disk.
2420 if (DEBUG) debug("Could not store MMS, error code " + rv);
2421 aRequest.notifyGetMessageFailed(
2422 gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv));
2423 return;
2424 }
2426 // Notifying observers a new MMS message is retrieved.
2427 this.broadcastReceivedMessageEvent(domMessage);
2429 // Return the request after retrieving the MMS message successfully.
2430 aRequest.notifyMessageGot(domMessage);
2432 // Cite 6.3.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
2433 // If an acknowledgement is requested, the MMS Client SHALL respond
2434 // with an M-Acknowledge.ind PDU to the MMS Proxy-Relay that supports
2435 // the specific MMS Client. The M-Acknowledge.ind PDU confirms
2436 // successful message retrieval to the MMS Proxy Relay.
2437 let transaction = new AcknowledgeTransaction(mmsConnection,
2438 transactionId,
2439 reportAllowed);
2440 transaction.run();
2441 }).bind(this));
2442 };
2444 // Update the delivery status to pending in DB.
2445 gMobileMessageDatabaseService
2446 .setMessageDeliveryByMessageId(aMessageId,
2447 null,
2448 null,
2449 DELIVERY_STATUS_PENDING,
2450 null,
2451 (function(rv) {
2452 let success = Components.isSuccessCode(rv);
2453 if (!success) {
2454 if (DEBUG) debug("Could not change the delivery status, error code " + rv);
2455 aRequest.notifyGetMessageFailed(
2456 gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv));
2457 return;
2458 }
2460 this.retrieveMessage(mmsConnection,
2461 url,
2462 responseNotify.bind(this),
2463 aDomMessage);
2464 }).bind(this));
2465 }).bind(this));
2466 },
2468 sendReadReport: function(messageID, toAddress, iccId) {
2469 if (DEBUG) {
2470 debug("messageID: " + messageID + " toAddress: " +
2471 JSON.stringify(toAddress));
2472 }
2474 // Get MmsConnection based on the saved MMS message record's ICC ID,
2475 // which could fail when the corresponding SIM card isn't installed.
2476 let mmsConnection;
2477 try {
2478 mmsConnection = gMmsConnections.getConnByIccId(iccId);
2479 } catch (e) {
2480 if (DEBUG) debug("Failed to get connection by IccId. e = " + e);
2481 return;
2482 }
2484 try {
2485 let transaction =
2486 new ReadRecTransaction(mmsConnection, messageID, toAddress);
2487 transaction.run();
2488 } catch (e) {
2489 if (DEBUG) debug("sendReadReport fail. e = " + e);
2490 }
2491 },
2493 // nsIWapPushApplication
2495 receiveWapPush: function(array, length, offset, options) {
2496 let data = {array: array, offset: offset};
2497 let msg = MMS.PduHelper.parse(data, null);
2498 if (!msg) {
2499 return false;
2500 }
2501 if (DEBUG) debug("receiveWapPush: msg = " + JSON.stringify(msg));
2503 switch (msg.type) {
2504 case MMS.MMS_PDU_TYPE_NOTIFICATION_IND:
2505 this.handleNotificationIndication(options.serviceId, msg);
2506 break;
2507 case MMS.MMS_PDU_TYPE_DELIVERY_IND:
2508 this.handleDeliveryIndication(msg);
2509 break;
2510 case MMS.MMS_PDU_TYPE_READ_ORIG_IND:
2511 this.handleReadOriginateIndication(msg);
2512 break;
2513 default:
2514 if (DEBUG) debug("Unsupported X-MMS-Message-Type: " + msg.type);
2515 break;
2516 }
2517 },
2519 // nsIObserver
2521 observe: function(aSubject, aTopic, aData) {
2522 switch (aTopic) {
2523 case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
2524 if (aData === kPrefDefaultServiceId) {
2525 this.mmsDefaultServiceId = getDefaultServiceId();
2526 }
2527 break;
2528 }
2529 }
2530 };
2532 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);