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 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
16 /* Copyright © 2013, Deutsche Telekom, Inc. */
18 "use strict";
20 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
22 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
23 Cu.import("resource://gre/modules/Services.jsm");
25 let NFC = {};
26 Cu.import("resource://gre/modules/nfc_consts.js", NFC);
28 Cu.import("resource://gre/modules/systemlibs.js");
29 const NFC_ENABLED = libcutils.property_get("ro.moz.nfc.enabled", "false") === "true";
31 // set to true in nfc_consts.js to see debug messages
32 let DEBUG = NFC.DEBUG_NFC;
34 let debug;
35 if (DEBUG) {
36 debug = function (s) {
37 dump("-*- Nfc: " + s + "\n");
38 };
39 } else {
40 debug = function (s) {};
41 }
43 const NFC_CONTRACTID = "@mozilla.org/nfc;1";
44 const NFC_CID =
45 Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
47 const NFC_IPC_MSG_NAMES = [
48 "NFC:SetSessionToken"
49 ];
51 const NFC_IPC_READ_PERM_MSG_NAMES = [
52 "NFC:ReadNDEF",
53 "NFC:GetDetailsNDEF",
54 "NFC:Connect",
55 "NFC:Close",
56 ];
58 const NFC_IPC_WRITE_PERM_MSG_NAMES = [
59 "NFC:WriteNDEF",
60 "NFC:MakeReadOnlyNDEF",
61 "NFC:SendFile",
62 "NFC:RegisterPeerTarget",
63 "NFC:UnregisterPeerTarget"
64 ];
66 const NFC_IPC_MANAGER_PERM_MSG_NAMES = [
67 "NFC:CheckP2PRegistration",
68 "NFC:NotifyUserAcceptedP2P",
69 "NFC:NotifySendFileStatus",
70 "NFC:StartPoll",
71 "NFC:StopPoll",
72 "NFC:PowerOff"
73 ];
75 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
76 "@mozilla.org/parentprocessmessagemanager;1",
77 "nsIMessageBroadcaster");
78 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
79 "@mozilla.org/system-message-internal;1",
80 "nsISystemMessagesInternal");
81 XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
82 "@mozilla.org/telephony/system-worker-manager;1",
83 "nsISystemWorkerManager");
84 XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
85 "@mozilla.org/uuid-generator;1",
86 "nsIUUIDGenerator");
87 XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
88 return {
89 QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
90 Ci.nsIObserver]),
92 nfc: null,
94 // Manage message targets in terms of sessionToken. Only the authorized and
95 // registered contents can receive related messages.
96 targetsBySessionTokens: {},
97 sessionTokens: [],
99 // Manage registered Peer Targets
100 peerTargetsMap: {},
101 currentPeerAppId: null,
103 init: function init(nfc) {
104 this.nfc = nfc;
106 Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
107 this._registerMessageListeners();
108 },
110 _shutdown: function _shutdown() {
111 this.nfc = null;
113 Services.obs.removeObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN);
114 this._unregisterMessageListeners();
115 },
117 _registerMessageListeners: function _registerMessageListeners() {
118 ppmm.addMessageListener("child-process-shutdown", this);
120 for (let msgname of NFC_IPC_MSG_NAMES) {
121 ppmm.addMessageListener(msgname, this);
122 }
124 for (let msgname of NFC_IPC_READ_PERM_MSG_NAMES) {
125 ppmm.addMessageListener(msgname, this);
126 }
128 for (let msgname of NFC_IPC_WRITE_PERM_MSG_NAMES) {
129 ppmm.addMessageListener(msgname, this);
130 }
132 for (let msgname of NFC_IPC_MANAGER_PERM_MSG_NAMES) {
133 ppmm.addMessageListener(msgname, this);
134 }
135 },
137 _unregisterMessageListeners: function _unregisterMessageListeners() {
138 ppmm.removeMessageListener("child-process-shutdown", this);
140 for (let msgname of NFC_IPC_MSG_NAMES) {
141 ppmm.removeMessageListener(msgname, this);
142 }
144 for (let msgname of NFC_IPC_READ_PERM_MSG_NAMES) {
145 ppmm.removeMessageListener(msgname, this);
146 }
148 for (let msgname of NFC_IPC_WRITE_PERM_MSG_NAMES) {
149 ppmm.removeMessageListener(msgname, this);
150 }
152 for (let msgname of NFC_IPC_MANAGER_PERM_MSG_NAMES) {
153 ppmm.removeMessageListener(msgname, this);
154 }
156 ppmm = null;
157 },
159 _registerMessageTarget: function _registerMessageTarget(sessionToken, target) {
160 let targets = this.targetsBySessionTokens[sessionToken];
161 if (!targets) {
162 targets = this.targetsBySessionTokens[sessionToken] = [];
163 let list = this.sessionTokens;
164 if (list.indexOf(sessionToken) == -1) {
165 list.push(sessionToken);
166 }
167 }
169 if (targets.indexOf(target) != -1) {
170 debug("Already registered this target!");
171 return;
172 }
174 targets.push(target);
175 debug("Registered :" + sessionToken + " target: " + target);
176 },
178 _unregisterMessageTarget: function _unregisterMessageTarget(sessionToken, target) {
179 if (sessionToken == null) {
180 // Unregister the target for every sessionToken when no sessionToken is specified.
181 for (let session of this.sessionTokens) {
182 this._unregisterMessageTarget(session, target);
183 }
184 return;
185 }
187 // Unregister the target for a specified sessionToken.
188 let targets = this.targetsBySessionTokens[sessionToken];
189 if (!targets) {
190 return;
191 }
193 if (target == null) {
194 debug("Unregistered all targets for the " + sessionToken + " targets: " + targets);
195 targets = [];
196 let list = this.sessionTokens;
197 if (sessionToken !== null) {
198 let index = list.indexOf(sessionToken);
199 if (index > -1) {
200 list.splice(index, 1);
201 }
202 }
203 return;
204 }
206 let index = targets.indexOf(target);
207 if (index != -1) {
208 targets.splice(index, 1);
209 }
210 },
212 _sendTargetMessage: function _sendTargetMessage(sessionToken, message, options) {
213 let targets = this.targetsBySessionTokens[sessionToken];
214 if (!targets) {
215 return;
216 }
218 for (let target of targets) {
219 target.sendAsyncMessage(message, options);
220 }
221 },
223 registerPeerTarget: function registerPeerTarget(msg) {
224 let appInfo = msg.json;
225 // Sanity check on PeerEvent
226 if (!this.isValidPeerEvent(appInfo.event)) {
227 return;
228 }
229 let targets = this.peerTargetsMap;
230 let targetInfo = targets[appInfo.appId];
231 // If the application Id is already registered
232 if (targetInfo) {
233 // If the event is not registered
234 if (targetInfo.event !== appInfo.event) {
235 // Update the event field ONLY
236 targetInfo.event |= appInfo.event;
237 }
238 // Otherwise event is already registered, return!
239 return;
240 }
241 // Target not registered yet! Add to the target map
243 // Registered targetInfo target consists of 2 fields (values)
244 // target : Target to notify the right content for peer notifications
245 // event : NFC_PEER_EVENT_READY (0x01) Or NFC_PEER_EVENT_LOST (0x02)
246 let newTargetInfo = { target : msg.target,
247 event : appInfo.event };
248 targets[appInfo.appId] = newTargetInfo;
249 },
251 unregisterPeerTarget: function unregisterPeerTarget(msg) {
252 let appInfo = msg.json;
253 // Sanity check on PeerEvent
254 if (!this.isValidPeerEvent(appInfo.event)) {
255 return;
256 }
257 let targets = this.peerTargetsMap;
258 let targetInfo = targets[appInfo.appId];
259 if (targetInfo) {
260 // Application Id registered and the event exactly matches.
261 if (targetInfo.event === appInfo.event) {
262 // Remove the target from the list of registered targets
263 delete targets[appInfo.appId]
264 }
265 else {
266 // Otherwise, update the event field ONLY, by removing the event flag
267 targetInfo.event &= ~appInfo.event;
268 }
269 }
270 },
272 removePeerTarget: function removePeerTarget(target) {
273 let targets = this.peerTargetsMap;
274 Object.keys(targets).forEach((appId) => {
275 let targetInfo = targets[appId];
276 if (targetInfo && targetInfo.target === target) {
277 // Remove the target from the list of registered targets
278 delete targets[appId];
279 }
280 });
281 },
283 isRegisteredP2PTarget: function isRegisteredP2PTarget(appId, event) {
284 let targetInfo = this.peerTargetsMap[appId];
285 // Check if it is a registered target for the 'event'
286 return ((targetInfo != null) && (targetInfo.event & event !== 0));
287 },
289 notifyPeerEvent: function notifyPeerEvent(appId, event) {
290 let targetInfo = this.peerTargetsMap[appId];
291 // Check if the application id is a registeredP2PTarget
292 if (this.isRegisteredP2PTarget(appId, event)) {
293 targetInfo.target.sendAsyncMessage("NFC:PeerEvent", {
294 event: event,
295 sessionToken: this.nfc.sessionTokenMap[this.nfc._currentSessionId]
296 });
297 return;
298 }
299 debug("Application ID : " + appId + " is not a registered target" +
300 "for the event " + event + " notification");
301 },
303 isValidPeerEvent: function isValidPeerEvent(event) {
304 // Valid values : 0x01, 0x02 Or 0x03
305 return ((event === NFC.NFC_PEER_EVENT_READY) ||
306 (event === NFC.NFC_PEER_EVENT_LOST) ||
307 (event === (NFC.NFC_PEER_EVENT_READY | NFC.NFC_PEER_EVENT_LOST)));
308 },
310 /**
311 * nsIMessageListener interface methods.
312 */
314 receiveMessage: function receiveMessage(msg) {
315 debug("Received '" + msg.name + "' message from content process");
316 if (msg.name == "child-process-shutdown") {
317 // By the time we receive child-process-shutdown, the child process has
318 // already forgotten its permissions so we need to unregister the target
319 // for every permission.
320 this._unregisterMessageTarget(null, msg.target);
321 this.removePeerTarget(msg.target);
322 return null;
323 }
325 if (NFC_IPC_MSG_NAMES.indexOf(msg.name) != -1) {
326 // Do nothing.
327 } else if (NFC_IPC_READ_PERM_MSG_NAMES.indexOf(msg.name) != -1) {
328 if (!msg.target.assertPermission("nfc-read")) {
329 debug("Nfc message " + msg.name +
330 " from a content process with no 'nfc-read' privileges.");
331 return null;
332 }
333 } else if (NFC_IPC_WRITE_PERM_MSG_NAMES.indexOf(msg.name) != -1) {
334 if (!msg.target.assertPermission("nfc-write")) {
335 debug("Nfc Peer message " + msg.name +
336 " from a content process with no 'nfc-write' privileges.");
337 return null;
338 }
339 } else if (NFC_IPC_MANAGER_PERM_MSG_NAMES.indexOf(msg.name) != -1) {
340 if (!msg.target.assertPermission("nfc-manager")) {
341 debug("NFC message " + message.name +
342 " from a content process with no 'nfc-manager' privileges.");
343 return null;
344 }
345 } else {
346 debug("Ignoring unknown message type: " + msg.name);
347 return null;
348 }
350 switch (msg.name) {
351 case "NFC:SetSessionToken":
352 this._registerMessageTarget(this.nfc.sessionTokenMap[this.nfc._currentSessionId], msg.target);
353 debug("Registering target for this SessionToken : " +
354 this.nfc.sessionTokenMap[this.nfc._currentSessionId]);
355 return null;
356 case "NFC:RegisterPeerTarget":
357 this.registerPeerTarget(msg);
358 return null;
359 case "NFC:UnregisterPeerTarget":
360 this.unregisterPeerTarget(msg);
361 return null;
362 case "NFC:CheckP2PRegistration":
363 // Check if the application id is a valid registered target.
364 // (It should have registered for NFC_PEER_EVENT_READY).
365 let isRegistered = this.isRegisteredP2PTarget(msg.json.appId,
366 NFC.NFC_PEER_EVENT_READY);
367 // Remember the current AppId if registered.
368 this.currentPeerAppId = (isRegistered) ? msg.json.appId : null;
369 let status = (isRegistered) ? NFC.GECKO_NFC_ERROR_SUCCESS :
370 NFC.GECKO_NFC_ERROR_GENERIC_FAILURE;
371 // Notify the content process immediately of the status
372 msg.target.sendAsyncMessage(msg.name + "Response", {
373 status: status,
374 requestId: msg.json.requestId
375 });
376 return null;
377 case "NFC:NotifyUserAcceptedP2P":
378 // Notify the 'NFC_PEER_EVENT_READY' since user has acknowledged
379 this.notifyPeerEvent(msg.json.appId, NFC.NFC_PEER_EVENT_READY);
380 return null;
381 case "NFC:NotifySendFileStatus":
382 // Upon receiving the status of sendFile operation, send the response
383 // to appropriate content process.
384 this.sendNfcResponseMessage(msg.name + "Response", msg.json);
385 return null;
386 default:
387 return this.nfc.receiveMessage(msg);
388 }
389 },
391 /**
392 * nsIObserver interface methods.
393 */
395 observe: function observe(subject, topic, data) {
396 switch (topic) {
397 case NFC.TOPIC_XPCOM_SHUTDOWN:
398 this._shutdown();
399 break;
400 }
401 },
403 sendNfcResponseMessage: function sendNfcResponseMessage(message, data) {
404 this._sendTargetMessage(this.nfc.sessionTokenMap[this.nfc._currentSessionId], message, data);
405 },
406 };
407 });
409 function Nfc() {
410 debug("Starting Worker");
411 this.worker = new ChromeWorker("resource://gre/modules/nfc_worker.js");
412 this.worker.onerror = this.onerror.bind(this);
413 this.worker.onmessage = this.onmessage.bind(this);
415 gMessageManager.init(this);
417 // Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
418 this.sessionTokenMap = {};
419 this.targetsByRequestId = {};
421 gSystemWorkerManager.registerNfcWorker(this.worker);
422 }
424 Nfc.prototype = {
426 classID: NFC_CID,
427 classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
428 classDescription: "Nfc",
429 interfaces: [Ci.nsIWorkerHolder]}),
431 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder]),
433 _currentSessionId: null,
435 powerLevel: NFC.NFC_POWER_LEVEL_UNKNOWN,
437 onerror: function onerror(event) {
438 debug("Got an error: " + event.filename + ":" +
439 event.lineno + ": " + event.message + "\n");
440 event.preventDefault();
441 },
443 /**
444 * Send arbitrary message to worker.
445 *
446 * @param nfcMessageType
447 * A text message type.
448 * @param message [optional]
449 * An optional message object to send.
450 */
451 sendToWorker: function sendToWorker(nfcMessageType, message) {
452 message = message || {};
453 message.type = nfcMessageType;
454 this.worker.postMessage(message);
455 },
457 /**
458 * Send Error response to content.
459 *
460 * @param message
461 * An nsIMessageListener's message parameter.
462 */
463 sendNfcErrorResponse: function sendNfcErrorResponse(message) {
464 if (!message.target) {
465 return;
466 }
468 let nfcMsgType = message.name + "Response";
469 message.target.sendAsyncMessage(nfcMsgType, {
470 sessionId: message.json.sessionToken,
471 requestId: message.json.requestId,
472 status: NFC.GECKO_NFC_ERROR_GENERIC_FAILURE
473 });
474 },
476 /**
477 * Process the incoming message from the NFC worker
478 */
479 onmessage: function onmessage(event) {
480 let message = event.data;
481 debug("Received message from NFC worker: " + JSON.stringify(message));
483 switch (message.type) {
484 case "techDiscovered":
485 this._currentSessionId = message.sessionId;
487 // Check if the session token already exists. If exists, continue to use the same one.
488 // If not, generate a new token.
489 if (!this.sessionTokenMap[this._currentSessionId]) {
490 this.sessionTokenMap[this._currentSessionId] = UUIDGenerator.generateUUID().toString();
491 }
492 // Update the upper layers with a session token (alias)
493 message.sessionToken = this.sessionTokenMap[this._currentSessionId];
494 // Do not expose the actual session to the content
495 delete message.sessionId;
497 gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
498 break;
499 case "techLost":
500 gMessageManager._unregisterMessageTarget(this.sessionTokenMap[this._currentSessionId], null);
502 // Update the upper layers with a session token (alias)
503 message.sessionToken = this.sessionTokenMap[this._currentSessionId];
504 // Do not expose the actual session to the content
505 delete message.sessionId;
507 gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
508 // Notify 'PeerLost' to appropriate registered target, if any
509 gMessageManager.notifyPeerEvent(this.currentPeerAppId, NFC.NFC_PEER_EVENT_LOST);
510 delete this.sessionTokenMap[this._currentSessionId];
511 this._currentSessionId = null;
512 this.currentPeerAppId = null;
513 break;
514 case "ConfigResponse":
515 let target = this.targetsByRequestId[message.requestId];
516 if (!target) {
517 debug("No target for requestId: " + message.requestId);
518 return;
519 }
520 delete this.targetsByRequestId[message.requestId];
522 if (message.status == NFC.GECKO_NFC_ERROR_SUCCESS) {
523 this.powerLevel = message.powerLevel;
524 }
526 target.sendAsyncMessage("NFC:ConfigResponse", message);
527 break;
528 case "ConnectResponse": // Fall through.
529 case "CloseResponse":
530 case "GetDetailsNDEFResponse":
531 case "ReadNDEFResponse":
532 case "MakeReadOnlyNDEFResponse":
533 case "WriteNDEFResponse":
534 message.sessionToken = this.sessionTokenMap[this._currentSessionId];
535 // Do not expose the actual session to the content
536 delete message.sessionId;
537 gMessageManager.sendNfcResponseMessage("NFC:" + message.type, message);
538 break;
539 default:
540 throw new Error("Don't know about this message type: " + message.type);
541 }
542 },
544 // nsINfcWorker
545 worker: null,
547 sessionTokenMap: null,
549 targetsByRequestId: null,
551 /**
552 * Process a message from the content process.
553 */
554 receiveMessage: function receiveMessage(message) {
555 debug("Received '" + JSON.stringify(message) + "' message from content process");
557 // Handle messages without sessionToken.
558 if (message.name == "NFC:StartPoll") {
559 this.targetsByRequestId[message.json.requestId] = message.target;
560 this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_ENABLED,
561 requestId: message.json.requestId});
562 return null;
563 } else if (message.name == "NFC:StopPoll") {
564 this.targetsByRequestId[message.json.requestId] = message.target;
565 this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_LOW,
566 requestId: message.json.requestId});
567 return null;
568 } else if (message.name == "NFC:PowerOff") {
569 this.targetsByRequestId[message.json.requestId] = message.target;
570 this.setConfig({powerLevel: NFC.NFC_POWER_LEVEL_DISABLED,
571 requestId: message.json.requestId});
572 return null;
573 }
575 if (this.powerLevel != NFC.NFC_POWER_LEVEL_ENABLED) {
576 debug("NFC is not enabled. current powerLevel:" + this.powerLevel);
577 this.sendNfcErrorResponse(message);
578 return null;
579 }
581 // Sanity check on sessionId
582 if (message.json.sessionToken !== this.sessionTokenMap[this._currentSessionId]) {
583 debug("Invalid Session Token: " + message.json.sessionToken +
584 " Expected Session Token: " + this.sessionTokenMap[this._currentSessionId]);
585 this.sendNfcErrorResponse(message);
586 return null;
587 }
589 // Update the current sessionId before sending to the worker
590 message.json.sessionId = this._currentSessionId;
592 switch (message.name) {
593 case "NFC:GetDetailsNDEF":
594 this.sendToWorker("getDetailsNDEF", message.json);
595 break;
596 case "NFC:ReadNDEF":
597 this.sendToWorker("readNDEF", message.json);
598 break;
599 case "NFC:WriteNDEF":
600 this.sendToWorker("writeNDEF", message.json);
601 break;
602 case "NFC:MakeReadOnlyNDEF":
603 this.sendToWorker("makeReadOnlyNDEF", message.json);
604 break;
605 case "NFC:Connect":
606 this.sendToWorker("connect", message.json);
607 break;
608 case "NFC:Close":
609 this.sendToWorker("close", message.json);
610 break;
611 case "NFC:SendFile":
612 // Chrome process is the arbitrator / mediator between
613 // system app (content process) that issued nfc 'sendFile' operation
614 // and system app that handles the system message :
615 // 'nfc-manager-send-file'. System app subsequently handover's
616 // the data to alternate carrier's (BT / WiFi) 'sendFile' interface.
618 // Notify system app to initiate BT send file operation
619 gSystemMessenger.broadcastMessage("nfc-manager-send-file",
620 message.json);
621 break;
622 default:
623 debug("UnSupported : Message Name " + message.name);
624 return null;
625 }
627 return null;
628 },
630 setConfig: function setConfig(prop) {
631 this.sendToWorker("config", prop);
632 }
633 };
635 if (NFC_ENABLED) {
636 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
637 }