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
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 module.metadata = {
8 "stability": "stable"
9 };
11 const { ns } = require("./core/namespace");
12 const { emit } = require("./event/core");
13 const { merge } = require("./util/object");
14 const { stringify } = require("./querystring");
15 const { EventTarget } = require("./event/target");
16 const { Class } = require("./core/heritage");
17 const { XMLHttpRequest, forceAllowThirdPartyCookie } = require("./net/xhr");
18 const apiUtils = require("./deprecated/api-utils");
19 const { isValidURI } = require("./url.js");
21 const response = ns();
22 const request = ns();
24 // Instead of creating a new validator for each request, just make one and
25 // reuse it.
26 const { validateOptions, validateSingleOption } = new OptionsValidator({
27 url: {
28 // Also converts a URL instance to string, bug 857902
29 map: function (url) url.toString(),
30 ok: isValidURI
31 },
32 headers: {
33 map: function (v) v || {},
34 is: ["object"],
35 },
36 content: {
37 map: function (v) v || null,
38 is: ["string", "object", "null"],
39 },
40 contentType: {
41 map: function (v) v || "application/x-www-form-urlencoded",
42 is: ["string"],
43 },
44 overrideMimeType: {
45 map: function(v) v || null,
46 is: ["string", "null"],
47 }
48 });
50 const REUSE_ERROR = "This request object has been used already. You must " +
51 "create a new one to make a new request."
53 // Utility function to prep the request since it's the same between
54 // request types
55 function runRequest(mode, target) {
56 let source = request(target)
57 let { xhr, url, content, contentType, headers, overrideMimeType } = source;
59 let isGetOrHead = (mode == "GET" || mode == "HEAD");
61 // If this request has already been used, then we can't reuse it.
62 // Throw an error.
63 if (xhr)
64 throw new Error(REUSE_ERROR);
66 xhr = source.xhr = new XMLHttpRequest();
68 // Build the data to be set. For GET or HEAD requests, we want to append that
69 // to the URL before opening the request.
70 let data = stringify(content);
71 // If the URL already has ? in it, then we want to just use &
72 if (isGetOrHead && data)
73 url = url + (/\?/.test(url) ? "&" : "?") + data;
75 // open the request
76 xhr.open(mode, url);
79 forceAllowThirdPartyCookie(xhr);
81 // request header must be set after open, but before send
82 xhr.setRequestHeader("Content-Type", contentType);
84 // set other headers
85 Object.keys(headers).forEach(function(name) {
86 xhr.setRequestHeader(name, headers[name]);
87 });
89 // set overrideMimeType
90 if (overrideMimeType)
91 xhr.overrideMimeType(overrideMimeType);
93 // handle the readystate, create the response, and call the callback
94 xhr.onreadystatechange = function onreadystatechange() {
95 if (xhr.readyState === 4) {
96 let response = Response(xhr);
97 source.response = response;
98 emit(target, 'complete', response);
99 }
100 };
102 // actually send the request.
103 // We don't want to send data on GET or HEAD requests.
104 xhr.send(!isGetOrHead ? data : null);
105 }
107 const Request = Class({
108 extends: EventTarget,
109 initialize: function initialize(options) {
110 // `EventTarget.initialize` will set event listeners that are named
111 // like `onEvent` in this case `onComplete` listener will be set to
112 // `complete` event.
113 EventTarget.prototype.initialize.call(this, options);
115 // Copy normalized options.
116 merge(request(this), validateOptions(options));
117 },
118 get url() { return request(this).url; },
119 set url(value) { request(this).url = validateSingleOption('url', value); },
120 get headers() { return request(this).headers; },
121 set headers(value) {
122 return request(this).headers = validateSingleOption('headers', value);
123 },
124 get content() { return request(this).content; },
125 set content(value) {
126 request(this).content = validateSingleOption('content', value);
127 },
128 get contentType() { return request(this).contentType; },
129 set contentType(value) {
130 request(this).contentType = validateSingleOption('contentType', value);
131 },
132 get response() { return request(this).response; },
133 delete: function() {
134 runRequest('DELETE', this);
135 return this;
136 },
137 get: function() {
138 runRequest('GET', this);
139 return this;
140 },
141 post: function() {
142 runRequest('POST', this);
143 return this;
144 },
145 put: function() {
146 runRequest('PUT', this);
147 return this;
148 },
149 head: function() {
150 runRequest('HEAD', this);
151 return this;
152 }
153 });
154 exports.Request = Request;
156 const Response = Class({
157 initialize: function initialize(request) {
158 response(this).request = request;
159 },
160 get text() response(this).request.responseText,
161 get xml() {
162 throw new Error("Sorry, the 'xml' property is no longer available. " +
163 "see bug 611042 for more information.");
164 },
165 get status() response(this).request.status,
166 get statusText() response(this).request.statusText,
167 get json() {
168 try {
169 return JSON.parse(this.text);
170 } catch(error) {
171 return null;
172 }
173 },
174 get headers() {
175 let headers = {}, lastKey;
176 // Since getAllResponseHeaders() will return null if there are no headers,
177 // defend against it by defaulting to ""
178 let rawHeaders = response(this).request.getAllResponseHeaders() || "";
179 rawHeaders.split("\n").forEach(function (h) {
180 // According to the HTTP spec, the header string is terminated by an empty
181 // line, so we can just skip it.
182 if (!h.length) {
183 return;
184 }
186 let index = h.indexOf(":");
187 // The spec allows for leading spaces, so instead of assuming a single
188 // leading space, just trim the values.
189 let key = h.substring(0, index).trim(),
190 val = h.substring(index + 1).trim();
192 // For empty keys, that means that the header value spanned multiple lines.
193 // In that case we should append the value to the value of lastKey with a
194 // new line. We'll assume lastKey will be set because there should never
195 // be an empty key on the first pass.
196 if (key) {
197 headers[key] = val;
198 lastKey = key;
199 }
200 else {
201 headers[lastKey] += "\n" + val;
202 }
203 });
204 return headers;
205 }
206 });
208 // apiUtils.validateOptions doesn't give the ability to easily validate single
209 // options, so this is a wrapper that provides that ability.
210 function OptionsValidator(rules) {
211 return {
212 validateOptions: function (options) {
213 return apiUtils.validateOptions(options, rules);
214 },
215 validateSingleOption: function (field, value) {
216 // We need to create a single rule object from our listed rules. To avoid
217 // JavaScript String warnings, check for the field & default to an empty object.
218 let singleRule = {};
219 if (field in rules) {
220 singleRule[field] = rules[field];
221 }
222 let singleOption = {};
223 singleOption[field] = value;
224 // This should throw if it's invalid, which will bubble up & out.
225 return apiUtils.validateOptions(singleOption, singleRule)[field];
226 }
227 };
228 }