/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see .
Based on nsChromeExtensionHandler example code by Ed Anuff at
http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol
***** END LICENSE BLOCK *****
*/
const ZOTERO_SCHEME = "zotero";
const ZOTERO_PROTOCOL_CID = Components.ID("{9BC3D762-9038-486A-9D70-C997AF848A7C}");
const ZOTERO_PROTOCOL_CONTRACTID = "@mozilla.org/network/protocol;1?name=" + ZOTERO_SCHEME;
const ZOTERO_PROTOCOL_NAME = "Zotero Chrome Extension Protocol";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
// Dummy chrome URL used to obtain a valid chrome channel
// This one was chosen at random and should be able to be substituted
// for any other well known chrome URL in the browser installation
const DUMMY_CHROME_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
var Zotero = Components.classes["@zotero.org/Zotero;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
function ZoteroProtocolHandler() {
this.wrappedJSObject = this;
this._principal = null;
this._extensions = {};
/**
* zotero://data/library/collection/ABCD1234/items?sort=itemType&direction=desc
* zotero://data/groups/12345/collection/ABCD1234/items?sort=title&direction=asc
*/
var DataExtension = {
loadAsChrome: false,
newChannel: function (uri) {
return new AsyncChannel(uri, function* () {
this.contentType = 'text/plain';
path = uri.spec.match(/zotero:\/\/[^/]+(.*)/)[1];
try {
return Zotero.Utilities.Internal.getAsyncInputStream(
Zotero.API.Data.getGenerator(path)
);
}
catch (e) {
if (e instanceof Zotero.Router.InvalidPathException) {
return "URL could not be parsed";
}
}
});
}
};
/*
* Report generation extension for Zotero protocol
*/
var ReportExtension = {
loadAsChrome: false,
newChannel: function (uri) {
return new AsyncChannel(uri, function* () {
var userLibraryID = Zotero.Libraries.userLibraryID;
var path = uri.path;
if (!path) {
return 'Invalid URL';
}
// Strip leading '/'
path = path.substr(1);
// Proxy CSS files
if (path.endsWith('.css')) {
var chromeURL = 'chrome://zotero/skin/report/' + path;
Zotero.debug(chromeURL);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
let uri = ios.newURI(chromeURL, null, null);
var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
.getService(Components.interfaces.nsIChromeRegistry);
return chromeReg.convertChromeURL(uri);
}
var params = {
objectType: 'item',
format: 'html',
sort: 'title'
};
var router = new Zotero.Router(params);
// Items within a collection or search
router.add('library/:scopeObject/:scopeObjectKey/items', function () {
params.libraryID = userLibraryID;
});
router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items');
// All items
router.add('library/items/:objectKey', function () {
params.libraryID = userLibraryID;
});
router.add('groups/:groupID/items');
// Old-style URLs
router.add('collection/:id/html/report.html', function () {
params.scopeObject = 'collections';
var lkh = Zotero.Collections.parseLibraryKeyHash(params.id);
if (lkh) {
params.libraryID = lkh.libraryID || userLibraryID;
params.scopeObjectKey = lkh.key;
}
else {
params.scopeObjectID = params.id;
}
delete params.id;
});
router.add('search/:id/html/report.html', function () {
params.scopeObject = 'searches';
var lkh = Zotero.Searches.parseLibraryKeyHash(this.id);
if (lkh) {
params.libraryID = lkh.libraryID || userLibraryID;
params.scopeObjectKey = lkh.key;
}
else {
params.scopeObjectID = this.id;
}
delete params.id;
});
router.add('items/:ids/html/report.html', function () {
var ids = this.ids.split('-');
params.libraryID = ids[0].split('_')[0] || userLibraryID;
params.itemKey = ids.map(x => x.split('_')[1]);
delete params.ids;
});
var parsed = router.run(path);
if (!parsed) {
return "URL could not be parsed";
}
// TODO: support old URLs
// collection
// search
// items
// item
if (params.sort.indexOf('/') != -1) {
let parts = params.sort.split('/');
params.sort = parts[0];
params.direction = parts[1] == 'd' ? 'desc' : 'asc';
}
try {
Zotero.API.parseParams(params);
var results = yield Zotero.API.getResultsFromParams(params);
}
catch (e) {
Zotero.debug(e, 1);
return e.toString();
}
var mimeType, content = '';
var items = [];
var itemsHash = {}; // key = itemID, val = position in |items|
var searchItemIDs = new Set(); // All selected items
var searchParentIDs = new Set(); // Parents of selected child items
var searchChildIDs = new Set() // Selected chlid items
var includeAllChildItems = Zotero.Prefs.get('report.includeAllChildItems');
var combineChildItems = Zotero.Prefs.get('report.combineChildItems');
var unhandledParents = {};
for (var i=0; iError generating report';
}
);
}
});
}
};
/**
* Generate MIT SIMILE Timeline
*
* Query string key abbreviations: intervals = i
* dateType = t
* timelineDate = d
*
* interval abbreviations: day = d | month = m | year = y | decade = e | century = c | millennium = i
* dateType abbreviations: date = d | dateAdded = da | dateModified = dm
* timelineDate format: shortMonthName.day.year (year is positive for A.D. and negative for B.C.)
*
* Defaults: intervals = month, year, decade
* dateType = date
* timelineDate = today's date
*/
var TimelineExtension = {
loadAsChrome: true,
newChannel: function (uri) {
return new AsyncChannel(uri, function* () {
var userLibraryID = Zotero.Libraries.userLibraryID;
path = uri.spec.match(/zotero:\/\/[^/]+(.*)/)[1];
if (!path) {
this.contentType = 'text/html';
return 'Invalid URL';
}
var params = {};
var router = new Zotero.Router(params);
// HTML
router.add('library/:scopeObject/:scopeObjectKey', function () {
params.libraryID = userLibraryID;
params.controller = 'html';
});
router.add('groups/:groupID/:scopeObject/:scopeObjectKey', function () {
params.controller = 'html';
});
router.add('library', function () {
params.libraryID = userLibraryID;
params.controller = 'html';
});
router.add('groups/:groupID', function () {
params.controller = 'html';
});
// Data
router.add('data/library/:scopeObject/:scopeObjectKey', function () {
params.libraryID = userLibraryID;
params.controller = 'data';
});
router.add('data/groups/:groupID/:scopeObject/:scopeObjectKey', function () {
params.controller = 'data';
});
router.add('data/library', function () {
params.libraryID = userLibraryID;
params.controller = 'data';
});
router.add('data/groups/:groupID', function () {
params.controller = 'data';
});
// Old-style HTML URLs
router.add('collection/:id', function () {
params.controller = 'html';
params.scopeObject = 'collections';
var lkh = Zotero.Collections.parseLibraryKeyHash(params.id);
if (lkh) {
params.libraryID = lkh.libraryID || userLibraryID;
params.scopeObjectKey = lkh.key;
}
else {
params.scopeObjectID = params.id;
}
delete params.id;
});
router.add('search/:id', function () {
params.controller = 'html';
params.scopeObject = 'searches';
var lkh = Zotero.Searches.parseLibraryKeyHash(params.id);
if (lkh) {
params.libraryID = lkh.libraryID || userLibraryID;
params.scopeObjectKey = lkh.key;
}
else {
params.scopeObjectID = params.id;
}
delete params.id;
});
router.add('/', function () {
params.controller = 'html';
params.libraryID = userLibraryID;
});
var parsed = router.run(path);
if (!parsed) {
this.contentType = 'text/html';
return "URL could not be parsed";
}
if (params.groupID) {
params.libraryID = Zotero.Groups.getLibraryIDFromGroupID(params.groupID);
}
var intervals = params.i ? params.i : '';
var timelineDate = params.d ? params.d : '';
var dateType = params.t ? params.t : '';
// Get the collection or search object
var collection, search;
switch (params.scopeObject) {
case 'collections':
if (params.scopeObjectKey) {
collection = yield Zotero.Collections.getByLibraryAndKeyAsync(
params.libraryID, params.scopeObjectKey
);
}
else {
collection = yield Zotero.Collections.getAsync(params.scopeObjectID);
}
if (!collection) {
this.contentType = 'text/html';
return 'Invalid collection ID or key';
}
break;
case 'searches':
if (params.scopeObjectKey) {
var s = yield Zotero.Searches.getByLibraryAndKeyAsync(
params.libraryID, params.scopeObjectKey
);
}
else {
var s = yield Zotero.Searches.getAsync(params.scopeObjectID);
}
if (!s) {
return 'Invalid search ID or key';
}
// FIXME: Hack to exclude group libraries for now
var search = new Zotero.Search();
search.setScope(s);
var groups = Zotero.Groups.getAll();
for (let group of groups) {
search.addCondition('libraryID', 'isNot', group.libraryID);
}
break;
}
//
// Create XML file
//
if (params.controller == 'data') {
switch (params.scopeObject) {
case 'collections':
var results = collection.getChildItems();
break;
case 'searches':
var ids = yield search.search();
var results = yield Zotero.Items.getAsync(ids);
break;
default:
if (params.scopeObject) {
return "Invalid scope object '" + params.scopeObject + "'";
}
let s = new Zotero.Search();
s.addCondition('libraryID', 'is', params.libraryID);
s.addCondition('noChildren', 'true');
var ids = yield s.search();
var results = yield Zotero.Items.getAsync(ids);
}
var items = [];
// Only include parent items
for (let i=0; i x.id));
}
}),
newChannel: function (uri) {
this.doAction(uri);
}
};
/*
zotero://fullscreen
*/
var FullscreenExtension = {
loadAsChrome: false,
newChannel: function (uri) {
return new AsyncChannel(uri, function* () {
try {
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://zotero/content/standalone/standalone.xul', '',
'chrome,centerscreen,resizable', null);
}
catch (e) {
Zotero.debug(e, 1);
throw e;
}
});
}
};
/*
zotero://debug/
*/
var DebugExtension = {
loadAsChrome: false,
newChannel: function (uri) {
return new AsyncChannel(uri, function* () {
this.contentType = "text/plain";
try {
return Zotero.Debug.get();
}
catch (e) {
Zotero.debug(e, 1);
throw e;
}
});
}
};
var ConnectorChannel = function(uri, data) {
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(Components.interfaces.nsIScriptSecurityManager);
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
this.name = uri;
this.URI = ioService.newURI(uri, "UTF-8", null);
this.owner = (secMan.getCodebasePrincipal || secMan.getSimpleCodebasePrincipal)(this.URI);
this._isPending = true;
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
this._stream = converter.convertToInputStream(data);
this.contentLength = this._stream.available();
}
ConnectorChannel.prototype.contentCharset = "UTF-8";
ConnectorChannel.prototype.contentType = "text/html";
ConnectorChannel.prototype.notificationCallbacks = null;
ConnectorChannel.prototype.securityInfo = null;
ConnectorChannel.prototype.status = 0;
ConnectorChannel.prototype.loadGroup = null;
ConnectorChannel.prototype.loadFlags = 393216;
ConnectorChannel.prototype.__defineGetter__("originalURI", function() { return this.URI });
ConnectorChannel.prototype.__defineSetter__("originalURI", function() { });
ConnectorChannel.prototype.asyncOpen = function(streamListener, context) {
if(this.loadGroup) this.loadGroup.addRequest(this, null);
streamListener.onStartRequest(this, context);
streamListener.onDataAvailable(this, context, this._stream, 0, this.contentLength);
streamListener.onStopRequest(this, context, this.status);
this._isPending = false;
if(this.loadGroup) this.loadGroup.removeRequest(this, null, 0);
}
ConnectorChannel.prototype.isPending = function() {
return this._isPending;
}
ConnectorChannel.prototype.cancel = function(status) {
this.status = status;
this._isPending = false;
if(this._stream) this._stream.close();
}
ConnectorChannel.prototype.suspend = function() {}
ConnectorChannel.prototype.resume = function() {}
ConnectorChannel.prototype.open = function() {
return this._stream;
}
ConnectorChannel.prototype.QueryInterface = function(iid) {
if (!iid.equals(Components.interfaces.nsIChannel) && !iid.equals(Components.interfaces.nsIRequest) &&
!iid.equals(Components.interfaces.nsISupports)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
}
/**
* zotero://connector/
*
* URI spoofing for transferring page data across boundaries
*/
var ConnectorExtension = new function() {
this.loadAsChrome = false;
this.newChannel = function(uri) {
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(Components.interfaces.nsIScriptSecurityManager);
var Zotero = Components.classes["@zotero.org/Zotero;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
try {
var originalURI = uri.path;
originalURI = decodeURIComponent(originalURI.substr(originalURI.indexOf("/")+1));
if(!Zotero.Server.Connector.Data[originalURI]) {
return null;
} else {
return new ConnectorChannel(originalURI, Zotero.Server.Connector.Data[originalURI]);
}
} catch(e) {
Zotero.debug(e);
throw e;
}
}
};
/**
* Open a PDF at a given page (or try to)
*
* zotero://open-pdf/library/items/[itemKey]?page=[page]
* zotero://open-pdf/groups/[groupID]/items/[itemKey]?page=[page]
*
* Also supports ZotFile format:
* zotero://open-pdf/[libraryID]_[key]/[page]
*/
var OpenPDFExtension = {
noContent: true,
doAction: async function (uri) {
var userLibraryID = Zotero.Libraries.userLibraryID;
var uriPath = uri.path;
if (!uriPath) {
return 'Invalid URL';
}
// Strip leading '/'
uriPath = uriPath.substr(1);
var mimeType, content = '';
var params = {
objectType: 'item'
};
var router = new Zotero.Router(params);
// All items
router.add('library/items/:objectKey', function () {
params.libraryID = userLibraryID;
});
router.add('groups/:groupID/items/:objectKey');
// ZotFile URLs
router.add(':id/:page', function () {
var lkh = Zotero.Items.parseLibraryKeyHash(params.id);
if (!lkh) {
Zotero.warn(`Invalid URL ${url}`);
return;
}
params.libraryID = lkh.libraryID || userLibraryID;
params.objectKey = lkh.key;
delete params.id;
});
router.run(uriPath);
Zotero.API.parseParams(params);
var results = await Zotero.API.getResultsFromParams(params);
var page = params.page;
if (parseInt(page) != page) {
page = null;
}
if (!results.length) {
Zotero.warn(`No item found for ${uriPath}`);
return;
}
var item = results[0];
if (!item.isFileAttachment()) {
Zotero.warn(`Item for ${uriPath} is not a file attachment`);
return;
}
var path = await item.getFilePathAsync();
if (!path) {
Zotero.warn(`${path} not found`);
return;
}
if (!path.toLowerCase().endsWith('.pdf')
&& Zotero.MIME.sniffForMIMEType(await Zotero.File.getSample(path)) != 'application/pdf') {
Zotero.warn(`${path} is not a PDF`);
return;
}
// If no page number, just open normally
if (!page) {
let zp = Zotero.getActiveZoteroPane();
// TODO: Open pane if closed (macOS)
if (zp) {
zp.viewAttachment([item.id]);
}
return;
}
try {
var opened = Zotero.OpenPDF.openToPage(path, page);
}
catch (e) {
Zotero.logError(e);
}
// If something went wrong, just open PDF without page
if (!opened) {
let zp = Zotero.getActiveZoteroPane();
// TODO: Open pane if closed (macOS)
if (zp) {
zp.viewAttachment([item.id]);
}
return;
}
Zotero.Notifier.trigger('open', 'file', item.id);
},
newChannel: function (uri) {
this.doAction(uri);
}
};
this._extensions[ZOTERO_SCHEME + "://data"] = DataExtension;
this._extensions[ZOTERO_SCHEME + "://report"] = ReportExtension;
this._extensions[ZOTERO_SCHEME + "://timeline"] = TimelineExtension;
this._extensions[ZOTERO_SCHEME + "://attachment"] = AttachmentExtension;
this._extensions[ZOTERO_SCHEME + "://select"] = SelectExtension;
this._extensions[ZOTERO_SCHEME + "://fullscreen"] = FullscreenExtension;
this._extensions[ZOTERO_SCHEME + "://debug"] = DebugExtension;
this._extensions[ZOTERO_SCHEME + "://connector"] = ConnectorExtension;
this._extensions[ZOTERO_SCHEME + "://open-pdf"] = OpenPDFExtension;
}
/*
* Implements nsIProtocolHandler
*/
ZoteroProtocolHandler.prototype = {
scheme: ZOTERO_SCHEME,
defaultPort : -1,
protocolFlags :
Components.interfaces.nsIProtocolHandler.URI_NORELATIVE |
Components.interfaces.nsIProtocolHandler.URI_NOAUTH |
// DEBUG: This should be URI_IS_LOCAL_FILE, and MUST be if any
// extensions that modify data are added
// - https://www.zotero.org/trac/ticket/1156
//
Components.interfaces.nsIProtocolHandler.URI_IS_LOCAL_FILE,
//Components.interfaces.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
allowPort : function(port, scheme) {
return false;
},
getExtension: function (uri) {
let uriString = uri;
if (uri instanceof Components.interfaces.nsIURI) {
uriString = uri.spec;
}
uriString = uriString.toLowerCase();
for (let extSpec in this._extensions) {
if (uriString.startsWith(extSpec)) {
return this._extensions[extSpec];
}
}
return false;
},
newURI : function(spec, charset, baseURI) {
var newURL = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIStandardURL);
newURL.init(1, -1, spec, charset, baseURI);
return newURL.QueryInterface(Components.interfaces.nsIURI);
},
newChannel : function(uri) {
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var chromeService = Components.classes["@mozilla.org/network/protocol;1?name=chrome"]
.getService(Components.interfaces.nsIProtocolHandler);
var newChannel = null;
try {
let ext = this.getExtension(uri);
if (!ext) {
// Return cancelled channel for unknown paths
//
// These can be in the form zotero://example.com/... -- maybe for "//example.com" URLs?
var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null);
var extChannel = chromeService.newChannel(chromeURI);
var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest);
chromeRequest.cancel(0x804b0002); // BINDING_ABORTED
return extChannel;
}
if (!this._principal) {
if (ext.loadAsChrome) {
var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null);
var chromeChannel = chromeService.newChannel(chromeURI);
// Cache System Principal from chrome request
// so proxied pages load with chrome privileges
this._principal = chromeChannel.owner;
var chromeRequest = chromeChannel.QueryInterface(Components.interfaces.nsIRequest);
chromeRequest.cancel(0x804b0002); // BINDING_ABORTED
}
}
var extChannel = ext.newChannel(uri);
// Extension returned null, so cancel request
if (!extChannel) {
var chromeURI = chromeService.newURI(DUMMY_CHROME_URL, null, null);
var extChannel = chromeService.newChannel(chromeURI);
var chromeRequest = extChannel.QueryInterface(Components.interfaces.nsIRequest);
chromeRequest.cancel(0x804b0002); // BINDING_ABORTED
}
// Apply cached principal to extension channel
if (this._principal) {
extChannel.owner = this._principal;
}
if(!extChannel.originalURI) extChannel.originalURI = uri;
return extChannel;
}
catch (e) {
Components.utils.reportError(e);
Zotero.debug(e, 1);
throw Components.results.NS_ERROR_FAILURE;
}
return newChannel;
},
contractID: ZOTERO_PROTOCOL_CONTRACTID,
classDescription: ZOTERO_PROTOCOL_NAME,
classID: ZOTERO_PROTOCOL_CID,
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
Components.interfaces.nsIProtocolHandler])
};
/**
* nsIChannel implementation that takes a promise-yielding generator that returns a
* string, nsIAsyncInputStream, or file
*/
function AsyncChannel(uri, gen) {
this._generator = gen;
this._isPending = true;
// nsIRequest
this.name = uri;
this.loadFlags = 0;
this.loadGroup = null;
this.status = 0;
// nsIChannel
this.contentLength = -1;
this.contentType = "text/html";
this.contentCharset = "utf-8";
this.URI = uri;
this.originalURI = uri;
this.owner = null;
this.notificationCallbacks = null;
this.securityInfo = null;
}
AsyncChannel.prototype = {
asyncOpen: Zotero.Promise.coroutine(function* (streamListener, context) {
if (this.loadGroup) this.loadGroup.addRequest(this, null);
var channel = this;
var resolve;
var reject;
var promise = new Zotero.Promise(function () {
resolve = arguments[0];
reject = arguments[1];
});
var listenerWrapper = {
onStartRequest: function (request, context) {
//Zotero.debug("Starting request");
streamListener.onStartRequest(channel, context);
},
onDataAvailable: function (request, context, inputStream, offset, count) {
//Zotero.debug("onDataAvailable");
streamListener.onDataAvailable(channel, context, inputStream, offset, count);
},
onStopRequest: function (request, context, status) {
//Zotero.debug("Stopping request");
streamListener.onStopRequest(channel, context, status);
channel._isPending = false;
if (status == 0) {
resolve();
}
else {
reject(new Error("AsyncChannel request failed with status " + status));
}
}
};
//Zotero.debug("AsyncChannel's asyncOpen called");
var t = new Date;
// Proxy requests to other zotero:// URIs
let uri2 = this.URI.clone();
if (uri2.path.startsWith('/proxy/')) {
let re = new RegExp(uri2.scheme + '://' + uri2.host + '/proxy/([^/]+)(.*)');
let matches = uri2.spec.match(re);
uri2.spec = uri2.scheme + '://' + matches[1] + '/' + (matches[2] ? matches[2] : '');
var data = Zotero.File.getContentsFromURL(uri2.spec);
}
try {
if (!data) {
data = yield Zotero.spawn(channel._generator, channel)
}
if (typeof data == 'string') {
//Zotero.debug("AsyncChannel: Got string from generator");
listenerWrapper.onStartRequest(this, context);
let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let inputStream = converter.convertToInputStream(data);
listenerWrapper.onDataAvailable(this, context, inputStream, 0, inputStream.available());
listenerWrapper.onStopRequest(this, context, this.status);
}
// If an async input stream is given, pass the data asynchronously to the stream listener
else if (data instanceof Ci.nsIAsyncInputStream) {
//Zotero.debug("AsyncChannel: Got input stream from generator");
var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(Ci.nsIInputStreamPump);
pump.init(data, -1, -1, 0, 0, true);
pump.asyncRead(listenerWrapper, context);
}
else if (data instanceof Ci.nsIFile || data instanceof Ci.nsIURI) {
if (data instanceof Ci.nsIFile) {
//Zotero.debug("AsyncChannel: Got file from generator");
data = ioService.newFileURI(data);
}
else {
//Zotero.debug("AsyncChannel: Got URI from generator");
}
let uri = data;
uri.QueryInterface(Ci.nsIURL);
this.contentType = Zotero.MIME.getMIMETypeFromExtension(uri.fileExtension);
if (!this.contentType) {
let sample = yield Zotero.File.getSample(data);
this.contentType = Zotero.MIME.getMIMETypeFromData(sample);
}
Components.utils.import("resource://gre/modules/NetUtil.jsm");
NetUtil.asyncFetch(data, function (inputStream, status) {
if (!Components.isSuccessCode(status)) {
reject();
return;
}
listenerWrapper.onStartRequest(channel, context);
try {
listenerWrapper.onDataAvailable(channel, context, inputStream, 0, inputStream.available());
}
catch (e) {
reject(e);
}
listenerWrapper.onStopRequest(channel, context, status);
});
}
else if (data === undefined) {
this.cancel(0x804b0002); // BINDING_ABORTED
}
else {
throw new Error("Invalid return type (" + typeof data + ") from generator passed to AsyncChannel");
}
if (this._isPending) {
//Zotero.debug("AsyncChannel request succeeded in " + (new Date - t) + " ms");
channel._isPending = false;
}
return promise;
} catch (e) {
Zotero.debug(e, 1);
if (channel._isPending) {
streamListener.onStopRequest(channel, context, Components.results.NS_ERROR_FAILURE);
channel._isPending = false;
}
throw e;
} finally {
if (channel.loadGroup) channel.loadGroup.removeRequest(channel, null, 0);
}
}),
// nsIRequest
isPending: function () {
return this._isPending;
},
cancel: function (status) {
Zotero.debug("Cancelling");
this.status = status;
this._isPending = false;
},
resume: function () {
Zotero.debug("Resuming");
},
suspend: function () {
Zotero.debug("Suspending");
},
// nsIWritablePropertyBag
setProperty: function (prop, val) {
this[prop] = val;
},
deleteProperty: function (prop) {
delete this[prop];
},
QueryInterface: function (iid) {
if (iid.equals(Components.interfaces.nsISupports)
|| iid.equals(Components.interfaces.nsIRequest)
|| iid.equals(Components.interfaces.nsIChannel)
// pdf.js wants this
|| iid.equals(Components.interfaces.nsIWritablePropertyBag)) {
return this;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroProtocolHandler]);