Add followRedirects: false option to Zotero.HTTP.request()

Currently only .status and .getResponseHeader() (for getting 'Location')
are available in the returned object, but we could make the body
available if necessary.
This commit is contained in:
Dan Stillman 2018-09-11 01:23:15 -04:00
parent b8db83af08
commit b782120840
3 changed files with 83 additions and 16 deletions

View file

@ -201,6 +201,8 @@ Zotero.HTTP = new function() {
// Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only)
var channel = xmlhttp.channel,
isFile = channel instanceof Components.interfaces.nsIFileChannel;
var redirectStatus;
var redirectLocation;
if(channel instanceof Components.interfaces.nsIHttpChannelInternal) {
channel.forceAllowThirdPartyCookie = true;
@ -218,8 +220,22 @@ Zotero.HTTP = new function() {
if (options.dontCache) {
channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
}
// Don't follow redirects
if (options.followRedirects === false) {
channel.notificationCallbacks = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor, Ci.nsIChannelEventSync]),
getInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink]),
asyncOnChannelRedirect: function (oldChannel, newChannel, flags, callback) {
redirectStatus = (flags & Ci.nsIChannelEventSink.REDIRECT_PERMANENT) ? 301 : 302;
redirectLocation = newChannel.URI.spec;
oldChannel.cancel(Cr.NS_BINDING_ABORTED);
callback.onRedirectVerifyCallback(Cr.NS_BINDING_ABORTED);
}
};
}
}
// Set responseType
if (options.responseType) {
xmlhttp.responseType = options.responseType;
@ -276,17 +292,24 @@ Zotero.HTTP = new function() {
deferred.reject(new Zotero.HTTP.TimeoutException(options.timeout));
};
xmlhttp.onloadend = function() {
var status = xmlhttp.status;
xmlhttp.onloadend = async function() {
var status = xmlhttp.status || redirectStatus;
// If an invalid HTTP response (e.g., NS_ERROR_INVALID_CONTENT_ENCODING) includes a
// 4xx or 5xx HTTP response code, swap it in, since it might be enough info to do
// what we need (e.g., verify a 404 from a WebDAV server)
try {
if (!status && xmlhttp.channel.responseStatus >= 400) {
Zotero.warn(`Overriding status for invalid response for ${dispURL} `
+ `(${xmlhttp.channel.status})`);
status = xmlhttp.channel.responseStatus;
if (!status) {
let responseStatus = xmlhttp.channel.responseStatus;
// If we cancelled a redirect, get the 3xx status from the channel
if (responseStatus >= 300 && responseStatus < 400) {
status = responseStatus;
}
// If an invalid HTTP response (e.g., NS_ERROR_INVALID_CONTENT_ENCODING) includes a
// 4xx or 5xx HTTP response code, swap it in, since it might be enough info to do
// what we need (e.g., verify a 404 from a WebDAV server)
else if (responseStatus >= 400) {
Zotero.warn(`Overriding status for invalid response for ${dispURL} `
+ `(${xmlhttp.channel.status})`);
status = responseStatus;
}
}
}
catch (e) {}
@ -301,6 +324,21 @@ Zotero.HTTP = new function() {
else if(isFile) {
var success = status == 200 || status == 0;
}
else if (redirectStatus) {
var success = true;
let channel = xmlhttp.channel;
xmlhttp = {
status,
getResponseHeader: function (header) {
if (header.toLowerCase() == 'location') {
return redirectLocation;
}
Zotero.debug("Warning: Attempt to get response header other than Location "
+ "for redirect", 2);
return null;
}
};
}
else {
var success = status >= 200 && status < 300;
}

View file

@ -29,6 +29,8 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
/** XPCOM files to be loaded for all modes **/
const xpcomFilesAll = [

View file

@ -1,6 +1,9 @@
describe("Zotero.HTTP", function () {
var httpd;
var port = 16213;
var baseURL = `http://127.0.0.1:${port}/`
var testURL = baseURL + 'test.html';
var redirectLocation = baseURL + 'test2.html';
before(function* () {
Components.utils.import("resource://zotero-unit/httpd.js");
@ -16,6 +19,16 @@ describe("Zotero.HTTP", function () {
}
}
);
httpd.registerPathHandler(
'/redirect',
{
handle: function (request, response) {
response.setHeader('Location', redirectLocation);
response.setStatusLine(null, 301, "Moved Permanently");
response.write(`<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\n<html><head>\n<title>301 Moved Permanently</title>\n</head><body>\n<h1>Moved Permanently</h1>\n<p>The document has moved <a href="${redirectLocation}">here</a>.</p>\n</body></html>`);
}
}
);
});
after(function* () {
@ -24,14 +37,29 @@ describe("Zotero.HTTP", function () {
yield defer.promise;
});
describe("#request()", function () {
it("should succeed with 3xx status if followRedirects is false", async function () {
var req = await Zotero.HTTP.request(
'GET',
baseURL + 'redirect',
{
followRedirects: false
}
);
assert.equal(req.status, 301);
assert.equal(req.getResponseHeader('Location'), redirectLocation);
});
});
describe("#processDocuments()", function () {
it("should provide a document object", function* () {
var called = false;
var url = `http://127.0.0.1:${port}/test.html`;
yield Zotero.HTTP.processDocuments(
url,
testURL,
function (doc) {
assert.equal(doc.location.href, url);
assert.equal(doc.location.href, testURL);
assert.equal(doc.querySelector('p').textContent, 'Test');
var p = doc.evaluate('//p', doc, null, XPathResult.ANY_TYPE, null).iterateNext();
assert.equal(p.textContent, 'Test');
@ -56,12 +84,11 @@ describe("Zotero.HTTP", function () {
it("should provide a document object", function* () {
var called = false;
var url = `http://127.0.0.1:${port}/test.html`;
yield new Zotero.Promise((resolve) => {
Zotero.HTTP.loadDocuments(
url,
testURL,
function (doc) {
assert.equal(doc.location.href, url);
assert.equal(doc.location.href, testURL);
assert.equal(doc.querySelector('p').textContent, 'Test');
var p = doc.evaluate('//p', doc, null, XPathResult.ANY_TYPE, null).iterateNext();
assert.equal(p.textContent, 'Test');