addon-sdk/source/lib/sdk/request.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

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.

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

mercurial