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.

     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 }

mercurial