Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 Cu.import("resource://gre/modules/Services.jsm");
12 this.EXPORTED_SYMBOLS = [];
14 const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
15 "Payment:Success",
16 "Payment:Failed"];
18 const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
19 const PREF_PAYMENT_BRANCH = "dom.payment.";
20 const PREF_DEBUG = "dom.payment.debug";
22 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
23 "@mozilla.org/parentprocessmessagemanager;1",
24 "nsIMessageListenerManager");
26 XPCOMUtils.defineLazyServiceGetter(this, "prefService",
27 "@mozilla.org/preferences-service;1",
28 "nsIPrefService");
30 let PaymentManager = {
31 init: function init() {
32 // Payment providers data are stored as a preference.
33 this.registeredProviders = null;
35 this.messageManagers = {};
37 // The dom.payment.skipHTTPSCheck pref is supposed to be used only during
38 // development process. This preference should not be active for a
39 // production build.
40 let paymentPrefs = prefService.getBranch(PREF_PAYMENT_BRANCH);
41 this.checkHttps = true;
42 try {
43 if (paymentPrefs.getPrefType("skipHTTPSCheck")) {
44 this.checkHttps = !paymentPrefs.getBoolPref("skipHTTPSCheck");
45 }
46 } catch(e) {}
48 for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
49 ppmm.addMessageListener(msgname, this);
50 }
52 Services.obs.addObserver(this, "xpcom-shutdown", false);
54 try {
55 this._debug =
56 Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
57 && Services.prefs.getBoolPref(PREF_DEBUG);
58 } catch(e) {
59 this._debug = false;
60 }
61 },
63 /**
64 * Process a message from the content process.
65 */
66 receiveMessage: function receiveMessage(aMessage) {
67 let name = aMessage.name;
68 let msg = aMessage.json;
69 if (this._debug) {
70 this.LOG("Received '" + name + "' message from content process");
71 }
73 switch (name) {
74 case "Payment:Pay": {
75 // First of all, we register the payment providers.
76 if (!this.registeredProviders) {
77 this.registeredProviders = {};
78 this.registerPaymentProviders();
79 }
81 // We save the message target message manager so we can later dispatch
82 // back messages without broadcasting to all child processes.
83 let requestId = msg.requestId;
84 this.messageManagers[requestId] = aMessage.target;
86 // We check the jwt type and look for a match within the
87 // registered payment providers to get the correct payment request
88 // information.
89 let paymentRequests = [];
90 let jwtTypes = [];
91 for (let i in msg.jwts) {
92 let pr = this.getPaymentRequestInfo(requestId, msg.jwts[i]);
93 if (!pr) {
94 continue;
95 }
96 // We consider jwt type repetition an error.
97 if (jwtTypes[pr.type]) {
98 this.paymentFailed(requestId,
99 "PAY_REQUEST_ERROR_DUPLICATED_JWT_TYPE");
100 return;
101 }
102 jwtTypes[pr.type] = true;
103 paymentRequests.push(pr);
104 }
106 if (!paymentRequests.length) {
107 this.paymentFailed(requestId,
108 "PAY_REQUEST_ERROR_NO_VALID_REQUEST_FOUND");
109 return;
110 }
112 // After getting the list of valid payment requests, we ask the user
113 // for confirmation before sending any request to any payment provider.
114 // If there is more than one choice, we also let the user select the one
115 // that he prefers.
116 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
117 .createInstance(Ci.nsIPaymentUIGlue);
118 if (!glue) {
119 if (this._debug) {
120 this.LOG("Could not create nsIPaymentUIGlue instance");
121 }
122 this.paymentFailed(requestId,
123 "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
124 return;
125 }
127 let confirmPaymentSuccessCb = function successCb(aRequestId,
128 aResult) {
129 // Get the appropriate payment provider data based on user's choice.
130 let selectedProvider = this.registeredProviders[aResult];
131 if (!selectedProvider || !selectedProvider.uri) {
132 if (this._debug) {
133 this.LOG("Could not retrieve a valid provider based on user's " +
134 "selection");
135 }
136 this.paymentFailed(aRequestId,
137 "INTERNAL_ERROR_NO_VALID_SELECTED_PROVIDER");
138 return;
139 }
141 let jwt;
142 for (let i in paymentRequests) {
143 if (paymentRequests[i].type == aResult) {
144 jwt = paymentRequests[i].jwt;
145 break;
146 }
147 }
148 if (!jwt) {
149 if (this._debug) {
150 this.LOG("The selected request has no JWT information " +
151 "associated");
152 }
153 this.paymentFailed(aRequestId,
154 "INTERNAL_ERROR_NO_JWT_ASSOCIATED_TO_REQUEST");
155 return;
156 }
158 this.showPaymentFlow(aRequestId, selectedProvider, jwt);
159 };
161 let confirmPaymentErrorCb = this.paymentFailed;
163 glue.confirmPaymentRequest(requestId,
164 paymentRequests,
165 confirmPaymentSuccessCb.bind(this),
166 confirmPaymentErrorCb.bind(this));
167 break;
168 }
169 case "Payment:Success":
170 case "Payment:Failed": {
171 let mm = this.messageManagers[msg.requestId];
172 mm.sendAsyncMessage(name, {
173 requestId: msg.requestId,
174 result: msg.result,
175 errorMsg: msg.errorMsg
176 });
177 break;
178 }
179 }
180 },
182 /**
183 * Helper function to register payment providers stored as preferences.
184 */
185 registerPaymentProviders: function registerPaymentProviders() {
186 let paymentProviders = prefService
187 .getBranch(PREF_PAYMENTPROVIDERS_BRANCH)
188 .getChildList("");
190 // First get the numbers of the providers by getting all ###.uri prefs.
191 let nums = [];
192 for (let i in paymentProviders) {
193 let match = /^(\d+)\.uri$/.exec(paymentProviders[i]);
194 if (!match) {
195 continue;
196 } else {
197 nums.push(match[1]);
198 }
199 }
201 // Now register the payment providers.
202 for (let i in nums) {
203 let branch = prefService
204 .getBranch(PREF_PAYMENTPROVIDERS_BRANCH + nums[i] + ".");
205 let vals = branch.getChildList("");
206 if (vals.length == 0) {
207 return;
208 }
209 try {
210 let type = branch.getCharPref("type");
211 if (type in this.registeredProviders) {
212 continue;
213 }
214 this.registeredProviders[type] = {
215 name: branch.getCharPref("name"),
216 uri: branch.getCharPref("uri"),
217 description: branch.getCharPref("description"),
218 requestMethod: branch.getCharPref("requestMethod")
219 };
220 if (this._debug) {
221 this.LOG("Registered Payment Providers: " +
222 JSON.stringify(this.registeredProviders[type]));
223 }
224 } catch (ex) {
225 if (this._debug) {
226 this.LOG("An error ocurred registering a payment provider. " + ex);
227 }
228 }
229 }
230 },
232 /**
233 * Helper for sending a Payment:Failed message to the parent process.
234 */
235 paymentFailed: function paymentFailed(aRequestId, aErrorMsg) {
236 let mm = this.messageManagers[aRequestId];
237 mm.sendAsyncMessage("Payment:Failed", {
238 requestId: aRequestId,
239 errorMsg: aErrorMsg
240 });
241 },
243 /**
244 * Helper function to get the payment request info according to the jwt
245 * type. Payment provider's data is stored as a preference.
246 */
247 getPaymentRequestInfo: function getPaymentRequestInfo(aRequestId, aJwt) {
248 if (!aJwt) {
249 this.paymentFailed(aRequestId, "INTERNAL_ERROR_CALL_WITH_MISSING_JWT");
250 return true;
251 }
253 // First thing, we check that the jwt type is an allowed type and has a
254 // payment provider flow information associated.
256 // A jwt string consists in three parts separated by period ('.'): header,
257 // payload and signature.
258 let segments = aJwt.split('.');
259 if (segments.length !== 3) {
260 if (this._debug) {
261 this.LOG("Error getting payment provider's uri. " +
262 "Not enough or too many segments");
263 }
264 this.paymentFailed(aRequestId,
265 "PAY_REQUEST_ERROR_WRONG_SEGMENTS_COUNT");
266 return true;
267 }
269 let payloadObject;
270 try {
271 // We only care about the payload segment, which contains the jwt type
272 // that should match with any of the stored payment provider's data and
273 // the payment request information to be shown to the user.
274 // Before decoding the JWT string we need to normalize it to be compliant
275 // with RFC 4648.
276 segments[1] = segments[1].replace("-", "+", "g").replace("_", "/", "g");
277 let payload = atob(segments[1]);
278 if (this._debug) {
279 this.LOG("Payload " + payload);
280 }
281 if (!payload.length) {
282 this.paymentFailed(aRequestId, "PAY_REQUEST_ERROR_EMPTY_PAYLOAD");
283 return true;
284 }
285 payloadObject = JSON.parse(payload);
286 if (!payloadObject) {
287 this.paymentFailed(aRequestId,
288 "PAY_REQUEST_ERROR_ERROR_PARSING_JWT_PAYLOAD");
289 return true;
290 }
291 } catch (e) {
292 this.paymentFailed(aRequestId,
293 "PAY_REQUEST_ERROR_ERROR_DECODING_JWT");
294 return true;
295 }
297 if (!payloadObject.typ) {
298 this.paymentFailed(aRequestId,
299 "PAY_REQUEST_ERROR_NO_TYP_PARAMETER");
300 return true;
301 }
303 if (!payloadObject.request) {
304 this.paymentFailed(aRequestId,
305 "PAY_REQUEST_ERROR_NO_REQUEST_PARAMETER");
306 return true;
307 }
309 // Once we got the jwt 'typ' value we look for a match within the payment
310 // providers stored preferences. If the jwt 'typ' is not recognized as one
311 // of the allowed values for registered payment providers, we skip the jwt
312 // validation but we don't fire any error. This way developers might have
313 // a default set of well formed JWTs that might be used in different B2G
314 // devices with a different set of allowed payment providers.
315 let provider = this.registeredProviders[payloadObject.typ];
316 if (!provider) {
317 if (this._debug) {
318 this.LOG("Not registered payment provider for jwt type: " +
319 payloadObject.typ);
320 }
321 return false;
322 }
324 if (!provider.uri || !provider.name) {
325 this.paymentFailed(aRequestId,
326 "INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
327 return true;
328 }
330 // We only allow https for payment providers uris.
331 if (this.checkHttps && !/^https/.exec(provider.uri.toLowerCase())) {
332 // We should never get this far.
333 if (this._debug) {
334 this.LOG("Payment provider uris must be https: " + provider.uri);
335 }
336 this.paymentFailed(aRequestId,
337 "INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
338 return true;
339 }
341 let pldRequest = payloadObject.request;
342 return { jwt: aJwt, type: payloadObject.typ, providerName: provider.name };
343 },
345 showPaymentFlow: function showPaymentFlow(aRequestId,
346 aPaymentProvider,
347 aJwt) {
348 let paymentFlowInfo = Cc["@mozilla.org/payment/flow-info;1"]
349 .createInstance(Ci.nsIPaymentFlowInfo);
350 paymentFlowInfo.uri = aPaymentProvider.uri;
351 paymentFlowInfo.requestMethod = aPaymentProvider.requestMethod;
352 paymentFlowInfo.jwt = aJwt;
354 let glue = Cc["@mozilla.org/payment/ui-glue;1"]
355 .createInstance(Ci.nsIPaymentUIGlue);
356 if (!glue) {
357 if (this._debug) {
358 this.LOG("Could not create nsIPaymentUIGlue instance");
359 }
360 this.paymentFailed(aRequestId,
361 "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
362 return false;
363 }
364 glue.showPaymentFlow(aRequestId,
365 paymentFlowInfo,
366 this.paymentFailed.bind(this));
367 },
369 // nsIObserver
371 observe: function observe(subject, topic, data) {
372 if (topic == "xpcom-shutdown") {
373 for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
374 ppmm.removeMessageListener(msgname, this);
375 }
376 this.registeredProviders = null;
377 this.messageManagers = null;
379 Services.obs.removeObserver(this, "xpcom-shutdown");
380 }
381 },
383 LOG: function LOG(s) {
384 if (!this._debug) {
385 return;
386 }
387 dump("-*- PaymentManager: " + s + "\n");
388 }
389 };
391 PaymentManager.init();