Zotero Everywhere megacommit

- Implement connector for Firefox (should switch in/out of connector mode automatically when Standalone is launched or closed, although this has only been tested extensively on OS X)
- Share core translation code between Zotero and connectors

Still to be done:

- Run translators in non-Fx connectors (this works in theory, but it's not currently enabled for any translators)
- Show translation results in non-Fx connectors
- Ability to translate to server when Zotero Standalone is not running
This commit is contained in:
Simon Kornblith 2011-06-14 00:36:21 +00:00
parent fd5d9496fd
commit 8268d1b01c
33 changed files with 3582 additions and 1645 deletions

View file

@ -63,6 +63,6 @@ contract @mozilla.org/autocomplete/search;1?name=zotero {06a2ed11-d0a4-4ff0-a56
component {9BC3D762-9038-486A-9D70-C997AF848A7C} components/zotero-protocol-handler.js
contract @mozilla.org/network/protocol;1?name=zotero {9BC3D762-9038-486A-9D70-C997AF848A7C}
component {531828f8-a16c-46be-b9aa-14845c3b010f} components/zotero-integration-service.js
contract @mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration {531828f8-a16c-46be-b9aa-14845c3b010f}
category command-line-handler m-zotero-integration @mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration
component {531828f8-a16c-46be-b9aa-14845c3b010f} components/zotero-command-line-handler.js
contract @mozilla.org/commandlinehandler/general-startup;1?type=zotero {531828f8-a16c-46be-b9aa-14845c3b010f}
category command-line-handler m-zotero @mozilla.org/commandlinehandler/general-startup;1?type=zotero

View file

@ -112,6 +112,17 @@ var Zotero_Browser = new function() {
function(e) { Zotero_Browser.chromeLoad(e) }, false);
window.addEventListener("unload",
function(e) { Zotero_Browser.chromeUnload(e) }, false);
ZoteroPane_Local.addReloadListener(reload);
reload();
}
/**
* Called when Zotero is reloaded
*/
function reload() {
// Handles the display of a div showing progress in scraping
Zotero_Browser.progress = new Zotero.ProgressWindow();
}
/**
@ -144,7 +155,7 @@ var Zotero_Browser = new function() {
// get libraryID and collectionID
var libraryID, collectionID;
if(ZoteroPane) {
if(ZoteroPane && !Zotero.isConnector) {
libraryID = ZoteroPane.getSelectedLibraryID();
collectionID = ZoteroPane.getSelectedCollection(true);
} else {
@ -374,7 +385,7 @@ var Zotero_Browser = new function() {
// get data object
var tab = _getTabObject(browser);
if(isHTML) {
if(isHTML && !Zotero.isConnector) {
var annotationID = Zotero.Annotate.getAnnotationIDFromURL(browser.currentURI.spec);
if(annotationID) {
if(Zotero.Annotate.isAnnotated(annotationID)) {
@ -509,8 +520,8 @@ var Zotero_Browser = new function() {
* Callback to be executed when an item has been finished
*/
function itemDone(obj, item, collection) {
var title = item.getField("title", false, true);
var icon = item.getImageSrc();
var title = item.title;
var icon = Zotero.ItemTypes.getImageSrc(item.itemType);
Zotero_Browser.progress.show();
Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scraping"));
Zotero_Browser.progress.addLines([title], [icon]);
@ -742,7 +753,7 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
this.page.hasBeenTranslated = true;
}
this.page.translate.clearHandlers("itemDone");
this.page.translate.setHandler("itemDone", function(obj, item) { Zotero_Browser.itemDone(obj, item, collection) });
this.page.translate.setHandler("itemDone", function(obj, dbItem, item) { Zotero_Browser.itemDone(obj, item, collection) });
this.page.translate.translate(libraryID);
}
@ -755,12 +766,10 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID) {
Zotero_Browser.Tab.prototype.getCaptureIcon = function() {
if(this.page.translators && this.page.translators.length) {
var itemType = this.page.translators[0].itemType;
if(itemType == "multiple") {
// Use folder icon for multiple types, for now
return "chrome://zotero/skin/treesource-collection.png";
} else {
return Zotero.ItemTypes.getImageSrc(itemType);
}
Zotero.debug("want capture icon for "+itemType);
return (itemType === "multiple"
? "chrome://zotero/skin/treesource-collection.png"
: Zotero.ItemTypes.getImageSrc(itemType));
}
return false;
@ -784,7 +793,7 @@ Zotero_Browser.Tab.prototype.getCaptureTooltip = function() {
/*
* called when a user is supposed to select items
*/
Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList) {
Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList, callback) {
// this is kinda ugly, mozillazine made me do it! honest!
var io = { dataIn:itemList, dataOut:null }
var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
@ -794,7 +803,7 @@ Zotero_Browser.Tab.prototype._selectItems = function(obj, itemList) {
Zotero_Browser.progress.close();
}
return io.dataOut;
callback(io.dataOut);
}
/*
@ -812,7 +821,4 @@ Zotero_Browser.Tab.prototype._translatorsAvailable = function(translate, transla
Zotero_Browser.updateStatus();
}
// Handles the display of a div showing progress in scraping
Zotero_Browser.progress = new Zotero.ProgressWindow();
Zotero_Browser.init();

View file

@ -29,11 +29,16 @@
var ZoteroOverlay = new function()
{
const DEFAULT_ZPANE_HEIGHT = 300;
var toolbarCollapseState, isFx36, showInPref;
var toolbarCollapseState, isFx36, showInPref;
var zoteroPane, zoteroSplitter;
var _stateBeforeReload = false;
this.isTab = false;
this.onLoad = function() {
zoteroPane = document.getElementById('zotero-pane-stack');
zoteroSplitter = document.getElementById('zotero-splitter');
ZoteroPane_Overlay = ZoteroPane;
ZoteroPane.init();
@ -141,6 +146,19 @@ var ZoteroOverlay = new function()
if(Zotero.isFx4) {
XULBrowserWindow.inContentWhitelist.push("chrome://zotero/content/tab.xul");
}
// Close pane if connector is enabled
ZoteroPane_Local.addReloadListener(function() {
if(Zotero.isConnector) {
// save current state
_stateBeforeReload = !zoteroPane.hidden && !zoteroPane.collapsed;
// ensure pane is closed
if(!zoteroPane.collapsed) ZoteroOverlay.toggleDisplay(false);
} else {
// reopen pane if it was open before
ZoteroOverlay.toggleDisplay(_stateBeforeReload);
}
});
}
this.onUnload = function() {
@ -158,10 +176,16 @@ var ZoteroOverlay = new function()
*/
this.toggleDisplay = function(makeVisible)
{
if(this.isTab && (makeVisible || makeVisible === undefined)) {
// If in separate tab mode, just open the tab
this.loadZoteroTab();
return;
if(makeVisible || makeVisible === undefined) {
if(Zotero.isConnector) {
// If in connector mode, bring Zotero Standalone to foreground
Zotero.activateStandalone();
return;
} else if(this.isTab) {
// If in separate tab mode, just open the tab
this.loadZoteroTab();
return;
}
}
if(!Zotero || !Zotero.initialized) {
@ -169,12 +193,7 @@ var ZoteroOverlay = new function()
return;
}
var zoteroPane = document.getElementById('zotero-pane-stack');
var zoteroSplitter = document.getElementById('zotero-splitter');
var isHidden = zoteroPane.getAttribute('hidden') == 'true';
var isCollapsed = zoteroPane.getAttribute('collapsed') == 'true';
if(makeVisible === undefined) makeVisible = isHidden || isCollapsed;
if(makeVisible === undefined) makeVisible = zoteroPane.hidden || zoteroPane.collapsed;
zoteroSplitter.setAttribute('hidden', !makeVisible);
zoteroPane.setAttribute('hidden', false);

View file

@ -402,7 +402,7 @@ Zotero_RecognizePDF.Recognizer.prototype._queryGoogle = function() {
Zotero.Browser.deleteHiddenBrowser(me._hiddenBrowser);
me._callback(item);
});
translate.setHandler("select", function(translate, items) { return me._selectItems(translate, items) });
translate.setHandler("select", function(translate, items) { me._selectItems(translate, items, callback) });
translate.setHandler("done", function(translate, success) { if(!success) me._queryGoogle(); });
this._hiddenBrowser.addEventListener("pageshow", function() { me._scrape(translate) }, true);
@ -449,10 +449,12 @@ Zotero_RecognizePDF.Recognizer.prototype._scrape = function(/**Zotero.Translate*
* @private
* @type Object
*/
Zotero_RecognizePDF.Recognizer.prototype._selectItems = function(/**Zotero.Translate*/ translate, /**Object*/ items) {
Zotero_RecognizePDF.Recognizer.prototype._selectItems = function(/**Zotero.Translate*/ translate,
/**Object*/ items, /**Function**/ callback) {
for(var i in items) {
var obj = {};
obj[i] = items;
return obj;
callback(obj);
return;
}
}

View file

@ -1,780 +0,0 @@
/*
***** 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 <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Connector = new function() {
var _onlineObserverRegistered;
var responseCodes = {
200:"OK",
201:"Created",
300:"Multiple Choices",
400:"Bad Request",
404:"Not Found",
500:"Internal Server Error",
501:"Method Not Implemented"
};
/**
* initializes a very rudimentary web server
*/
this.init = function() {
if (Zotero.HTTP.browserIsOffline()) {
Zotero.debug('Browser is offline -- not initializing connector HTTP server');
_registerOnlineObserver();
return;
}
// start listening on socket
var serv = Components.classes["@mozilla.org/network/server-socket;1"]
.createInstance(Components.interfaces.nsIServerSocket);
try {
// bind to a random port on loopback only
serv.init(Zotero.Prefs.get('connector.port'), true, -1);
serv.asyncListen(Zotero.Connector.SocketListener);
Zotero.debug("Connector HTTP server listening on 127.0.0.1:"+serv.port);
} catch(e) {
Zotero.debug("Not initializing connector HTTP server");
}
_registerOnlineObserver()
}
/**
* generates the response to an HTTP request
*/
this.generateResponse = function (status, contentType, body) {
var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n";
response += "Access-Control-Allow-Origin: org.zotero.zoteroconnectorforsafari-69x6c999f9\r\n";
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD\r\n";
if(body) {
if(contentType) {
response += "Content-Type: "+contentType+"\r\n";
}
response += "\r\n"+body;
} else {
response += "Content-Length: 0\r\n\r\n"
}
return response;
}
/**
* Decodes application/x-www-form-urlencoded data
*
* @param {String} postData application/x-www-form-urlencoded data, as sent in a g request
* @return {Object} data in object form
*/
this.decodeURLEncodedData = function(postData) {
var splitData = postData.split("&");
var variables = {};
for each(var variable in splitData) {
var splitIndex = variable.indexOf("=");
variables[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1));
}
return variables;
}
function _registerOnlineObserver() {
if (_onlineObserverRegistered) {
return;
}
// Observer to enable the integration when we go online
var observer = {
observe: function(subject, topic, data) {
if (data == 'online') {
Zotero.Connector.init();
}
}
};
var observerService =
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(observer, "network:offline-status-changed", false);
_onlineObserverRegistered = true;
}
}
Zotero.Connector.SocketListener = new function() {
this.onSocketAccepted = onSocketAccepted;
this.onStopListening = onStopListening;
/*
* called when a socket is opened
*/
function onSocketAccepted(socket, transport) {
// get an input stream
var iStream = transport.openInputStream(0, 0, 0);
var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
var dataListener = new Zotero.Connector.DataListener(iStream, oStream);
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
.createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(iStream, -1, -1, 0, 0, false);
pump.asyncRead(dataListener, null);
}
function onStopListening(serverSocket, status) {
Zotero.debug("Connector HTTP server going offline");
}
}
/*
* handles the actual acquisition of data
*/
Zotero.Connector.DataListener = function(iStream, oStream) {
this.header = "";
this.headerFinished = false;
this.body = "";
this.bodyLength = 0;
this.iStream = iStream;
this.oStream = oStream;
this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
this.sStream.init(iStream);
this.foundReturn = false;
}
/*
* called when a request begins (although the request should have begun before
* the DataListener was generated)
*/
Zotero.Connector.DataListener.prototype.onStartRequest = function(request, context) {}
/*
* called when a request stops
*/
Zotero.Connector.DataListener.prototype.onStopRequest = function(request, context, status) {
this.iStream.close();
this.oStream.close();
}
/*
* called when new data is available
*/
Zotero.Connector.DataListener.prototype.onDataAvailable = function(request, context,
inputStream, offset, count) {
var readData = this.sStream.read(count);
if(this.headerFinished) { // reading body
this.body += readData;
// check to see if data is done
this._bodyData();
} else { // reading header
// see if there's a magic double return
var lineBreakIndex = readData.indexOf("\r\n\r\n");
if(lineBreakIndex != -1) {
if(lineBreakIndex != 0) {
this.header += readData.substr(0, lineBreakIndex+4);
this.body = readData.substr(lineBreakIndex+4);
}
this._headerFinished();
return;
}
var lineBreakIndex = readData.indexOf("\n\n");
if(lineBreakIndex != -1) {
if(lineBreakIndex != 0) {
this.header += readData.substr(0, lineBreakIndex+2);
this.body = readData.substr(lineBreakIndex+2);
}
this._headerFinished();
return;
}
if(this.header && this.header[this.header.length-1] == "\n" &&
(readData[0] == "\n" || readData[0] == "\r")) {
if(readData.length > 1 && readData[1] == "\n") {
this.header += readData.substr(0, 2);
this.body = readData.substr(2);
} else {
this.header += readData[0];
this.body = readData.substr(1);
}
this._headerFinished();
return;
}
this.header += readData;
}
}
/*
* processes an HTTP header and decides what to do
*/
Zotero.Connector.DataListener.prototype._headerFinished = function() {
this.headerFinished = true;
Zotero.debug(this.header);
const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/;
// get first line of request (all we care about for now)
var method = methodRe.exec(this.header);
if(!method) {
this._requestFinished(Zotero.Connector.generateResponse(400));
return;
}
if(!Zotero.Connector.Endpoints[method[2]]) {
this._requestFinished(Zotero.Connector.generateResponse(404));
return;
}
this.endpoint = Zotero.Connector.Endpoints[method[2]];
if(method[1] == "HEAD" || method[1] == "OPTIONS") {
this._requestFinished(Zotero.Connector.generateResponse(200));
} else if(method[1] == "GET") {
this._requestFinished(this._processEndpoint("GET", method[3]));
} else if(method[1] == "POST") {
const contentLengthRe = /[\r\n]Content-Length: *([0-9]+)/i;
// parse content length
var m = contentLengthRe.exec(this.header);
if(!m) {
this._requestFinished(Zotero.Connector.generateResponse(400));
return;
}
this.bodyLength = parseInt(m[1]);
this._bodyData();
} else {
this._requestFinished(Zotero.Connector.generateResponse(501));
return;
}
}
/*
* checks to see if Content-Length bytes of body have been read and, if so, processes the body
*/
Zotero.Connector.DataListener.prototype._bodyData = function() {
if(this.body.length >= this.bodyLength) {
// convert to UTF-8
var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
.createInstance(Components.interfaces.nsIStringInputStream);
dataStream.setData(this.body, this.bodyLength);
var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
utf8Stream.init(dataStream, "UTF-8", 4096, "?");
this.body = "";
var string = {};
while(utf8Stream.readString(this.bodyLength, string)) {
this.body += string.value;
}
// handle envelope
this._processEndpoint("POST", this.body);
}
}
/**
* Generates a response based on calling the function associated with the endpoint
*/
Zotero.Connector.DataListener.prototype._processEndpoint = function(method, postData) {
try {
var endpoint = new this.endpoint;
var me = this;
var sendResponseCallback = function(code, contentType, arg) {
me._requestFinished(Zotero.Connector.generateResponse(code, contentType, arg));
}
endpoint.init(method, postData ? postData : undefined, sendResponseCallback);
} catch(e) {
Zotero.debug(e);
this._requestFinished(Zotero.Connector.generateResponse(500));
throw e;
}
}
/*
* returns HTTP data from a request
*/
Zotero.Connector.DataListener.prototype._requestFinished = function(response) {
// close input stream
this.iStream.close();
// open UTF-8 converter for output stream
var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Components.interfaces.nsIConverterOutputStream);
// write
try {
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
// write response
Zotero.debug(response);
intlStream.writeString(response);
} finally {
intlStream.close();
}
}
/**
* Manage cookies in a sandboxed fashion
*
* @param {browser} browser Hidden browser object
* @param {String} uri URI of page to manage cookies for (cookies for domains that are not
* subdomains of this URI are ignored)
* @param {String} cookieData Cookies with which to initiate the sandbox
*/
Zotero.Connector.CookieManager = function(browser, uri, cookieData) {
this._webNav = browser.webNavigation;
this._browser = browser;
this._watchedBrowsers = [browser];
this._observerService = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
this._uri = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newURI(uri, null, null);
var splitCookies = cookieData.split(/; ?/);
this._cookies = {};
for each(var cookie in splitCookies) {
var key = cookie.substr(0, cookie.indexOf("="));
var value = cookie.substr(cookie.indexOf("=")+1);
this._cookies[key] = value;
}
[this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
}
Zotero.Connector.CookieManager.prototype = {
"_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
"_watchedXHRs":[],
/**
* nsIObserver implementation for adding, clearing, and slurping cookies
*/
"observe": function(channel, topic) {
if(topic == "quit-application") {
Zotero.debug("WARNING: A Zotero.Connector.CookieManager for "+this._uri.spec+" was still open on shutdown");
} else {
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
var isTracked = null;
try {
var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
for each(var browser in this._watchedBrowsers) {
isTracked = topDoc == browser.contentDocument;
if(isTracked) break;
}
} catch(e) {}
if(isTracked === null) {
try {
isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
} catch(e) {}
}
if(isTracked === null) {
try {
isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
} catch(e) {}
}
// isTracked is now either true, false, or null
// true => we should manage cookies for this request
// false => we should not manage cookies for this request
// null => this request is of a type we couldn't match to this request. one such type
// is a link prefetch (nsPrefetchNode) but there might be others as well. for
// now, we are paranoid and reject these.
if(isTracked === false) {
Zotero.debug("Zotero.Connector.CookieManager: not touching channel for "+channel.URI.spec);
return;
} else if(isTracked) {
Zotero.debug("Zotero.Connector.CookieManager: managing cookies for "+channel.URI.spec);
} else {
Zotero.debug("Zotero.Connector.CookieManager: being paranoid about channel for "+channel.URI.spec);
}
if(topic == "http-on-modify-request") {
// clear cookies to be sent to other domains
if(isTracked === null || channel.URI.host != this._uri.host) {
channel.setRequestHeader("Cookie", "", false);
channel.setRequestHeader("Cookie2", "", false);
Zotero.debug("Zotero.Connector.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
return;
}
// add cookies to be sent to this domain
var cookies = [key+"="+this._cookies[key]
for(key in this._cookies)].join("; ");
channel.setRequestHeader("Cookie", cookies, false);
Zotero.debug("Zotero.Connector.CookieManager: added cookies for request to "+channel.URI.spec);
} else if(topic == "http-on-examine-response") {
// clear cookies being received
try {
var cookieHeader = channel.getResponseHeader("Set-Cookie");
} catch(e) {
return;
}
channel.setResponseHeader("Set-Cookie", "", false);
channel.setResponseHeader("Set-Cookie2", "", false);
// don't process further if these cookies are for another set of domains
if(isTracked === null || channel.URI.host != this._uri.host) {
Zotero.debug("Zotero.Connector.CookieManager: rejected cookies from "+channel.URI.spec);
return;
}
// put new cookies into our sandbox
if(cookieHeader) {
var cookies = cookieHeader.split(/; ?/);
var newCookies = {};
for each(var cookie in cookies) {
var key = cookie.substr(0, cookie.indexOf("="));
var value = cookie.substr(cookie.indexOf("=")+1);
var lcCookie = key.toLowerCase();
if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
// ignore cookie parameters; we are only holding cookies for a few minutes
// with a single domain, and the path attribute doesn't allow any additional
// security anyway
continue;
} else if(lcCookie == "secure") {
// don't accept secure cookies
newCookies = {};
break;
} else {
newCookies[key] = value;
}
}
[this._cookies[key] = newCookies[key] for(key in newCookies)];
}
Zotero.debug("Zotero.Connector.CookieManager: slurped cookies from "+channel.URI.spec);
}
}
},
/**
* Attach CookieManager to a specific XMLHttpRequest
* @param {XMLHttpRequest} xhr
*/
"attachToBrowser": function(browser) {
this._watchedBrowsers.push(browser);
},
/**
* Attach CookieManager to a specific XMLHttpRequest
* @param {XMLHttpRequest} xhr
*/
"attachToXHR": function(xhr) {
this._watchedXHRs.push(xhr);
},
/**
* Destroys this CookieManager (intended to be executed when the browser is destroyed)
*/
"destroy": function() {
[this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
}
}
Zotero.Connector.Data = {};
Zotero.Connector.Translate = function() {};
Zotero.Connector.Translate._waitingForSelection = {};
/**
* Lists all available translators, including code for translators that should be run on every page
*/
Zotero.Connector.Translate.List = function() {};
Zotero.Connector.Translate.List.prototype = {
/**
* Gets available translator list
* @param {String} method "GET" or "POST"
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(method, data, sendResponseCallback) {
if(method != "POST") {
sendResponseCallback(400);
return;
}
var translators = Zotero.Translators.getAllForType("web");
var jsons = [];
for each(var translator in translators) {
let json = {};
for each(var key in ["translatorID", "label", "creator", "target", "priority", "detectXPath"]) {
json[key] = translator[key];
}
json["localExecution"] = translator.browserSupport.indexOf(data["browser"]) !== -1;
// Do not pass targetless translators that do not support this browser (since that
// would mean passing each page back to Zotero)
if(json["target"] || json["detectXPath"] || json["localExecution"]) {
jsons.push(json);
}
}
sendResponseCallback(200, "application/json", JSON.stringify(jsons));
}
}
/**
* Detects whether there is an available translator to handle a given page
*/
Zotero.Connector.Translate.Detect = function() {};
Zotero.Connector.Translate.Detect.prototype = {
/**
* Loads HTML into a hidden browser and initiates translator detection
* @param {String} method "GET" or "POST"
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(method, data, sendResponseCallback) {
if(method != "POST") {
sendResponseCallback(400);
return;
}
this.sendResponse = sendResponseCallback;
this._parsedPostData = JSON.parse(data);
this._translate = new Zotero.Translate("web");
this._translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
Zotero.Connector.Data[this._parsedPostData["uri"]] = "<html>"+this._parsedPostData["html"]+"</html>";
this._browser = Zotero.Browser.createHiddenBrowser();
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var uri = ioService.newURI(this._parsedPostData["uri"], "UTF-8", null);
var pageShowCalled = false;
var me = this;
this._translate.setCookieManager(new Zotero.Connector.CookieManager(this._browser,
this._parsedPostData["uri"], this._parsedPostData["cookie"]));
this._browser.addEventListener("DOMContentLoaded", function() {
try {
if(me._browser.contentDocument.location.href == "about:blank") return;
if(pageShowCalled) return;
pageShowCalled = true;
delete Zotero.Connector.Data[me._parsedPostData["uri"]];
// get translators
me._translate.setDocument(me._browser.contentDocument);
me._translate.getTranslators();
} catch(e) {
Zotero.debug(e);
throw e;
}
}, false);
me._browser.loadURI("zotero://connector/"+encodeURIComponent(this._parsedPostData["uri"]));
},
/**
* Callback to be executed when list of translators becomes available. Sends response with
* item types, translator IDs, labels, and icons for available translators.
* @param {Zotero.Translate} translate
* @param {Zotero.Translator[]} translators
*/
"_translatorsAvailable":function(obj, translators) {
var jsons = [];
for each(var translator in translators) {
if(translator.itemType == "multiple") {
var icon = "treesource-collection.png"
} else {
var icon = Zotero.ItemTypes.getImageSrc(translator.itemType);
icon = icon.substr(icon.lastIndexOf("/")+1);
}
var json = {"itemType":translator.itemType, "translatorID":translator.translatorID,
"label":translator.label, "icon":icon}
jsons.push(json);
}
this.sendResponse(200, "application/json", JSON.stringify(jsons));
this._translate.cookieManager.destroy();
Zotero.Browser.deleteHiddenBrowser(this._browser);
}
}
/**
* Performs translation of a given page
*/
Zotero.Connector.Translate.Save = function() {};
Zotero.Connector.Translate.Save.prototype = {
/**
* Init method inherited from Zotero.Connector.Translate.Detect
* @borrows Zotero.Connector.Translate.Detect as this.init
*/
"init":Zotero.Connector.Translate.Detect.prototype.init,
/**
* Callback to be executed when items must be selected
* @param {Zotero.Translate} translate
* @param {Object} itemList ID=>text pairs representing available items
*/
"_selectItems":function(translate, itemList) {
var instanceID = Zotero.randomString();
Zotero.Connector.Translate._waitingForSelection[instanceID] = this;
// Fix for translators that don't create item lists as objects
if(itemList.push && typeof itemList.push === "function") {
var newItemList = {};
for(var item in itemList) {
Zotero.debug(item);
newItemList[item] = itemList[item];
}
itemList = newItemList;
}
// Send "Multiple Choices" HTTP response
this.sendResponse(300, "application/json", JSON.stringify({"items":itemList, "instanceID":instanceID, "uri":this._parsedPostData.uri}));
// We need this to make sure that we won't stop Firefox from quitting, even if the user
// didn't close the selectItems window
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
var me = this;
var quitObserver = {observe:function() { me.selectedItems = false; }};
observerService.addObserver(quitObserver, "quit-application", false);
this.selectedItems = null;
var endTime = Date.now() + 60*60*1000; // after an hour, timeout, so that we don't
// permanently slow Firefox with this loop
while(this.selectedItems === null && Date.now() < endTime) {
Zotero.mainThread.processNextEvent(true);
}
observerService.removeObserver(quitObserver, "quit-application");
if(!this.selectedItems) this._progressWindow.close();
return this.selectedItems;
},
/**
* Callback to be executed when list of translators becomes available. Opens progress window,
* selects specified translator, and initiates translation.
* @param {Zotero.Translate} translate
* @param {Zotero.Translator[]} translators
*/
"_translatorsAvailable":function(translate, translators) {
// make sure translatorsAvailable succeded
if(!translators.length) {
Zotero.Browser.deleteHiddenBrowser(this._browser);
this.sendResponse(500);
return;
}
// set up progress window
var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
this._progressWindow = win.Zotero_Browser.progress;
if(Zotero.locked) {
this._progressWindow.changeHeadline(Zotero.getString("ingester.scrapeError"));
var desc = Zotero.localeJoin([
Zotero.getString('general.operationInProgress'), Zotero.getString('general.operationInProgress.waitUntilFinishedAndTryAgain')
]);
this._progressWindow.addDescription(desc);
this._progressWindow.show();
this._progressWindow.startCloseTimer(8000);
return;
}
this._progressWindow.show();
// set save callbacks
this._libraryID = null;
var collection = null;
try {
this._libraryID = win.ZoteroPane.getSelectedLibraryID();
collection = win.ZoteroPane.getSelectedCollection();
} catch(e) {}
var me = this;
translate.setHandler("select", function(obj, item) { return me._selectItems(obj, item) });
translate.setHandler("itemDone", function(obj, item) { win.Zotero_Browser.itemDone(obj, item, collection) });
translate.setHandler("done", function(obj, item) {
win.Zotero_Browser.finishScraping(obj, item, collection);
me._translate.cookieManager.destroy();
Zotero.Browser.deleteHiddenBrowser(me._browser);
me.sendResponse(201);
});
// set translator and translate
translate.setTranslator(this._parsedPostData.translatorID);
translate.translate(this._libraryID);
}
}
/**
* Handle item selection
*/
Zotero.Connector.Translate.Select = function() {};
Zotero.Connector.Translate.Select.prototype = {
/**
* Finishes up translation when item selection is complete
* @param {String} method "GET" or "POST"
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(method, postData, sendResponseCallback) {
if(method != "POST") {
sendResponseCallback(400);
return;
}
var postData = JSON.parse(postData);
var saveInstance = Zotero.Connector.Translate._waitingForSelection[postData.instanceID];
saveInstance.sendResponse = sendResponseCallback;
saveInstance.selectedItems = false;
for(var i in postData.items) {
saveInstance.selectedItems = postData.items;
break;
}
}
}
/**
* Endpoints for the Connector HTTP server
*
* Each endpoint should take the form of an object. The init() method of this object will be passed:
* method - the method of the request ("GET" or "POST")
* data - the query string (for a "GET" request) or POST data (for a "POST" request)
* sendResponseCallback - a function to send a response to the HTTP request. This can be passed
* a response code alone (e.g., sendResponseCallback(404)) or a response
* code, MIME type, and response body
* (e.g., sendResponseCallback(200, "text/plain", "Hello World!"))
*/
Zotero.Connector.Endpoints = {
"/translate/list":Zotero.Connector.Translate.List,
"/translate/detect":Zotero.Connector.Translate.Detect,
"/translate/save":Zotero.Connector.Translate.Save,
"/translate/select":Zotero.Connector.Translate.Select
}

View file

@ -0,0 +1,113 @@
/*
***** 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
/**
* Emulates very small parts of cachedTypes.js and itemFields.js APIs for use with connector
*/
/**
* @namespace
*/
Zotero.Connector.Types = new function() {
/**
* Initializes types
* @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema
*/
this.init = function(typeSchema) {
const schemaTypes = ["itemTypes", "creatorTypes", "fields"];
// attach IDs and make referenceable by either ID or name
for(var i=0; i<schemaTypes.length; i++) {
var schemaType = schemaTypes[i];
this[schemaType] = typeSchema[schemaType];
for(var id in this[schemaType]) {
var entry = this[schemaType][id];
entry.id = id;
this[schemaType][entry.name] = entry;
}
}
}
}
Zotero.CachedTypes = function() {
this.getID = function(idOrName) {
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
return Zotero.Connector.Types[this.schemaType][idOrName].id;
}
this.getName = function(idOrName) {
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
return Zotero.Connector.Types[this.schemaType][idOrName].name;
}
this.getLocalizedString = function(idOrName) {
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
return Zotero.Connector.Types[this.schemaType][idOrName].localizedString;
}
}
Zotero.ItemTypes = new function() {
this.schemaType = "itemTypes";
Zotero.CachedTypes.call(this);
this.getImageSrc = function(idOrName) {
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
if(Zotero.isFx) {
return "chrome://zotero/skin/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
} else {
return "images/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
}
}
}
Zotero.CreatorTypes = new function() {
this.schemaType = "creatorTypes";
Zotero.CachedTypes.call(this);
this.getTypesForItemType = function(idOrName) {
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
var itemType = Zotero.Connector.Types["itemTypes"][idOrName];
var creatorTypes = [];
for(var i=0; i<itemType.creatorTypes.length; i++) {
creatorTypes.push(Zotero.Connector.Types["creatorTypes"][itemType.creatorTypes[i]]);
}
return creatorTypes;
}
}
Zotero.ItemFields = new function() {
this.schemaType = "fields";
Zotero.CachedTypes.call(this);
this.isValidForType = function(fieldIdOrName, typeIdOrName) {
// mimics itemFields.js
if(!Zotero.Connector.Types["fields"][fieldIdOrName]
|| !Zotero.Connector.Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID";
return Zotero.Connector.Types["itemTypes"][typeIdOrName].fields.indexOf(
Zotero.Connector.Types["fields"][fieldIdOrName].id) !== -1;
}
}

View file

@ -0,0 +1,176 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2011 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 <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Connector = new function() {
const CONNECTOR_URI = "http://127.0.0.1:23119/";
this.isOnline = true;
this.haveRefreshedData = false;
this.data = null;
/**
* Called to initialize Zotero
*/
this.init = function() {
Zotero.Connector.getData();
}
function _getDataFile() {
var dataFile = Zotero.getZoteroDirectory();
dataFile.append("connector.json");
return dataFile;
}
/**
* Serializes the Zotero.Connector.data object to localStorage/preferences
* @param {String} [json] The
*/
this.serializeData = function(json) {
if(!json) json = JSON.stringify(Zotero.Connector.data);
if(Zotero.isFx) {
Zotero.File.putContents(_getDataFile(), json);
} else {
localStorage.data = json;
}
}
/**
* Unserializes the Zotero.Connector.data object from localStorage/preferences
*/
this.unserializeData = function() {
var data = null;
if(Zotero.isFx) {
var dataFile = _getDataFile();
if(dataFile.exists()) data = Zotero.File.getContents(dataFile);
} else {
if(localStorage.data) data = localStorage.data;
}
if(data) Zotero.Connector.data = JSON.parse(data);
}
// saner descriptions of some HTTP error codes
this.EXCEPTION_NOT_AVAILABLE = 0;
this.EXCEPTION_BAD_REQUEST = 400;
this.EXCEPTION_NO_ENDPOINT = 404;
this.EXCEPTION_CONNECTOR_INTERNAL = 500;
this.EXCEPTION_METHOD_NOT_IMPLEMENTED = 501;
this.EXCEPTION_CODES = [0, 400, 404, 500, 501];
/**
* Updates Zotero's status depending on the success or failure of a request
*
* @param {Boolean} isOnline Whether or not Zotero was online
* @param {Function} successCallback Function to be called after loading new data if
* Zotero is online
* @param {Function} failureCallback Function to be called if Zotero is offline
*
* Calls Zotero.Connector.Browser.onStateChange(isOnline, method, context) if status has changed
*/
function _checkState(isOnline, callback) {
if(isOnline) {
if(Zotero.Connector.haveRefreshedData) {
if(callback) callback(true);
} else {
Zotero.Connector.getData(callback);
}
} else {
if(callback) callback(false, this.EXCEPTION_NOT_AVAILABLE);
}
if(Zotero.Connector.isOnline !== isOnline) {
Zotero.Connector.isOnline = isOnline;
if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) {
Zotero.Connector_Browser.onStateChange(isOnline);
}
}
return isOnline;
}
/**
* Loads list of translators and other relevant data from local Zotero instance
*
* @param {Function} successCallback Function to be called after loading new data if
* Zotero is online
* @param {Function} failureCallback Function to be called if Zotero is offline
*/
this.getData = function(callback) {
Zotero.HTTP.doPost(CONNECTOR_URI+"connector/getData",
JSON.stringify({"browser":Zotero.Connector_Browser}),
function(req) {
var isOnline = req.status !== 0;
if(isOnline) {
// if request succeded, update data
Zotero.Connector.haveRefreshedData = true;
Zotero.Connector.serializeData(req.responseText);
Zotero.Connector.data = JSON.parse(req.responseText);
} else {
// if request failed, unserialize saved data
Zotero.Connector.unserializeData();
}
Zotero.Connector.Types.init(Zotero.Connector.data.schema);
// update online state. this shouldn't loop, since haveRefreshedData should
// be true if isOnline is true.
_checkState(isOnline, callback);
}, {"Content-Type":"application/json"});
}
/**
* Sends the XHR to execute an RPC call.
*
* @param {String} method RPC method. See documentation above.
* @param {Object} data RPC data. See documentation above.
* @param {Function} successCallback Function to be called if request succeeded.
* @param {Function} failureCallback Function to be called if request failed.
*/
this.callMethod = function(method, data, callback) {
Zotero.HTTP.doPost(CONNECTOR_URI+"connector/"+method, JSON.stringify(data),
function(req) {
_checkState(req.status != 0, function() {
if(!callback) callback(false);
if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) {
if(callback) callback(false, req.status);
} else {
if(callback) {
var val = undefined;
if(req.responseText) {
if(req.getResponseHeader("Content-Type") === "application/json") {
val = JSON.parse(req.responseText);
} else {
val = req.responseText;
}
}
callback(val, req.status);
}
}
});
}, {"Content-Type":"application/json"});
}
}

View file

@ -23,8 +23,18 @@
***** END LICENSE BLOCK *****
*/
Zotero.Translate.Item = {
"saveItem":function (translate, item) {
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) {
this.newItems = [];
}
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
Zotero.Translate.ItemSaver.prototype = {
"saveItem":function(item) {
this.newItems.push(item);
Zotero.debug("Saving item");
Zotero.Connector.callMethod("saveItems", {"items":[item]});
}
}
};

View file

@ -0,0 +1,341 @@
/*
***** 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 <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
// Enumeration of types of translators
const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
/**
* Singleton to handle loading and caching of translators
* @namespace
*/
Zotero.Translators = new function() {
var _cache, _translators;
var _initialized = false;
/**
* Initializes translator cache, loading all relevant translators into memory
*/
this.init = function() {
_cache = {"import":[], "export":[], "web":[], "search":[]};
_translators = {};
_initialized = true;
// Build caches
var translators = Zotero.Connector.data.translators;
for(var i=0; i<translators.length; i++) {
var translator = new Zotero.Translator(translators[i]);
_translators[translator.translatorID] = translator;
for(var type in TRANSLATOR_TYPES) {
if(translator.translatorType & TRANSLATOR_TYPES[type]) {
_cache[type].push(translator);
}
}
}
// Sort by priority
var cmp = function (a, b) {
if (a.priority > b.priority) {
return 1;
}
else if (a.priority < b.priority) {
return -1;
}
}
for(var type in _cache) {
_cache[type].sort(cmp);
}
}
/**
* Gets the translator that corresponds to a given ID
* @param {String} id The ID of the translator
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned.
*/
this.get = function(id, callback) {
if(!_initialized) Zotero.Translators.init();
var translator = _translators[id];
if(!translator) {
callback(false);
return false;
}
// only need to get code if it is of some use
if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
translator.getCode(function() { callback(translator) });
} else {
callback(translator);
}
}
/**
* Gets all translators for a specific type of translation
* @param {String} type The type of translators to get (import, export, web, or search)
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned.
*/
this.getAllForType = function(type, callback) {
if(!_initialized) Zotero.Translators.init()
var translators = _cache[type].slice(0);
new Zotero.Translators.CodeGetter(translators, callback, translators);
return true;
}
/**
* Gets web translators for a specific location
* @param {String} uri The URI for which to look for translators
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned. The callback is passed a set of functions for
* converting URLs from proper to proxied forms as the second
* argument.
*/
this.getWebTranslatorsForLocation = function(uri, callback) {
if(!_initialized) Zotero.Translators.init();
var allTranslators = _cache["web"];
var potentialTranslators = [];
var searchURIs = [uri];
Zotero.debug("Translators: Looking for translators for "+uri);
// if there is a subdomain that is also a TLD, also test against URI with the domain
// dropped after the TLD
// (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
var properHosts = [];
var proxyHosts = [];
if(m) {
var hostnames = m[2].split(".");
for(var i=1; i<hostnames.length-2; i++) {
if(TLDS[hostnames[i].toLowerCase()]) {
var properHost = hostnames.slice(0, i+1).join(".");
searchURIs.push(m[1]+properHost+uri.substr(m[0].length));
properHosts.push(properHost);
proxyHosts.push(hostnames.slice(i+1).join("."));
}
}
}
var converterFunctions = [];
for(var i=0; i<allTranslators.length; i++) {
for(var j=0; j<searchURIs.length; j++) {
// don't attempt to use translators with no target that can't be run in this browser
// since that would require transmitting every page to Zotero host
if(!allTranslators[i].webRegexp
&& allTranslators[i].runMode !== Zotero.Translator.RUN_MODE_IN_BROWSER) {
continue;
}
if(!allTranslators[i].webRegexp
|| (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) {
// add translator to list
potentialTranslators.push(allTranslators[i]);
if(j === 0) {
converterFunctions.push(null);
} else if(Zotero.isFx) {
// in Firefox, push the converterFunction
converterFunctions.push(new function() {
var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi");
var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$");
return function(uri) { return uri.replace(re, "$&."+proxyHost) };
});
} else {
// in Chrome/Safari, the converterFunction needs to be passed as JSON, so
// just push an array with the proper and proxyHosts
converterFunctions.push([properHosts[j-1], proxyHosts[j-1]]);
}
// don't add translator more than once
break;
}
}
}
new Zotero.Translators.CodeGetter(potentialTranslators, callback,
[potentialTranslators, converterFunctions]);
return true;
}
/**
* Converts translators to JSON-serializable objects
*/
this.serialize = function(translator) {
// handle translator arrays
if(translator.length !== undefined) {
var newTranslators = new Array(translator.length);
for(var i in translator) {
newTranslators[i] = Zotero.Translators.serialize(translator[i]);
}
return newTranslators;
}
// handle individual translator
var newTranslator = {};
for(var i in PRESERVE_PROPERTIES) {
var property = PRESERVE_PROPERTIES[i];
newTranslator[property] = translator[property];
}
return newTranslator;
}
}
/**
* A class to get the code for a set of translators at once
*/
Zotero.Translators.CodeGetter = function(translators, callback, callbackArgs) {
this._translators = translators;
this._callbackArgs = callbackArgs;
this._callback = callback;
this.getCodeFor(0);
}
Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
var me = this;
while(true) {
if(i === this._translators.length) {
// all done; run callback
this._callback(this._callbackArgs);
return;
}
if(this._translators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
// get next translator
this._translators[i].getCode(function() { me.getCodeFor(i+1) });
return;
}
// if we are not at end of list and there is no reason to retrieve the code, keep going
// through the list of potential translators
i++;
}
}
const TRANSLATOR_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
"priority", "browserSupport"];
var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "configOptions",
"code", "runMode"]);
/**
* @class Represents an individual translator
* @constructor
* @property {String} translatorID Unique GUID of the translator
* @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
* @property {String} label Human-readable name of the translator
* @property {String} creator Author(s) of the translator
* @property {String} target Location that the translator processes
* @property {String} minVersion Minimum Zotero version
* @property {String} maxVersion Minimum Zotero version
* @property {Integer} priority Lower-priority translators will be selected first
* @property {String} browserSupport String indicating browser supported by the translator
* g = Gecko (Firefox)
* c = Google Chrome (WebKit & V8)
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
* i = Internet Explorer
* @property {Object} configOptions Configuration options for import/export
* @property {Object} displayOptions Display options for export
* @property {Boolean} inRepository Whether the translator may be found in the repository
* @property {String} lastUpdated SQL-style date and time of translator's last update
* @property {String} code The executable JavaScript for the translator
*/
Zotero.Translator = function(info) {
// make sure we have all the properties
for(var i in TRANSLATOR_PROPERTIES) {
var property = TRANSLATOR_PROPERTIES[i];
if(info[property] === undefined) {
this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label);
haveMetadata = false;
break;
} else {
this[property] = info[property];
}
}
if(info["browserSupport"].indexOf(Zotero.browser) !== -1) {
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
} else {
this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
}
this._configOptions = info["configOptions"] ? info["configOptions"] : {};
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
// compile import regexp to match only file extension
this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
}
if(this.translatorType & TRANSLATOR_TYPES["web"]) {
// compile web regexp
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
}
if(info.code) {
this.code = info.code;
}
}
Zotero.Translator.prototype.getCode = function(callback) {
if(this.code) {
callback(true);
} else {
var me = this;
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":this.translatorID},
function(code) {
if(!code) {
callback(false);
} else {
me.code = code;
callback(true);
}
}
);
}
}
Zotero.Translator.prototype.__defineGetter__("displayOptions", function() {
return Zotero.Utilities.deepCopy(this._displayOptions);
});
Zotero.Translator.prototype.__defineGetter__("configOptions", function() {
return Zotero.Utilities.deepCopy(this._configOptions);
});
/**
* Log a translator-related error
* @param {String} message The error message
* @param {String} [type] The error type ("error", "warning", "exception", or "strict")
* @param {String} [line] The text of the line on which the error occurred
* @param {Integer} lineNumber
* @param {Integer} colNumber
*/
Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
Zotero.log(message, type ? type : "error", this.label);
}
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;

View file

@ -61,7 +61,7 @@ Zotero.Date = new function(){
.getService(Components.interfaces.nsIStringBundleService).createBundle(src, appLocale);
_months = {"short":[], "long":[]};
for(let i=1; i<=12; i++) {
for(var i=1; i<=12; i++) {
_months.short.push(bundle.GetStringFromName("month."+i+".Mmm"));
_months.long.push(bundle.GetStringFromName("month."+i+".name"));
}

View file

@ -129,7 +129,7 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
}
dataset.push(row);
}
statement.reset();
statement.finalize();
return dataset.length ? dataset : false;
}
@ -170,12 +170,12 @@ Zotero.DBConnection.prototype.valueQuery = function (sql,params) {
// No rows
if (!statement.executeStep()) {
statement.reset();
statement.finalize();
return false;
}
var value = this._getTypedValue(statement, 0);
statement.reset();
statement.finalize();
return value;
}
@ -202,7 +202,7 @@ Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
while (statement.executeStep()) {
column.push(this._getTypedValue(statement, 0));
}
statement.reset();
statement.finalize();
return column.length ? column : false;
}
return false;
@ -630,7 +630,7 @@ Zotero.DBConnection.prototype.getColumns = function (table) {
for (var i=0,len=statement.columnCount; i<len; i++) {
cols.push(statement.getColumnName(i));
}
statement.reset();
statement.finalize();
return cols;
}
catch (e) {
@ -771,8 +771,11 @@ Zotero.DBConnection.prototype.checkException = function (e) {
Zotero.DBConnection.prototype.closeDatabase = function () {
var db = this._getDBConnection();
db.close();
if(this._connection) {
this.stopDummyStatement();
this._connection.close();
return true;
}
}
@ -855,10 +858,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
var hadDummyStatement = !!this._dummyStatement;
try {
if (dbLockExclusive) {
Zotero.DB.query("PRAGMA locking_mode=NORMAL");
this.query("PRAGMA locking_mode=NORMAL");
}
if (hadDummyStatement) {
Zotero.DB.stopDummyStatement();
this.stopDummyStatement();
}
var store = Components.classes["@mozilla.org/storage/service;1"].
@ -872,10 +875,10 @@ Zotero.DBConnection.prototype.backupDatabase = function (suffix) {
}
finally {
if (dbLockExclusive) {
Zotero.DB.query("PRAGMA locking_mode=EXCLUSIVE");
this.query("PRAGMA locking_mode=EXCLUSIVE");
}
if (hadDummyStatement) {
Zotero.DB.startDummyStatement();
this.startDummyStatement();
}
}
@ -1003,8 +1006,10 @@ Zotero.DBConnection.prototype.stopDummyStatement = function () {
}
Zotero.debug("Stopping dummy statement for '" + this._dbName + "'");
this._dummyStatement.reset();
this._dummyStatement = null;
this._dummyStatement.finalize();
this._dummyConnection.close();
delete this._dummyConnection;
delete this._dummyStatement;
}

View file

@ -25,8 +25,8 @@
Zotero.Debug = new function () {
this.__defineGetter__('storing', function () _store);
this.__defineGetter__('enabled', function () _console || _store);
this.__defineGetter__('storing', function () { return _store; });
this.__defineGetter__('enabled', function () { return _console || _store; });
var _console;
var _store;
@ -81,7 +81,12 @@ Zotero.Debug = new function () {
}
if (_console) {
dump('zotero(' + level + ')' + (_time ? deltaStr : '') + ': ' + message + "\n\n");
var output = 'zotero(' + level + ')' + (_time ? deltaStr : '') + ': ' + message;
if(Zotero.isFx) {
dump(output+"\n\n");
} else {
console.log(output);
}
}
if (_store) {
if (Math.random() < 1/1000) {

View file

@ -31,7 +31,6 @@ const DATA_VERSION = 3;
// this is used only for update checking
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
const INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
Zotero.Integration = new function() {
var _fifoFile = null;
@ -39,8 +38,7 @@ Zotero.Integration = new function() {
var _osascriptFile;
var _inProgress = false;
var _integrationVersionsOK = null;
var _pipeMode = false;
var _winUser32;
var INTEGRATION_MIN_VERSIONS;
// these need to be global because of GC
var _timer;
@ -52,29 +50,32 @@ Zotero.Integration = new function() {
* Initializes the pipe used for integration on non-Windows platforms.
*/
this.init = function() {
// initialize SOAP server just to throw version errors
Zotero.Integration.Compat.init();
if(Zotero.isMac || Zotero.isWin) { // on Mac or Windows, we don't have pipe issues
INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.1b1", "3.1b1"];
} else { // on *NIX, there's no point in supporting 3.1b1
INTEGRATION_MIN_VERSIONS = ["3.1.2", "3.5a1", "3.1b1"];
}
// Windows uses a command line handler for integration. See
// We only use an integration pipe on OS X.
// On Linux, we use the alternative communication method in the OOo plug-in
// On Windows, we use a command line handler for integration. See
// components/zotero-integration-service.js for this implementation.
if(Zotero.isWin) return;
if(!Zotero.isMac) return;
// Determine where to put the pipe
if(Zotero.isMac) {
// on OS X, first try /Users/Shared for those who can't put pipes in their home
// directories
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
_fifoFile.initWithPath("/Users/Shared");
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
var logname = Components.classes["@mozilla.org/process/environment;1"].
getService(Components.interfaces.nsIEnvironment).
get("LOGNAME");
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
} else {
_fifoFile = null;
}
// on OS X, first try /Users/Shared for those who can't put pipes in their home
// directories
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
_fifoFile.initWithPath("/Users/Shared");
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
var logname = Components.classes["@mozilla.org/process/environment;1"].
getService(Components.interfaces.nsIEnvironment).
get("LOGNAME");
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
} else {
_fifoFile = null;
}
if(!_fifoFile) {
@ -85,8 +86,6 @@ Zotero.Integration = new function() {
_fifoFile.append(".zoteroIntegrationPipe");
}
Zotero.debug("Initializing Zotero integration pipe at "+_fifoFile.path);
// destroy old pipe, if one exists
try {
if(_fifoFile.exists()) {
@ -100,35 +99,27 @@ Zotero.Integration = new function() {
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
+ "for instructions on correcting this problem."
);
if(Zotero.isMac) {
// can attempt to delete on OS X
try {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
if(!deletePipe) return;
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
if(_fifoFile.exists()) return;
} catch(e) {
Zotero.logError(e);
return;
}
// can attempt to delete on OS X
try {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
if(!deletePipe) return;
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
if(_fifoFile.exists()) return;
} catch(e) {
Zotero.logError(e);
return;
}
}
// try to initialize pipe
try {
var pipeInitialized = _initializeIntegrationPipe();
Zotero.IPC.Pipe.initPipeListener(_fifoFile, _parseIntegrationPipeCommand);
} catch(e) {
Components.utils.reportError(e);
}
if(pipeInitialized) {
// if initialization succeeded, add an observer so that we don't hang shutdown
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver({ observe: Zotero.Integration.destroy }, "quit-application", false);
Zotero.logError(e);
}
_updateTimer = Components.classes["@mozilla.org/timer;1"].
@ -210,246 +201,14 @@ Zotero.Integration = new function() {
if(parts) {
var agent = parts[1].toString();
var cmd = parts[2].toString();
// return if we were told to shutdown
if(agent === "Zotero" && cmd === "shutdown") return;
_initializePipeStreamPump();
var document = parts[3] ? parts[3].toString() : null;
Zotero.Integration.execCommand(agent, cmd, document);
} else {
_initializePipeStreamPump();
Components.utils.reportError("Zotero: Invalid integration input received: "+string);
}
} else {
_initializePipeStreamPump();
}
}
/**
* Listens asynchronously for data on the integration pipe and reads it when available
*
* Used to read from the integration pipe on Fx 4.2
*/
var _integrationPipeListenerFx42 = {
"onStartRequest":function() {},
"onStopRequest":function() {},
"onDataAvailable":function(request, context, inputStream, offset, count) {
// read from pipe
var converterInputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
converterInputStream.init(inputStream, "UTF-8", 4096,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var out = {};
converterInputStream.readString(count, out);
inputStream.close();
_parseIntegrationPipeCommand(out.value);
}};
/**
* Polling mechanism for file
*/
var _integrationPipeObserverFx36 = {"notify":function() {
if(_fifoFile.fileSize === 0) return;
// read from pipe (file, actually)
var string = Zotero.File.getContents(_fifoFile);
// clear file
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.close();
// run command
_parseIntegrationPipeCommand(string);
}};
/**
* Initializes the nsIInputStream and nsIInputStreamPump to read from _fifoFile
*/
function _initializePipeStreamPump() {
// Fx >4 supports deferred open; no need to use sh
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
// 16 = open as deferred so that we don't block on open
fifoStream.init(_fifoFile, -1, 0, 16);
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(fifoStream, -1, -1, 4096, 1, true);
pump.asyncRead(_integrationPipeListenerFx42, null);
}
/**
* Initializes the Zotero Integration Pipe
*/
function _initializeIntegrationPipe() {
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
getService(Components.interfaces.nsIXULAppInfo);
if(Zotero.isFx4) {
if(verComp.compare("2.0b9pre", appInfo.platformVersion) > 0) {
Components.utils.reportError("Zotero word processor integration requires "+
"Firefox 4.0b9 or later. Please update to the latest Firefox 4.0 beta.");
return;
} else if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) {
_pipeMode = "deferredOpen";
} else {
_pipeMode = "fx4thread";
}
} else {
if(Zotero.isMac) {
_pipeMode = "poll";
} else {
_pipeMode = "fx36thread";
}
}
Zotero.debug("Using integration pipe mode "+_pipeMode);
if(_pipeMode === "poll") {
// create empty file
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.close();
// no deferred open capability, so we need to poll
// has to be global so that we don't get garbage collected
_timer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
_timer.initWithCallback(_integrationPipeObserverFx36, 1000,
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
} else {
// make a new pipe
var mkfifo = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
mkfifo.initWithPath("/usr/bin/mkfifo");
if(!mkfifo.exists()) mkfifo.initWithPath("/bin/mkfifo");
if(!mkfifo.exists()) mkfifo.initWithPath("/usr/local/bin/mkfifo");
if(mkfifo.exists()) {
// create named pipe
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(mkfifo);
proc.run(true, [_fifoFile.path], 1);
if(_fifoFile.exists()) {
if(_pipeMode === "deferredOpen") {
_initializePipeStreamPump();
} else if(_pipeMode === "fx36thread") {
var main = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
var background = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0);
function mainThread(agent, cmd, doc) {
this.agent = agent;
this.cmd = cmd;
this.document = doc;
}
mainThread.prototype.run = function() {
Zotero.Integration.execCommand(this.agent, this.cmd, this.document);
}
function fifoThread() {}
fifoThread.prototype.run = function() {
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
var line = {};
while(true) {
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
fifoStream.init(_fifoFile, -1, 0, 0);
fifoStream.QueryInterface(Components.interfaces.nsILineInputStream);
fifoStream.readLine(line);
fifoStream.close();
var parts = line.value.split(" ");
var agent = parts[0];
var cmd = parts[1];
var document = parts.length >= 3 ? line.value.substr(agent.length+cmd.length+2) : null;
if(agent == "Zotero" && cmd == "shutdown") return;
main.dispatch(new mainThread(agent, cmd, document), background.DISPATCH_NORMAL);
}
}
fifoThread.prototype.QueryInterface = mainThread.prototype.QueryInterface = function(iid) {
if (iid.equals(Components.interfaces.nsIRunnable) ||
iid.equals(Components.interfaces.nsISupports)) return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
background.dispatch(new fifoThread(), background.DISPATCH_NORMAL);
} else if(_pipeMode === "fx4thread") {
Components.utils.import("resource://gre/modules/ctypes.jsm");
// get possible names for libc
if(Zotero.isMac) {
var possibleLibcs = ["/usr/lib/libc.dylib"];
} else {
var possibleLibcs = [
"libc.so.6",
"libc.so.6.1",
"libc.so"
];
}
// try all possibilities
while(possibleLibcs.length) {
var libc = possibleLibcs.shift();
try {
var lib = ctypes.open(libc);
break;
} catch(e) {}
}
// throw appropriate error on failure
if(!lib) {
throw "libc could not be loaded. Please post on the Zotero Forums so we can add "+
"support for your operating system.";
}
// int mkfifo(const char *path, mode_t mode);
var mkfifo = lib.declare("mkfifo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_int);
// make pipe
var ret = mkfifo(_fifoFile.path, 0600);
if(!_fifoFile.exists()) return false;
lib.close();
// set up worker
var worker = Components.classes["@mozilla.org/threads/workerfactory;1"]
.createInstance(Components.interfaces.nsIWorkerFactory)
.newChromeWorker("chrome://zotero/content/xpcom/integration_worker.js");
worker.onmessage = function(event) {
if(event.data[0] == "Exception") {
throw event.data[1];
} else if(event.data[0] == "Debug") {
Zotero.debug(event.data[1]);
} else {
Zotero.Integration.execCommand(event.data[0], event.data[1], event.data[2]);
}
}
worker.postMessage({"path":_fifoFile.path, "libc":libc});
}
} else {
Components.utils.reportError("Zotero: mkfifo failed -- not initializing integration pipe");
return false;
}
} else {
Components.utils.reportError("Zotero: mkfifo or sh not found -- not initializing integration pipe");
return false;
}
}
return true;
}
/**
* Calls the Integration applicatoon
*/
@ -546,22 +305,6 @@ Zotero.Integration = new function() {
}
}
/**
* Destroys the integration pipe.
*/
this.destroy = function() {
if(_pipeMode !== "poll") {
// send shutdown message to fifo thread
var oStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
getService(Components.interfaces.nsIFileOutputStream);
oStream.init(_fifoFile, 0x02 | 0x10, 0, 0);
var cmd = "Zotero shutdown\n";
oStream.write(cmd, cmd.length);
oStream.close();
}
_fifoFile.remove(false);
}
/**
* Activates Firefox
*/

View file

@ -0,0 +1,484 @@
/*
***** 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.IPC = new function() {
var _libc, _libcPath, _instancePipe, _user32;
/**
* Initialize pipe for communication with connector
*/
this.init = function() {
if(!Zotero.isWin && (Zotero.isFx4 || Zotero.isMac)) { // no pipe support on Fx 3.6
_instancePipe = _getPipeDirectory();
if(!_instancePipe.exists()) {
_instancePipe.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
}
_instancePipe.append(Zotero.instanceID);
Zotero.IPC.Pipe.initPipeListener(_instancePipe, this.parsePipeInput);
}
}
/**
* Parses input received via instance pipe
*/
this.parsePipeInput = function(msg) {
// remove a newline if there is one
if(msg[msg.length-1] === "\n") msg = msg.substr(0, msg.length-1);
Zotero.debug('IPC: Received "'+msg+'"');
if(msg === "releaseLock" && !Zotero.isConnector) {
switchConnectorMode(true);
} else if(msg === "lockReleased") {
Zotero.onDBLockReleased();
} else if(msg === "initComplete") {
Zotero.onInitComplete();
}
}
/**
* Broadcast a message to all other Zotero instances
*/
this.broadcast = function(msg) {
if(Zotero.isWin) { // communicate via WM_COPYDATA method
// there is no ctypes struct support in Fx 3.6
// while we could mimic it, it's easier just to require users to upgrade if they
// want connector sharing
if(!Zotero.isFx4) return false;
Components.utils.import("resource://gre/modules/ctypes.jsm");
// communicate via message window
var user32 = ctypes.open("user32.dll");
/* http://msdn.microsoft.com/en-us/library/ms633499%28v=vs.85%29.aspx
* HWND WINAPI FindWindow(
* __in_opt LPCTSTR lpClassName,
* __in_opt LPCTSTR lpWindowName
* );
*/
var FindWindow = user32.declare("FindWindowW", ctypes.winapi_abi, ctypes.int32_t,
ctypes.jschar.ptr, ctypes.jschar.ptr);
/* http://msdn.microsoft.com/en-us/library/ms633539%28v=vs.85%29.aspx
* BOOL WINAPI SetForegroundWindow(
* __in HWND hWnd
* );
*/
var SetForegroundWindow = user32.declare("SetForegroundWindow", ctypes.winapi_abi,
ctypes.bool, ctypes.int32_t);
/*
* LRESULT WINAPI SendMessage(
* __in HWND hWnd,
* __in UINT Msg,
* __in WPARAM wParam,
* __in LPARAM lParam
* );
*/
var SendMessage = user32.declare("SendMessageW", ctypes.winapi_abi, ctypes.uintptr_t,
ctypes.int32_t, ctypes.unsigned_int, ctypes.voidptr_t, ctypes.voidptr_t);
/* http://msdn.microsoft.com/en-us/library/ms649010%28v=vs.85%29.aspx
* typedef struct tagCOPYDATASTRUCT {
* ULONG_PTR dwData;
* DWORD cbData;
* PVOID lpData;
* } COPYDATASTRUCT, *PCOPYDATASTRUCT;
*/
var COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT", [
{"dwData":ctypes.voidptr_t},
{"cbData":ctypes.uint32_t},
{"lpData":ctypes.voidptr_t}
]);
const appNames = ["Firefox", "Zotero", "Nightly", "Aurora", "Minefield"];
for each(var appName in appNames) {
// don't send messages to ourself
if(appName === Zotero.appName) continue;
var thWnd = FindWindow(appName+"MessageWindow", null);
if(thWnd) {
Zotero.debug('IPC: Broadcasting "'+msg+'" to window "'+appName+'MessageWindow"');
// allocate message
var data = ctypes.char.array()('firefox.exe -ZoteroIPC "'+msg.replace('"', '""', "g")+'"\x00C:\\');
var dataSize = data.length*data.constructor.size;
// create new COPYDATASTRUCT
var cds = new COPYDATASTRUCT();
cds.dwData = null;
cds.cbData = dataSize;
cds.lpData = data.address();
// send COPYDATASTRUCT
var success = SendMessage(thWnd, 0x004A /** WM_COPYDATA **/, null, cds.address());
user32.close();
return !!success;
}
}
user32.close();
return false;
} else { // communicate via pipes
// look for other Zotero instances
var pipes = [];
var pipeDir = _getPipeDirectory();
if(pipeDir.exists()) {
var dirEntries = pipeDir.directoryEntries;
while (dirEntries.hasMoreElements()) {
var pipe = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
if(pipe.leafName[0] !== "." && (!_instancePipe || !pipe.equals(_instancePipe))) {
pipes.push(pipe);
}
}
}
if(!pipes.length) return false;
// safely write to instance pipes
var lib = this.getLibc();
if(!lib) return false;
// int open(const char *path, int oflag);
if(Zotero.isFx36) {
var open = lib.declare("open", ctypes.default_abi, ctypes.int32_t, ctypes.string, ctypes.int32_t);
} else {
var open = lib.declare("open", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.int);
}
// ssize_t write(int fildes, const void *buf, size_t nbyte);
if(Zotero.isFx36) {
} else {
var write = lib.declare("write", ctypes.default_abi, ctypes.ssize_t, ctypes.int, ctypes.char.ptr, ctypes.size_t);
}
// int close(int filedes);
if(Zotero.isFx36) {
} else {
var close = lib.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
}
var success = false;
for each(var pipe in pipes) {
var fd = open(pipe.path, 0x0004 | 0x0001); // O_NONBLOCK | O_WRONLY
if(fd !== -1) {
Zotero.debug('IPC: Broadcasting "'+msg+'" to instance '+pipe.leafName);
success = true;
write(fd, msg+"\n", msg.length);
close(fd);
} else {
try {
pipe.remove(true);
} catch(e) {};
}
}
return success;
}
}
/**
* Get directory containing Zotero pipes
*/
function _getPipeDirectory() {
var dir = Zotero.getZoteroDirectory();
dir.append("pipes");
return dir;
}
/**
* Gets the path to libc as a string
*/
this.getLibcPath = function() {
if(_libcPath) return _libcPath;
Components.utils.import("resource://gre/modules/ctypes.jsm");
// get possible names for libc
if(Zotero.isMac) {
var possibleLibcs = ["/usr/lib/libc.dylib"];
} else {
var possibleLibcs = [
"libc.so.6",
"libc.so.6.1",
"libc.so"
];
}
// try all possibilities
while(possibleLibcs.length) {
var libPath = possibleLibcs.shift();
try {
var lib = ctypes.open(libPath);
break;
} catch(e) {}
}
// throw appropriate error on failure
if(!lib) {
Components.utils.reportError("Zotero: libc could not be loaded. Word processor integration "+
"and other functionality will not be available. Please post on the Zotero Forums so we "+
"can add support for your operating system.");
return;
}
_libc = lib;
_libcPath = libPath;
return libPath;
}
/**
* Gets standard C library via ctypes
*/
this.getLibc = function() {
if(!_libc) this.getLibcPath();
return _libc;
}
}
/**
* Methods for reading from and writing to a pipe
*/
Zotero.IPC.Pipe = new function() {
var _mkfifo, _pipeClass;
/**
* Creates and listens on a pipe
*
* @param {nsIFile} file The location where the pipe should be created
* @param {Function} callback A function to be passed any data recevied on the pipe
*/
this.initPipeListener = function(file, callback) {
Zotero.debug("IPC: Initializing pipe at "+file.path);
// determine type of pipe
if(!_pipeClass) {
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
getService(Components.interfaces.nsIXULAppInfo);
if(verComp.compare("2.2a1pre", appInfo.platformVersion) <= 0) { // Gecko 5
_pipeClass = Zotero.IPC.Pipe.DeferredOpen;
} else if(verComp.compare("2.0b9pre", appInfo.platformVersion) <= 0) { // Gecko 2.0b9+
_pipeClass = Zotero.IPC.Pipe.WorkerThread;
} else { // Gecko 1.9.2
_pipeClass = Zotero.IPC.Pipe.Poll;
}
}
// make new pipe
new _pipeClass(file, callback);
}
/**
* Makes a fifo
* @param {nsIFile} file Location to create the fifo
*/
this.mkfifo = function(file) {
// int mkfifo(const char *path, mode_t mode);
if(!_mkfifo) {
var libc = Zotero.IPC.getLibc();
if(!libc) return false;
if(Zotero.isFx36) {
_mkfifo = libc.declare("mkfifo", ctypes.default_abi, ctypes.int32_t, ctypes.string, ctypes.uint32_t);
} else {
_mkfifo = libc.declare("mkfifo", ctypes.default_abi, ctypes.int, ctypes.char.ptr, ctypes.unsigned_int);
}
}
// make pipe
var ret = _mkfifo(file.path, 0600);
return file.exists();
}
/**
* Adds a shutdown listener for a pipe that writes "Zotero shutdown\n" to the pipe and then
* deletes it
*/
this.writeShutdownMessage = function(file) {
var oStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
getService(Components.interfaces.nsIFileOutputStream);
oStream.init(file, 0x02 | 0x10, 0, 0);
const cmd = "Zotero shutdown\n";
oStream.write(cmd, cmd.length);
oStream.close();
file.remove(false);
Zotero.debug("IPC: Closing pipe "+file.path);
}
}
/**
* Listens asynchronously for data on the integration pipe and reads it when available
*
* Used to read from pipe on Gecko 5+
*/
Zotero.IPC.Pipe.DeferredOpen = function(file, callback) {
this._file = file;
this._callback = callback;
if(!Zotero.IPC.Pipe.mkfifo(file)) return;
this._initPump();
// add shutdown listener
Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, file));
}
Zotero.IPC.Pipe.DeferredOpen.prototype = {
"onStartRequest":function() {},
"onStopRequest":function() {},
"onDataAvailable":function(request, context, inputStream, offset, count) {
// read from pipe
var converterInputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
converterInputStream.init(inputStream, "UTF-8", 4096,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var out = {};
converterInputStream.readString(count, out);
inputStream.close();
if(out.value === "Zotero shutdown\n") return
this._initPump();
this._callback(out.value);
},
/**
* Initializes the nsIInputStream and nsIInputStreamPump to read from _fifoFile
*
* Used after reading from file on Gecko 5+
*/
"_initPump":function() {
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
// 16 = open as deferred so that we don't block on open
fifoStream.init(this._file, -1, 0, 16);
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(fifoStream, -1, -1, 4096, 1, true);
pump.asyncRead(this, null);
}
};
/**
* Listens synchronously for data on the integration pipe on a separate JS thread and reads it
* when available
*
* Used to read from pipe on Gecko 2
*/
Zotero.IPC.Pipe.WorkerThread = function(file, callback) {
this._callback = callback;
if(!Zotero.IPC.Pipe.mkfifo(file)) return;
// set up worker
var worker = Components.classes["@mozilla.org/threads/workerfactory;1"]
.createInstance(Components.interfaces.nsIWorkerFactory)
.newChromeWorker("chrome://zotero/content/xpcom/pipe_worker.js");
worker.onmessage = this.onmessage.bind(this);
worker.postMessage({"path":file.path, "libc":Zotero.IPC.getLibcPath()});
// add shutdown listener
Zotero.addShutdownListener(Zotero.IPC.Pipe.writeShutdownMessage.bind(null, file));
}
Zotero.IPC.Pipe.WorkerThread.prototype = {
/**
* onmessage call for worker thread, to get data from it
*/
"onmessage":function(event) {
if(event.data[0] === "Exception") {
throw event.data[1];
} else if(event.data[0] === "Debug") {
Zotero.debug(event.data[1]);
} else if(event.data[0] === "Read") {
this._callback(event.data[1]);
}
}
}
/**
* Polling mechanism for file
*
* Used to read from integration "pipe" on Gecko 1.9.2/Firefox 3.6
*/
Zotero.IPC.Pipe.Poll = function(file, callback) {
this._file = file;
this._callback = callback;
// create empty file
this._clearFile();
// no deferred open capability, so we need to poll
this._timer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
this._timer.initWithCallback(this, 1000,
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
// this has to be in global scope so we don't get garbage collected
Zotero.IPC.Pipe.Poll._activePipes.push(this);
// add shutdown listener
Zotero.addShutdownListener(this);
}
Zotero.IPC.Pipe.Poll._activePipes = [];
Zotero.IPC.Pipe.Poll.prototype = {
/**
* Called every second to check if there is new data to be read
*/
"notify":function() {
if(this._file.fileSize === 0) return;
// read from pipe (file, actually)
var string = Zotero.File.getContents(this._file);
this._clearFile();
// run command
this._callback(string);
},
/**
* Called on quit to remove the file
*/
"observe":function() {
this._file.remove();
},
/**
* Clears the old contents of the fifo file
*/
"_clearFile":function() {
// clear file
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
foStream.init(_fifoFile, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.close();
}
};

View file

@ -180,6 +180,8 @@ Zotero.MIMETypeHandler = new function () {
*/
var _Observer = new function() {
this.observe = function(channel) {
if(Zotero.isConnector) return;
channel.QueryInterface(Components.interfaces.nsIRequest);
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
@ -222,6 +224,7 @@ Zotero.MIMETypeHandler = new function () {
* Called to see if we can handle a content type
*/
this.canHandleContent = this.isPreferred = function(contentType, isContentPreferred, desiredContentType) {
if(Zotero.isConnector) return false;
return !!_typeHandlers[contentType.toLowerCase()];
}

View file

@ -54,19 +54,12 @@ onmessage = function(event) {
// extract message
var string = buf.readString();
var parts = string.match(/^([^ \n]*) ([^ \n]*)(?: ([^\n]*))?\n?$/);
if(!parts) {
postMessage(["Exception", "Integration Worker: Invalid input received: "+string]);
continue;
}
var agent = parts[1].toString();
var cmd = parts[2].toString();
var document = parts[3] ? parts[3] : null;
if(agent == "Zotero" && cmd == "shutdown") {
postMessage(["Debug", "Integration Worker: Shutting down"]);
if(string === "Zotero shutdown\n") {
postMessage(["Debug", "IPC: Worker closing "+event.data.path]);
lib.close();
return;
}
postMessage([agent, cmd, document]);
postMessage(["Read", string]);
}
};

View file

@ -480,8 +480,6 @@ const Zotero_Proxy_schemeParameterRegexps = {
"%a":/([^%])%a/
};
const Zotero_Proxy_metaRegexp = /[-[\]{}()*+?.\\^$|,#\s]/g;
/**
* Compiles the regular expression against which we match URLs to determine if this proxy is in use
* and saves it in this.regexp
@ -514,7 +512,7 @@ Zotero.Proxy.prototype.compileRegexp = function() {
})
// now replace with regexp fragment in reverse order
var re = "^"+this.scheme.replace(Zotero_Proxy_metaRegexp, "\\$&")+"$";
var re = "^"+Zotero.Utilities.quotemeta(this.scheme)+"$";
for(var i=this.parameters.length-1; i>=0; i--) {
var param = this.parameters[i];
re = re.replace(Zotero_Proxy_schemeParameterRegexps[param], "$1"+parametersToCheck[param]);
@ -571,7 +569,7 @@ Zotero.Proxy.prototype.save = function(transparent) {
if(hasErrors) throw "Zotero.Proxy: could not be saved because it is invalid: error "+hasErrors[0];
// we never save any changes to non-persisting proxies, so this works
var newProxy = !!this.proxyID;
var newProxy = !this.proxyID;
this.autoAssociate = this.multiHost && this.autoAssociate;
this.compileRegexp();

View file

@ -0,0 +1,379 @@
/*
***** 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Server = new function() {
var _onlineObserverRegistered;
var responseCodes = {
200:"OK",
201:"Created",
300:"Multiple Choices",
400:"Bad Request",
404:"Not Found",
500:"Internal Server Error",
501:"Method Not Implemented"
};
/**
* initializes a very rudimentary web server
*/
this.init = function() {
if (Zotero.HTTP.browserIsOffline()) {
Zotero.debug('Browser is offline -- not initializing HTTP server');
_registerOnlineObserver();
return;
}
// start listening on socket
var serv = Components.classes["@mozilla.org/network/server-socket;1"]
.createInstance(Components.interfaces.nsIServerSocket);
try {
// bind to a random port on loopback only
serv.init(Zotero.Prefs.get('httpServer.port'), true, -1);
serv.asyncListen(Zotero.Server.SocketListener);
Zotero.debug("HTTP server listening on 127.0.0.1:"+serv.port);
} catch(e) {
Zotero.debug("Not initializing HTTP server");
}
_registerOnlineObserver()
}
/**
* generates the response to an HTTP request
*/
this.generateResponse = function (status, contentType, body) {
var response = "HTTP/1.0 "+status+" "+responseCodes[status]+"\r\n";
response += "Access-Control-Allow-Origin: org.zotero.zoteroconnectorforsafari-69x6c999f9\r\n";
response += "Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD\r\n";
if(body) {
if(contentType) {
response += "Content-Type: "+contentType+"\r\n";
}
response += "\r\n"+body;
} else {
response += "Content-Length: 0\r\n\r\n";
}
return response;
}
function _registerOnlineObserver() {
if (_onlineObserverRegistered) {
return;
}
// Observer to enable the integration when we go online
var observer = {
observe: function(subject, topic, data) {
if (data == 'online') {
Zotero.Server.init();
}
}
};
var observerService =
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(observer, "network:offline-status-changed", false);
_onlineObserverRegistered = true;
}
}
Zotero.Server.SocketListener = new function() {
this.onSocketAccepted = onSocketAccepted;
this.onStopListening = onStopListening;
/*
* called when a socket is opened
*/
function onSocketAccepted(socket, transport) {
// get an input stream
var iStream = transport.openInputStream(0, 0, 0);
var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
var dataListener = new Zotero.Server.DataListener(iStream, oStream);
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
.createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(iStream, -1, -1, 0, 0, false);
pump.asyncRead(dataListener, null);
}
function onStopListening(serverSocket, status) {
Zotero.debug("HTTP server going offline");
}
}
/*
* handles the actual acquisition of data
*/
Zotero.Server.DataListener = function(iStream, oStream) {
this.header = "";
this.headerFinished = false;
this.body = "";
this.bodyLength = 0;
this.iStream = iStream;
this.oStream = oStream;
this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
this.sStream.init(iStream);
this.foundReturn = false;
}
/*
* called when a request begins (although the request should have begun before
* the DataListener was generated)
*/
Zotero.Server.DataListener.prototype.onStartRequest = function(request, context) {}
/*
* called when a request stops
*/
Zotero.Server.DataListener.prototype.onStopRequest = function(request, context, status) {
this.iStream.close();
this.oStream.close();
}
/*
* called when new data is available
*/
Zotero.Server.DataListener.prototype.onDataAvailable = function(request, context,
inputStream, offset, count) {
var readData = this.sStream.read(count);
if(this.headerFinished) { // reading body
this.body += readData;
// check to see if data is done
this._bodyData();
} else { // reading header
// see if there's a magic double return
var lineBreakIndex = readData.indexOf("\r\n\r\n");
if(lineBreakIndex != -1) {
if(lineBreakIndex != 0) {
this.header += readData.substr(0, lineBreakIndex+4);
this.body = readData.substr(lineBreakIndex+4);
}
this._headerFinished();
return;
}
var lineBreakIndex = readData.indexOf("\n\n");
if(lineBreakIndex != -1) {
if(lineBreakIndex != 0) {
this.header += readData.substr(0, lineBreakIndex+2);
this.body = readData.substr(lineBreakIndex+2);
}
this._headerFinished();
return;
}
if(this.header && this.header[this.header.length-1] == "\n" &&
(readData[0] == "\n" || readData[0] == "\r")) {
if(readData.length > 1 && readData[1] == "\n") {
this.header += readData.substr(0, 2);
this.body = readData.substr(2);
} else {
this.header += readData[0];
this.body = readData.substr(1);
}
this._headerFinished();
return;
}
this.header += readData;
}
}
/*
* processes an HTTP header and decides what to do
*/
Zotero.Server.DataListener.prototype._headerFinished = function() {
this.headerFinished = true;
Zotero.debug(this.header);
const methodRe = /^([A-Z]+) ([^ \r\n?]+)(\?[^ \r\n]+)?/;
const contentTypeRe = /[\r\n]Content-Type: +([^ \r\n]+)/i;
// get first line of request
var method = methodRe.exec(this.header);
// get content-type
var contentType = contentTypeRe.exec(this.header);
if(contentType) {
var splitContentType = contentType[1].split(/\s*;/);
this.contentType = splitContentType[0];
}
if(!method) {
this._requestFinished(Zotero.Server.generateResponse(400));
return;
}
if(!Zotero.Server.Endpoints[method[2]]) {
this._requestFinished(Zotero.Server.generateResponse(404));
return;
}
this.endpoint = Zotero.Server.Endpoints[method[2]];
if(method[1] == "HEAD" || method[1] == "OPTIONS") {
this._requestFinished(Zotero.Server.generateResponse(200));
} else if(method[1] == "GET") {
this._requestFinished(this._processEndpoint("GET", method[3]));
} else if(method[1] == "POST") {
const contentLengthRe = /[\r\n]Content-Length: +([0-9]+)/i;
// parse content length
var m = contentLengthRe.exec(this.header);
if(!m) {
this._requestFinished(Zotero.Server.generateResponse(400));
return;
}
this.bodyLength = parseInt(m[1]);
this._bodyData();
} else {
this._requestFinished(Zotero.Server.generateResponse(501));
return;
}
}
/*
* checks to see if Content-Length bytes of body have been read and, if so, processes the body
*/
Zotero.Server.DataListener.prototype._bodyData = function() {
if(this.body.length >= this.bodyLength) {
// convert to UTF-8
var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
.createInstance(Components.interfaces.nsIStringInputStream);
dataStream.setData(this.body, this.bodyLength);
var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
utf8Stream.init(dataStream, "UTF-8", 4096, "?");
this.body = "";
var string = {};
while(utf8Stream.readString(this.bodyLength, string)) {
this.body += string.value;
}
// handle envelope
this._processEndpoint("POST", this.body);
}
}
/**
* Generates a response based on calling the function associated with the endpoint
*/
Zotero.Server.DataListener.prototype._processEndpoint = function(method, postData) {
try {
var endpoint = new this.endpoint;
// check that endpoint supports method
if(endpoint.supportedMethods.indexOf(method) === -1) {
this._requestFinished(Zotero.Server.generateResponse(400));
return;
}
var decodedData = null;
if(postData && this.contentType) {
// check that endpoint supports contentType
if(endpoint.supportedDataTypes.indexOf(this.contentType) === -1) {
this._requestFinished(Zotero.Server.generateResponse(400));
return;
}
// decode JSON or urlencoded post data, and pass through anything else
if(this.contentType === "application/json") {
decodedData = JSON.parse(postData);
} else if(this.contentType === "application/x-www-urlencoded") {
var splitData = postData.split("&");
decodedData = {};
for each(var variable in splitData) {
var splitIndex = variable.indexOf("=");
data[decodeURIComponent(variable.substr(0, splitIndex))] = decodeURIComponent(variable.substr(splitIndex+1));
}
} else {
decodedData = postData;
}
}
// set up response callback
var me = this;
var sendResponseCallback = function(code, contentType, arg) {
me._requestFinished(Zotero.Server.generateResponse(code, contentType, arg));
}
// pass to endpoint
endpoint.init(decodedData, sendResponseCallback);
} catch(e) {
Zotero.debug(e);
this._requestFinished(Zotero.Server.generateResponse(500));
throw e;
}
}
/*
* returns HTTP data from a request
*/
Zotero.Server.DataListener.prototype._requestFinished = function(response) {
// close input stream
this.iStream.close();
// open UTF-8 converter for output stream
var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Components.interfaces.nsIConverterOutputStream);
// write
try {
intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
// write response
Zotero.debug(response);
intlStream.writeString(response);
} finally {
intlStream.close();
}
}
/**
* Endpoints for the HTTP server
*
* Each endpoint should take the form of an object. The init() method of this object will be passed:
* method - the method of the request ("GET" or "POST")
* data - the query string (for a "GET" request) or POST data (for a "POST" request)
* sendResponseCallback - a function to send a response to the HTTP request. This can be passed
* a response code alone (e.g., sendResponseCallback(404)) or a response
* code, MIME type, and response body
* (e.g., sendResponseCallback(200, "text/plain", "Hello World!"))
*
* See connector/server_connector.js for examples
*/
Zotero.Server.Endpoints = {}

View file

@ -0,0 +1,607 @@
/*
***** 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 <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.Server.Connector = function() {};
Zotero.Server.Connector._waitingForSelection = {};
Zotero.Server.Connector.Data = {};
/**
* Manage cookies in a sandboxed fashion
*
* @param {browser} browser Hidden browser object
* @param {String} uri URI of page to manage cookies for (cookies for domains that are not
* subdomains of this URI are ignored)
* @param {String} cookieData Cookies with which to initiate the sandbox
*/
Zotero.Server.Connector.CookieManager = function(browser, uri, cookieData) {
this._webNav = browser.webNavigation;
this._browser = browser;
this._watchedBrowsers = [browser];
this._observerService = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
this._uri = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newURI(uri, null, null);
var splitCookies = cookieData.split(/; ?/);
this._cookies = {};
for each(var cookie in splitCookies) {
var key = cookie.substr(0, cookie.indexOf("="));
var value = cookie.substr(cookie.indexOf("=")+1);
this._cookies[key] = value;
}
[this._observerService.addObserver(this, topic, false) for each(topic in this._observerTopics)];
}
Zotero.Server.Connector.CookieManager.prototype = {
"_observerTopics":["http-on-examine-response", "http-on-modify-request", "quit-application"],
"_watchedXHRs":[],
/**
* nsIObserver implementation for adding, clearing, and slurping cookies
*/
"observe": function(channel, topic) {
if(topic == "quit-application") {
Zotero.debug("WARNING: A Zotero.Server.CookieManager for "+this._uri.spec+" was still open on shutdown");
} else {
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
var isTracked = null;
try {
var topDoc = channel.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document;
for each(var browser in this._watchedBrowsers) {
isTracked = topDoc == browser.contentDocument;
if(isTracked) break;
}
} catch(e) {}
if(isTracked === null) {
try {
isTracked = channel.loadGroup.notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.document == this._browser.contentDocument;
} catch(e) {}
}
if(isTracked === null) {
try {
isTracked = this._watchedXHRs.indexOf(channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIXMLHttpRequest)) !== -1;
} catch(e) {}
}
// isTracked is now either true, false, or null
// true => we should manage cookies for this request
// false => we should not manage cookies for this request
// null => this request is of a type we couldn't match to this request. one such type
// is a link prefetch (nsPrefetchNode) but there might be others as well. for
// now, we are paranoid and reject these.
if(isTracked === false) {
Zotero.debug("Zotero.Server.CookieManager: not touching channel for "+channel.URI.spec);
return;
} else if(isTracked) {
Zotero.debug("Zotero.Server.CookieManager: managing cookies for "+channel.URI.spec);
} else {
Zotero.debug("Zotero.Server.CookieManager: being paranoid about channel for "+channel.URI.spec);
}
if(topic == "http-on-modify-request") {
// clear cookies to be sent to other domains
if(isTracked === null || channel.URI.host != this._uri.host) {
channel.setRequestHeader("Cookie", "", false);
channel.setRequestHeader("Cookie2", "", false);
Zotero.debug("Zotero.Server.CookieManager: cleared cookies to be sent to "+channel.URI.spec);
return;
}
// add cookies to be sent to this domain
var cookies = [key+"="+this._cookies[key]
for(key in this._cookies)].join("; ");
channel.setRequestHeader("Cookie", cookies, false);
Zotero.debug("Zotero.Server.CookieManager: added cookies for request to "+channel.URI.spec);
} else if(topic == "http-on-examine-response") {
// clear cookies being received
try {
var cookieHeader = channel.getResponseHeader("Set-Cookie");
} catch(e) {
return;
}
channel.setResponseHeader("Set-Cookie", "", false);
channel.setResponseHeader("Set-Cookie2", "", false);
// don't process further if these cookies are for another set of domains
if(isTracked === null || channel.URI.host != this._uri.host) {
Zotero.debug("Zotero.Server.CookieManager: rejected cookies from "+channel.URI.spec);
return;
}
// put new cookies into our sandbox
if(cookieHeader) {
var cookies = cookieHeader.split(/; ?/);
var newCookies = {};
for each(var cookie in cookies) {
var key = cookie.substr(0, cookie.indexOf("="));
var value = cookie.substr(cookie.indexOf("=")+1);
var lcCookie = key.toLowerCase();
if(["comment", "domain", "max-age", "path", "version", "expires"].indexOf(lcCookie) != -1) {
// ignore cookie parameters; we are only holding cookies for a few minutes
// with a single domain, and the path attribute doesn't allow any additional
// security.
// DEBUG: does ignoring the path attribute break any sites?
continue;
} else if(lcCookie == "secure") {
// don't accept secure cookies
newCookies = {};
break;
} else {
newCookies[key] = value;
}
}
[this._cookies[key] = newCookies[key] for(key in newCookies)];
}
Zotero.debug("Zotero.Server.CookieManager: slurped cookies from "+channel.URI.spec);
}
}
},
/**
* Attach CookieManager to a specific XMLHttpRequest
* @param {XMLHttpRequest} xhr
*/
"attachToBrowser": function(browser) {
this._watchedBrowsers.push(browser);
},
/**
* Attach CookieManager to a specific XMLHttpRequest
* @param {XMLHttpRequest} xhr
*/
"attachToXHR": function(xhr) {
this._watchedXHRs.push(xhr);
},
/**
* Destroys this CookieManager (intended to be executed when the browser is destroyed)
*/
"destroy": function() {
[this._observerService.removeObserver(this, topic) for each(topic in this._observerTopics)];
}
}
/**
* Lists all available translators, including code for translators that should be run on every page
*
* Accepts:
* browser - one-letter code of the current browser
* g = Gecko (Firefox)
* c = Google Chrome (WebKit & V8)
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
* i = Internet Explorer
* Returns:
* translators - Zotero.Translator objects
* schema - Some information about the database. Currently includes:
* itemTypes
* name
* localizedString
* creatorTypes
* fields
* baseFields
* creatorTypes
* name
* localizedString
* fields
* name
* localizedString
*/
Zotero.Server.Connector.GetData = function() {};
Zotero.Server.Endpoints["/connector/getData"] = Zotero.Server.Connector.GetData;
Zotero.Server.Connector.GetData.prototype = {
"supportedMethods":["POST"],
"supportedDataTypes":["application/json"],
/**
* Gets available translator list and other important data
* @param {Object} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(data, sendResponseCallback) {
// Translator data
var responseData = {"preferences":{}, "translators":[]};
// TODO only send necessary translators
var translators = Zotero.Translators.getAll();
for each(var translator in translators) {
let serializableTranslator = {};
for each(var key in ["translatorID", "translatorType", "label", "creator", "target",
"priority", "browserSupport"]) {
serializableTranslator[key] = translator[key];
}
// Do not pass targetless translators that do not support this browser (since that
// would mean passing each page back to Zotero)
responseData.translators.push(serializableTranslator);
}
// Various DB data (only sending what is required at the moment)
var systemVersion = Zotero.Schema.getDBVersion("system");
if(systemVersion != data.systemVersion) {
responseData.schema = this._generateTypeSchema();
}
// Preferences
var prefs = Zotero.Prefs.prefBranch.getChildList("", {}, {});
for each(var pref in prefs) {
responseData.preferences[pref] = Zotero.Prefs.get(pref);
}
sendResponseCallback(200, "application/json", JSON.stringify(responseData));
},
/**
* Generates a type schema. This is used by connector/type.js to handle types without DB access.
*/
"_generateTypeSchema":function() {
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
var types = Zotero.ItemTypes.getTypes();
var fieldIDs = Zotero.DB.columnQuery("SELECT fieldID FROM fieldsCombined");
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
for each(var fieldID in fieldIDs) {
var fieldObj = {"name":Zotero.ItemFields.getName(fieldID)};
try {
fieldObj.localizedString = Zotero.getString("itemFields." + fieldObj.name)
} catch(e) {}
schema.fields[fieldID] = fieldObj;
}
// names, localizedStrings, creatorTypes, and fields for each item type
for each(var type in types) {
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
var baseFields = {};
for each(var fieldID in fieldIDs) {
if(baseMappedFields.indexOf(fieldID) !== -1) {
baseFields[fieldID] = Zotero.ItemFields.getFieldIDFromTypeAndBase(type.id, fieldID);
}
}
var icon = Zotero.ItemTypes.getImageSrc(type.name);
icon = icon.substr(icon.lastIndexOf("/")+1);
schema.itemTypes[type.id] = {"name":type.name,
"localizedString":Zotero.ItemTypes.getLocalizedString(type.name),
"creatorTypes":[creatorType.id for each(creatorType in Zotero.CreatorTypes.getTypesForItemType(type.id))],
"fields":fieldIDs, "baseFields":baseFields, "icon":icon};
}
var types = Zotero.CreatorTypes.getTypes();
for each(var type in types) {
schema.creatorTypes[type.id] = {"name":type.name,
"localizedString":Zotero.CreatorTypes.getLocalizedString(type.name)};
}
return schema;
}
}
/**
* Detects whether there is an available translator to handle a given page
*
* Accepts:
* uri - The URI of the page to be saved
* html - document.innerHTML or equivalent
* cookie - document.cookie or equivalent
*
* Returns a list of available translators as an array
*/
Zotero.Server.Connector.Detect = function() {};
Zotero.Server.Endpoints["/connector/detect"] = Zotero.Server.Connector.Detect;
Zotero.Server.Connector.Data = {};
Zotero.Server.Connector.Detect.prototype = {
"supportedMethods":["POST"],
"supportedDataTypes":["application/json"],
/**
* Loads HTML into a hidden browser and initiates translator detection
* @param {Object} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(data, sendResponseCallback) {
this._sendResponse = sendResponseCallback;
this._parsedPostData = data;
this._translate = new Zotero.Translate("web");
this._translate.setHandler("translators", function(obj, item) { me._translatorsAvailable(obj, item) });
Zotero.Server.Connector.Data[this._parsedPostData["uri"]] = "<html>"+this._parsedPostData["html"]+"</html>";
this._browser = Zotero.Browser.createHiddenBrowser();
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var uri = ioService.newURI(this._parsedPostData["uri"], "UTF-8", null);
var pageShowCalled = false;
var me = this;
this._translate.setCookieManager(new Zotero.Server.Connector.CookieManager(this._browser,
this._parsedPostData["uri"], this._parsedPostData["cookie"]));
this._browser.addEventListener("DOMContentLoaded", function() {
try {
if(me._browser.contentDocument.location.href == "about:blank") return;
if(pageShowCalled) return;
pageShowCalled = true;
delete Zotero.Server.Connector.Data[me._parsedPostData["uri"]];
// get translators
me._translate.setDocument(me._browser.contentDocument);
me._translate.getTranslators();
} catch(e) {
Zotero.debug(e);
throw e;
}
}, false);
me._browser.loadURI("zotero://connector/"+encodeURIComponent(this._parsedPostData["uri"]));
},
/**
* Callback to be executed when list of translators becomes available. Sends response with
* item types, translator IDs, labels, and icons for available translators.
* @param {Zotero.Translate} translate
* @param {Zotero.Translator[]} translators
*/
"_translatorsAvailable":function(obj, translators) {
var jsons = [];
for each(var translator in translators) {
if(translator.itemType == "multiple") {
var icon = "treesource-collection.png"
} else {
var icon = Zotero.ItemTypes.getImageSrc(translator.itemType);
icon = icon.substr(icon.lastIndexOf("/")+1);
}
var json = {"itemType":translator.itemType, "translatorID":translator.translatorID,
"label":translator.label, "priority":translator.priority}
jsons.push(json);
}
this._sendResponse(200, "application/json", JSON.stringify(jsons));
this._translate.cookieManager.destroy();
Zotero.Browser.deleteHiddenBrowser(this._browser);
}
}
/**
* Performs translation of a given page
*
* Accepts:
* uri - The URI of the page to be saved
* html - document.innerHTML or equivalent
* cookie - document.cookie or equivalent
*
* Returns:
* If a single item, sends response code 201 with no body.
* If multiple items, sends response code 300 with the following content:
* items - list of items in the format typically passed to the selectItems handler
* instanceID - an ID that must be maintained for the subsequent Zotero.Connector.Select call
* uri - the URI of the page for which multiple items are available
*/
Zotero.Server.Connector.SavePage = function() {};
Zotero.Server.Endpoints["/connector/savePage"] = Zotero.Server.Connector.SavePage;
Zotero.Server.Connector.SavePage.prototype = {
"supportedMethods":["POST"],
"supportedDataTypes":["application/json"],
/**
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
* to the database
* @param {Object} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(data, sendResponseCallback) {
this._sendResponse = sendResponseCallback;
Zotero.Server.Connector.Detect.prototype.init.apply(this, [data, sendResponseCallback])
},
/**
* Callback to be executed when items must be selected
* @param {Zotero.Translate} translate
* @param {Object} itemList ID=>text pairs representing available items
*/
"_selectItems":function(translate, itemList, callback) {
var instanceID = Zotero.randomString();
Zotero.Server.Connector._waitingForSelection[instanceID] = this;
// Fix for translators that don't create item lists as objects
if(itemList.push && typeof itemList.push === "function") {
var newItemList = {};
for(var item in itemList) {
newItemList[item] = itemList[item];
}
itemList = newItemList;
}
// Send "Multiple Choices" HTTP response
this._sendResponse(300, "application/json", JSON.stringify({"selectItems":itemList, "instanceID":instanceID, "uri":this._parsedPostData.uri}));
// We need this to make sure that we won't stop Firefox from quitting, even if the user
// didn't close the selectItems window
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
var me = this;
var quitObserver = {observe:function() { me.selectedItems = false; }};
observerService.addObserver(quitObserver, "quit-application", false);
this.selectedItems = null;
var endTime = Date.now() + 60*60*1000; // after an hour, timeout, so that we don't
// permanently slow Firefox with this loop
while(this.selectedItems === null && Date.now() < endTime) {
Zotero.mainThread.processNextEvent(true);
}
observerService.removeObserver(quitObserver, "quit-application");
callback(this.selectedItems);
},
/**
* Callback to be executed when list of translators becomes available. Opens progress window,
* selects specified translator, and initiates translation.
* @param {Zotero.Translate} translate
* @param {Zotero.Translator[]} translators
*/
"_translatorsAvailable":function(translate, translators) {
// make sure translatorsAvailable succeded
if(!translators.length) {
Zotero.Browser.deleteHiddenBrowser(this._browser);
this._sendResponse(500);
return;
}
// figure out where to save
var libraryID = null;
var collectionID = null;
var zp = Zotero.getActiveZoteroPane();
try {
var libraryID = zp.getSelectedLibraryID();
var collection = zp.getSelectedCollection();
} catch(e) {}
// set handlers for translation
var me = this;
var jsonItems = [];
translate.setHandler("select", function(obj, item, callback) { return me._selectItems(obj, item, callback) });
translate.setHandler("itemDone", function(obj, item, jsonItem) {
if(collection) {
collection.addItem(item.id);
}
jsonItems.push(jsonItem);
});
translate.setHandler("done", function(obj, item) {
me._translate.cookieManager.destroy();
Zotero.Browser.deleteHiddenBrowser(me._browser);
me._sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
});
// set translator and translate
translate.setTranslator(this._parsedPostData.translatorID);
translate.translate(libraryID);
}
}
/**
* Performs translation of a given page, or, alternatively, saves items directly
*
* Accepts:
* items - an array of JSON format items
* Returns:
* 201 response code with empty body
*/
Zotero.Server.Connector.SaveItem = function() {};
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
Zotero.Server.Connector.SaveItem.prototype = {
"supportedMethods":["POST"],
"supportedDataTypes":["application/json"],
/**
* Either loads HTML into a hidden browser and initiates translation, or saves items directly
* to the database
* @param {Object} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(data, sendResponseCallback) {
// figure out where to save
var libraryID = null;
var collectionID = null;
var zp = Zotero.getActiveZoteroPane();
try {
var libraryID = zp.getSelectedLibraryID();
var collection = zp.getSelectedCollection();
} catch(e) {}
// save items
var itemSaver = new Zotero.Translate.ItemSaver(libraryID,
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1);
for each(var item in data.items) {
var savedItem = itemSaver.saveItem(item);
if(collection) collection.addItem(savedItem.id);
}
sendResponseCallback(201);
}
}
/**
* Handle item selection
*
* Accepts:
* selectedItems - a list of items to translate in ID => text format as returned by a selectItems handler
* instanceID - as returned by savePage call
* Returns:
* 201 response code with empty body
*/
Zotero.Server.Connector.SelectItems = function() {};
Zotero.Server.Endpoints["/connector/selectItems"] = Zotero.Server.Connector.SelectItems;
Zotero.Server.Connector.SelectItems.prototype = {
"supportedMethods":["POST"],
"supportedDataTypes":["application/json"],
/**
* Finishes up translation when item selection is complete
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(data, sendResponseCallback) {
var saveInstance = Zotero.Server.Connector._waitingForSelection[data.instanceID];
saveInstance._sendResponse = sendResponseCallback;
saveInstance.selectedItems = false;
for(var i in data.selectedItems) {
saveInstance.selectedItems = data.selectedItems;
break;
}
}
}
/**
* Get code for a translator
*
* Accepts:
* translatorID
* Returns:
* code - translator code
*/
Zotero.Server.Connector.GetTranslatorCode = function() {};
Zotero.Server.Endpoints["/connector/getTranslatorCode"] = Zotero.Server.Connector.GetTranslatorCode;
Zotero.Server.Connector.GetTranslatorCode.prototype = {
"supportedMethods":["POST"],
"supportedDataTypes":["application/json"],
/**
* Finishes up translation when item selection is complete
* @param {String} data POST data or GET query string
* @param {Function} sendResponseCallback function to send HTTP response
*/
"init":function(postData, sendResponseCallback) {
var translator = Zotero.Translators.get(postData.translatorID);
sendResponseCallback(200, "application/javascript", translator.code);
}
}

View file

@ -1,66 +0,0 @@
/*
***** 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 <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
/**
* @class Manages the translator sandbox
* @param {Zotero.Translate} translate
* @param {String|window} sandboxLocation
*/
Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
this.sandbox = {};
this._translate = translate;
}
Zotero.Translate.SandboxManager.prototype = {
/**
* Evaluates code in the sandbox
*/
"eval":function(code) {
// eval in sandbox scope
(new Function("with(this) { " + code + " }")).call(this.sandbox);
},
/**
* Imports an object into the sandbox
*
* @param {Object} object Object to be imported (under Zotero)
* @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed
* as the first argument to the function.
*/
"importObject":function(object, passAsFirstArgument) {
var translate = this._translate;
for(var key in (object.__exposedProps__ ? object.__exposedProps__ : object)) {
var fn = (function(object, key) { return object[key] })();
// magic "this"-preserving wrapping closure
this.sandbox[key] = function() {
var args = (passAsFirstArgument ? [passAsFirstArgument] : []);
for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
fn.apply(object, args);
};
}
}
}

View file

@ -0,0 +1,271 @@
const TLDS = {
"ac":true,
"ad":true,
"ae":true,
"aero":true,
"af":true,
"ag":true,
"ai":true,
"al":true,
"am":true,
"an":true,
"ao":true,
"aq":true,
"ar":true,
"arpa":true,
"as":true,
"asia":true,
"at":true,
"au":true,
"aw":true,
"ax":true,
"az":true,
"ba":true,
"bb":true,
"bd":true,
"be":true,
"bf":true,
"bg":true,
"bh":true,
"bi":true,
"biz":true,
"bj":true,
"bm":true,
"bn":true,
"bo":true,
"br":true,
"bs":true,
"bt":true,
"bv":true,
"bw":true,
"by":true,
"bz":true,
"ca":true,
"cat":true,
"cc":true,
"cd":true,
"cf":true,
"cg":true,
"ch":true,
"ci":true,
"ck":true,
"cl":true,
"cm":true,
"cn":true,
"co":true,
"com":true,
"coop":true,
"cr":true,
"cu":true,
"cv":true,
"cx":true,
"cy":true,
"cz":true,
"de":true,
"dj":true,
"dk":true,
"dm":true,
"do":true,
"dz":true,
"ec":true,
"edu":true,
"ee":true,
"eg":true,
"er":true,
"es":true,
"et":true,
"eu":true,
"fi":true,
"fj":true,
"fk":true,
"fm":true,
"fo":true,
"fr":true,
"ga":true,
"gb":true,
"gd":true,
"ge":true,
"gf":true,
"gg":true,
"gh":true,
"gi":true,
"gl":true,
"gm":true,
"gn":true,
"gov":true,
"gp":true,
"gq":true,
"gr":true,
"gs":true,
"gt":true,
"gu":true,
"gw":true,
"gy":true,
"hk":true,
"hm":true,
"hn":true,
"hr":true,
"ht":true,
"hu":true,
"id":true,
"ie":true,
"il":true,
"im":true,
"in":true,
"info":true,
"int":true,
"io":true,
"iq":true,
"ir":true,
"is":true,
"it":true,
"je":true,
"jm":true,
"jo":true,
"jobs":true,
"jp":true,
"ke":true,
"kg":true,
"kh":true,
"ki":true,
"km":true,
"kn":true,
"kp":true,
"kr":true,
"kw":true,
"ky":true,
"kz":true,
"la":true,
"lb":true,
"lc":true,
"li":true,
"lk":true,
"lr":true,
"ls":true,
"lt":true,
"lu":true,
"lv":true,
"ly":true,
"ma":true,
"mc":true,
"md":true,
"me":true,
"mg":true,
"mh":true,
"mil":true,
"mk":true,
"ml":true,
"mm":true,
"mn":true,
"mo":true,
"mobi":true,
"mp":true,
"mq":true,
"mr":true,
"ms":true,
"mt":true,
"mu":true,
"museum":true,
"mv":true,
"mw":true,
"mx":true,
"my":true,
"mz":true,
"na":true,
"name":true,
"nc":true,
"ne":true,
"net":true,
"nf":true,
"ng":true,
"ni":true,
"nl":true,
"no":true,
"np":true,
"nr":true,
"nu":true,
"nz":true,
"om":true,
"org":true,
"pa":true,
"pe":true,
"pf":true,
"pg":true,
"ph":true,
"pk":true,
"pl":true,
"pm":true,
"pn":true,
"pr":true,
"pro":true,
"ps":true,
"pt":true,
"pw":true,
"py":true,
"qa":true,
"re":true,
"ro":true,
"rs":true,
"ru":true,
"rw":true,
"sa":true,
"sb":true,
"sc":true,
"sd":true,
"se":true,
"sg":true,
"sh":true,
"si":true,
"sj":true,
"sk":true,
"sl":true,
"sm":true,
"sn":true,
"so":true,
"sr":true,
"st":true,
"su":true,
"sv":true,
"sy":true,
"sz":true,
"tc":true,
"td":true,
"tel":true,
"tf":true,
"tg":true,
"th":true,
"tj":true,
"tk":true,
"tl":true,
"tm":true,
"tn":true,
"to":true,
"tp":true,
"tr":true,
"travel":true,
"tt":true,
"tv":true,
"tw":true,
"tz":true,
"ua":true,
"ug":true,
"uk":true,
"us":true,
"uy":true,
"uz":true,
"va":true,
"vc":true,
"ve":true,
"vg":true,
"vi":true,
"vn":true,
"vu":true,
"wf":true,
"ws":true,
"xxx":true,
"ye":true,
"yt":true,
"za":true,
"zm":true,
"zw":true
};

View file

@ -25,14 +25,15 @@
/**
* @class
* Deprecated class for creating new Zotero.Translate instances
* Deprecated class for creating new Zotero.Translate instances<br/>
* <br/>
* New code should use Zotero.Translate.Web, Zotero.Translate.Import, Zotero.Translate.Export, or
* Zotero.Translate.Search
*/
Zotero.Translate = function(type) {
Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to");
// hack
var translate = Zotero.Translate.new(type);
var translate = Zotero.Translate.newInstance(type);
for(var i in translate) {
this[i] = translate[i];
}
@ -43,10 +44,14 @@ Zotero.Translate = function(type) {
/**
* Create a new translator by a string type
*/
Zotero.Translate.new = function(type) {
Zotero.Translate.newInstance = function(type) {
return new Zotero.Translate[type[0].toUpperCase()+type.substr(1).toLowerCase()];
}
/**
* Namespace for Zotero sandboxes
* @namespace
*/
Zotero.Translate.Sandbox = {
/**
* Combines a sandbox with the base sandbox
@ -67,10 +72,11 @@ Zotero.Translate.Sandbox = {
/**
* Base sandbox. These methods are available to all translators.
* @namespace
*/
"Base": {
/**
* Called as Zotero.Item#complete() from translators to save items to the database.
* Called as {@link Zotero.Item#complete} from translators to save items to the database.
* @param {Zotero.Translate} translate
* @param {SandboxItem} An item created using the Zotero.Item class from the sandbox
*/
@ -86,7 +92,7 @@ Zotero.Translate.Sandbox = {
// just return the item array
if(translate._libraryID === false || translate._parentTranslator) {
translate.newItems.push(item);
translate._runHandler("itemDone", item);
translate._runHandler("itemDone", item, item);
return;
}
@ -99,7 +105,8 @@ Zotero.Translate.Sandbox = {
Zotero.wait();
}
translate._runHandler("itemDone", newItem);
// pass both the saved item and the original JS array item
translate._runHandler("itemDone", newItem, item);
},
/**
@ -142,16 +149,19 @@ Zotero.Translate.Sandbox = {
}
Zotero.debug("Translate: creating translate instance of type "+type+" in sandbox");
var translation = Zotero.Translate.new(type);
var translation = Zotero.Translate.newInstance(type);
translation._parentTranslator = translate;
if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) {
throw("Translate: only export translators may call other export translators");
}
// for security reasons, safeTranslator wraps the translator object.
// note that setLocation() is not allowed
var safeTranslator = new Object();
/**
* @class Wrapper for {@link Zotero.Translate} for safely calling another translator
* from inside an existing translator
* @inner
*/
var safeTranslator = {};
safeTranslator.__exposedProps__ = {
"setSearch":"r",
"setDocument":"r",
@ -185,43 +195,61 @@ Zotero.Translate.Sandbox = {
);
};
safeTranslator.setString = function(arg) { translation.setString(arg) };
safeTranslator.setTranslator = function(arg) { return translation.setTranslator(arg) };
safeTranslator.setTranslator = function(arg) {
var success = translation.setTranslator(arg);
if(!success) {
throw "Translator "+translate.translator[0].translatorID+" attempted to call invalid translatorID "+arg;
}
};
safeTranslator.getTranslators = function() { return translation.getTranslators() };
safeTranslator.translate = function() {
setDefaultHandlers(translate, translation);
return translation.translate(false);
};
// TODO
safeTranslator.getTranslatorObject = function(callback) {
translation._loadTranslator(translation.translator[0]);
if(Zotero.isFx) {
// do same origin check
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);
var haveTranslatorFunction = function(translator) {
translation.translator[0] = translator;
if(!Zotero._loadTranslator(translator)) throw "Translator could not be loaded";
var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
translate._sandboxLocation.location : translate._sandboxLocation, null, null);
var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
translation._sandboxLocation.location : translation._sandboxLocation, null, null);
Zotero.debug(outerSandboxURI.spec);
Zotero.debug(innerSandboxURI.spec);
try {
secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
} catch(e) {
throw "Translate: getTranslatorObject() may not be called from web or search "+
"translators to web or search translators from different origins.";
if(Zotero.isFx) {
// do same origin check
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);
var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
translate._sandboxLocation.location : translate._sandboxLocation, null, null);
var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
translation._sandboxLocation.location : translation._sandboxLocation, null, null);
try {
secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
} catch(e) {
throw "Translate: getTranslatorObject() may not be called from web or search "+
"translators to web or search translators from different origins.";
}
}
translation._prepareTranslation();
setDefaultHandlers(translate, translation);
if(callback) callback(translation._sandboxManager.sandbox);
};
if(typeof translation.translator[0] === "object") {
haveTranslatorFunction(translation.translator[0]);
return translation._sandboxManager.sandbox;
} else {
if(Zotero.isConnector && !callback) {
throw "Translate: Translator must accept a callback to getTranslatorObject() to "+
"operate in this translation environment.";
}
Zotero.Translators.get(translation.translator[0], haveTranslatorFunction);
if(!Zotero.isConnector) return translation._sandboxManager.sandbox;
}
translation._prepareTranslation();
setDefaultHandlers(translate, translation);
// return sandbox
if(callback) callback(translation._sandboxManager.sandbox);
return translation._sandboxManager.sandbox;
};
// TODO security is not super-tight here, as someone could pass something into arg
@ -275,22 +303,57 @@ Zotero.Translate.Sandbox = {
/**
* Lets user pick which items s/he wants to put in his/her library
* @param {Zotero.Translate} translate
* @param {Object} options An set of id => name pairs in object format
* @param {Object} items An set of id => name pairs in object format
*/
"selectItems":function(translate, options, callback) {
// hack to see if there are options
var haveOptions = false;
for(var i in options) {
haveOptions = true;
break;
}
if(!haveOptions) {
"selectItems":function(translate, items, callback) {
if(Zotero.Utilities.isEmpty(items)) {
throw "Translate: translator called select items with no items";
}
if(translate._handlers.select) {
options = translate._runHandler("select", options);
if(translate._selectedItems) {
// if we have a set of selected items for this translation, use them
return translate._selectedItems;
} else if(translate._handlers.select) {
var haveAsyncCallback = !!callback;
var haveAsyncHandler = false;
var returnedItems = null;
// if this translator doesn't provide an async callback for selectItems, set things
// up so that we can wait to see if the select handler returns synchronously. If it
// doesn't, we will need to restart translation.
if(!haveAsyncCallback) {
callback = function(selectedItems) {
if(haveAsyncHandler) {
translate.translate(this._libraryID, this._saveAttachments, selectedItems);
} else {
returnedItems = selectedItems;
}
};
}
translate._runHandler("select", items, callback);
if(!haveAsyncCallback) {
if(translate.translator[0].browserSupport !== "g") {
Zotero.debug("Translate: WARNING: This translator is configured for "+
"non-Firefox browser support, but no callback was provided for "+
"selectItems(). When executed outside of Firefox, a selectItems() call "+
"will require that this translator to be called multiple times.", 3);
}
if(returnedItems === null) {
// The select handler is asynchronous, but this translator doesn't support
// asynchronous select. We return false to abort translation in this
// instance, and we will restart it later when the selectItems call is
// complete.
haveAsyncHandler = true;
return false;
} else {
return returnedItems;
}
}
} else { // no handler defined; assume they want all of them
return options;
}
if(callback) callback(options);
@ -378,7 +441,7 @@ Zotero.Translate.Sandbox = {
"Import":{
/**
* Saves a collection to the DB
* Called as Zotero.Collection#complete() from the sandbox
* Called as {@link Zotero.Collection#complete} from the sandbox
* @param {Zotero.Translate} translate
* @param {SandboxCollection} collection
*/
@ -520,7 +583,7 @@ Zotero.Translate.Base.prototype = {
throw("No translatorID specified");
}
} else {
this.translator = [Zotero.Translators.get(translator)];
this.translator = [translator];
}
return !!this.translator;
@ -591,17 +654,25 @@ Zotero.Translate.Base.prototype = {
* @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values
* @param {Any} argument Argument to be passed to handler
*/
"_runHandler":function(type, argument) {
"_runHandler":function(type) {
var returnValue = undefined;
if(this._handlers[type]) {
// compile list of arguments
if(this._parentTranslator) {
// if there is a parent translator, make sure we don't the Zotero.Translate
// object, since it could open a security hole
var args = [null];
} else {
var args = [this];
}
for(var i=1; i<arguments.length; i++) {
args.push(arguments[i]);
}
for(var i in this._handlers[type]) {
Zotero.debug("Translate: running handler "+i+" for "+type, 5);
try {
if(this._parentTranslator) {
returnValue = this._handlers[type][i](null, argument);
} else {
returnValue = this._handlers[type][i](this, argument);
}
returnValue = this._handlers[type][i].apply(null, args);
} catch(e) {
if(this._parentTranslator) {
// throw handler errors if they occur when a translator is
@ -635,16 +706,73 @@ Zotero.Translate.Base.prototype = {
if(this._currentState == "detect") throw "Translate: getTranslators: detection is already running";
this._currentState = "detect";
this._getAllTranslators = getAllTranslators;
this._potentialTranslators = this._getPotentialTranslators();
this._foundTranslators = [];
Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3);
this._detect();
this._getTranslatorsGetPotentialTranslators();
// if detection returns immediately, return found translators
if(!this._currentState) return this._foundTranslators;
},
/**
* Get all potential translators
* @return {Zotero.Translator[]}
*/
"_getTranslatorsGetPotentialTranslators":function() {
var me = this;
Zotero.Translators.getAllForType(this.type,
function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
},
/**
* Called on completion of {@link #_getTranslatorsGetPotentialTranslators} call
*/
"_getTranslatorsTranslatorsReceived":function(allPotentialTranslators, properToProxyFunctions) {
this._potentialTranslators = [];
this._foundTranslators = [];
// this gets passed out by Zotero.Translators.getWebTranslatorsForLocation() because it is
// specific for each translator, but we want to avoid making a copy of a translator whenever
// possible.
this._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null;
this._waitingForRPC = false;
for(var i in allPotentialTranslators) {
var translator = allPotentialTranslators[i];
if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
this._potentialTranslators.push(translator);
} else {
this._waitingForRPC = true;
}
}
// TODO maybe this should only be in the web translator
if(this._waitingForRPC) {
var me = this;
Zotero.Connector.callMethod("detect", {"uri":this.location.toString(),
"cookie":this.document.cookie,
"html":this.document.documentElement.innerHTML},
function(returnValue) { me._getTranslatorsRPCComplete(returnValue) });
}
this._detect();
},
/**
* Called on completion of detect RPC for
* {@link Zotero.Translate.Base#_getTranslatorsTranslatorsReceived}
*/
"_getTranslatorsRPCComplete":function(rpcTranslators) {
this._waitingForRPC = false;
// if there are translators, add them to the list of found translators
if(rpcTranslators) {
this._foundTranslators = this._foundTranslators.concat(rpcTranslators);
}
// call _detectTranslatorsCollected to return detected translators
if(this._currentState === null) {
this._detectTranslatorsCollected();
}
},
/**
* Begins the actual translation. At present, this returns immediately for import/export
@ -656,14 +784,34 @@ Zotero.Translate.Base.prototype = {
* if FALSE, don't save items
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
*/
"translate":function(libraryID, saveAttachments) {
// initialize properties specific to each translation
"translate":function(libraryID, saveAttachments) { // initialize properties specific to each translation
this._currentState = "translate";
if(!this.translator || !this.translator.length) {
throw("Translate: Failed: no translator specified");
}
this._libraryID = libraryID;
this._saveAttachments = saveAttachments === undefined || saveAttachments;
if(typeof this.translator[0] === "object") {
// already have a translator object, so use it
this._translateHaveTranslator();
} else {
// need to get translator first
var me = this;
Zotero.Translators.get(this.translator[0],
function(translator) {
me.translator[0] = translator;
me._translateHaveTranslator();
});
}
},
/**
* Called when translator has been retrieved
*/
"_translateHaveTranslator":function() {
// load translators
if(!this._loadTranslator(this.translator[0])) return;
@ -671,15 +819,13 @@ Zotero.Translate.Base.prototype = {
if(!this._displayOptions) this._displayOptions = this.translator[0].displayOptions;
// prepare translation
this._libraryID = libraryID;
this._saveAttachments = typeof saveAttachments === "undefined" ? true : saveAttachments;
this._prepareTranslation();
Zotero.debug("Translate: Beginning translation with "+this.translator[0].label);
// translate
try {
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(this.null, this._getParameters());
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(null, this._getParameters());
} catch(e) {
if(this._parentTranslator) {
throw(e);
@ -715,9 +861,10 @@ Zotero.Translate.Base.prototype = {
if(oldState === "detect") {
if(this._potentialTranslators.length) {
var lastTranslator = this._potentialTranslators.shift();
var lastProperToProxyFunction = this._properToProxyFunctions ? this._properToProxyFunctions.shift() : null;
if(returnValue) {
var dupeTranslator = {"itemType":returnValue};
var dupeTranslator = {"itemType":returnValue, "properToProxy":lastProperToProxyFunction};
for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i];
this._foundTranslators.push(dupeTranslator);
} else if(error) {
@ -730,7 +877,7 @@ Zotero.Translate.Base.prototype = {
this._detect();
} else {
this._currentState = null;
this._runHandler("translators", this._foundTranslators ? this._foundTranslators : false);
if(!this._waitingForRPC) this._detectTranslatorsCollected();
}
} else {
this._currentState = null;
@ -762,6 +909,12 @@ Zotero.Translate.Base.prototype = {
* Runs detect code for a translator
*/
"_detect":function() {
// there won't be any translators if we need an RPC call
if(!this._potentialTranslators.length) {
this.complete(true);
return;
}
if(!this._loadTranslator(this._potentialTranslators[0])) {
this.complete(false, "Error loading translator into sandbox");
return;
@ -778,6 +931,15 @@ Zotero.Translate.Base.prototype = {
if(!this._waitForCompletion) this.complete(returnValue);
},
/**
* Called when all translators have been collected for detection
*/
"_detectTranslatorsCollected":function() {
Zotero.debug("Translate: All translator detect calls and RPC calls complete");
this._foundTranslators.sort(function(a, b) { return a.priority-b.priority });
this._runHandler("translators", this._foundTranslators);
},
/**
* Loads the translator into its sandbox
* @param {Zotero.Translator} translator
@ -795,7 +957,6 @@ Zotero.Translate.Base.prototype = {
try {
this._sandboxManager.eval("var translatorInfo = "+translator.code, this._sandbox);
return true;
} catch(e) {
if(translator.logError) {
translator.logError(e.toString());
@ -815,13 +976,14 @@ Zotero.Translate.Base.prototype = {
*/
"_generateSandbox":function() {
Zotero.debug("Translate: Binding sandbox to "+(typeof this._sandboxLocation == "object" ? this._sandboxLocation.document.location : this._sandboxLocation), 4);
this._sandboxManager = new Zotero.Translate.SandboxManager(this, this._sandboxLocation);
this._sandboxManager = new Zotero.Translate.SandboxManager(this._sandboxLocation);
const createArrays = "['creators', 'notes', 'tags', 'seeAlso', 'attachments']";
var src = "var Zotero = {};"+
"Zotero.Item = function (itemType) {"+
"const createArrays = "+createArrays+";"+
"this.itemType = itemType;"+
"for each(var array in "+createArrays+") {"+
"this[array] = [];"+
"for(var i in createArrays) {"+
"this[createArrays[i]] = [];"+
"}"+
"};"+
"Zotero.Collection = function () {};"+
@ -927,17 +1089,11 @@ Zotero.Translate.Base.prototype = {
* No-op for preparing translation
*/
"_prepareTranslation":function() {},
/**
* Get all potential translators
* @return {Zotero.Translator[]}
*/
"_getPotentialTranslators":function() {
return Zotero.Translators.getAllForType(this.type);
}
}
/**
* @class Web translation
*
* @property {Document} document The document object to be used for web scraping (set with setDocument)
* @property {Zotero.Connector.CookieManager} cookieManager A CookieManager to manage cookies for
* this Translate instance.
@ -975,30 +1131,21 @@ Zotero.Translate.Web.prototype.setCookieManager = function(cookieManager) {
* @param {String} location The URL of the page to translate
*/
Zotero.Translate.Web.prototype.setLocation = function(location) {
// account for proxies
this.location = Zotero.Proxies.proxyToProper(location);
if(this.location != location) {
// figure out if this URL is being proxies
this.locationIsProxied = true;
}
this.location = location;
this.path = this.location;
}
/**
* Get all potential translators
* Get potential web translators
*/
Zotero.Translate.Web.prototype._getPotentialTranslators = function() {
var allTranslators = Zotero.Translators.getAllForType("web");
var potentialTranslators = [];
Zotero.debug("Translate: Running regular expressions");
for(var i=0; i<allTranslators.length; i++) {
if(!allTranslators[i].webRegexp || (this.location.length < 8192 && allTranslators[i].webRegexp.test(this.location))) {
potentialTranslators.push(allTranslators[i]);
}
}
return potentialTranslators;
Zotero.Translate.Web.prototype._getTranslatorsGetPotentialTranslators = function() {
var me = this;
Zotero.Translators.getWebTranslatorsForLocation(this.location,
function(data) {
// data[0] = list of translators
// data[1] = list of functions to convert proper URIs to proxied URIs
me._getTranslatorsTranslatorsReceived(data[0], data[1]);
});
}
/**
@ -1023,14 +1170,67 @@ Zotero.Translate.Web.prototype._prepareTranslation = function() {
}
/**
* Overload detect to test regexp first
* Overload translate to set selectedItems
*/
Zotero.Translate.Web.prototype.translate = function(libraryID, saveAttachments, selectedItems) {
this._selectedItems = selectedItems;
Zotero.Translate.Base.prototype.translate.apply(this, libraryID, saveAttachments);
}
/**
* Overload _translateHaveTranslator to send an RPC call if necessary
*/
Zotero.Translate.Web.prototype._translateHaveTranslator = function() {
if(this.translator[0].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
// begin process to run translator in browser
Zotero.Translate.Base.prototype._translateHaveTranslator.apply(this);
} else {
// otherwise, ferry translator load to RPC
var me = this;
Zotero.Connector.callMethod("savePage", {
"uri":this.location.toString(),
"translatorID":(typeof this.translator[0] === "object"
? this.translator[0].translatorID : this.translator[0]),
"cookie":this.document.cookie,
"html":this.document.documentElement.innerHTML
}, function(obj) { me._translateRPCComplete(obj) });
}
}
/**
* Called when an RPC call for remote translation completes
*/
Zotero.Translate.Web.prototype._translateRPCComplete = function(obj, failureCode) {
if(!obj) this.complete(false, failureCode);
if(obj.selectItems) {
// if we have to select items, call the selectItems handler and do it
var me = this;
var items = this._runHandler("select", obj.selectItems,
function(selectedItems) {
Zotero.Connector.callMethod("selectItems",
{"instanceID":obj.instanceID, "selectedItems":selectedItems},
function(obj) { me._translateRPCComplete(obj) })
}
);
} else {
// if we don't have to select items, continue
for(var i in obj.items) {
this._runHandler("itemDone", null, obj.items[i]);
}
this.complete(true);
}
}
/**
* Overload complete to report translation failure
*/
Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
// call super
var oldState = this._currentState;
var errorString = Zotero.Translate.Base.prototype.complete.apply(this, [returnValue, error]);
// Report translaton failure if we failed
// Report translation failure if we failed
if(oldState == "translate" && errorString && this.translator[0].inRepository && Zotero.Prefs.get("reportTranslationFailure")) {
// Don't report failure if in private browsing mode
if(Zotero.isFx && !Zotero.isStandalone) {
@ -1049,6 +1249,9 @@ Zotero.Translate.Web.prototype.complete = function(returnValue, error) {
}
}
/**
* @class Import translation
*/
Zotero.Translate.Import = function() {
this.init();
}
@ -1081,35 +1284,26 @@ Zotero.Translate.Import.prototype.complete = function(returnValue, error) {
}
/**
* Get all potential translators, ordering translators with the right file extension first
* Get all potential import translators, ordering translators with the right file extension first
*/
Zotero.Translate.Import.prototype._getPotentialTranslators = function() {
var allTranslators = Zotero.Translators.getAllForType("import");
var tier1Translators = [];
var tier2Translators = [];
for(var i=0; i<allTranslators.length; i++) {
if(allTranslators[i].importRegexp.test(this.location)) {
tier1Translators.push(allTranslators[i]);
} else {
tier2Translators.push(allTranslators[i]);
}
}
return tier1Translators.concat(tier2Translators);
Zotero.Translate.Import.prototype._getTranslatorsGetPotentialTranslators = function() {
var me = this;
Zotero.Translators.getImportTranslatorsForLocation(this.location,
function(translators) { me._getTranslatorsTranslatorsReceived(translators) });
}
/**
* Overload Zotero.Translate.Base#_detect to return all translators immediately only if no string
* or location is set
* Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately only
* if no string or location is set
*/
Zotero.Translate.Import.prototype._detect = function() {
Zotero.Translate.Import.prototype.getTranslators = function() {
if(!this._string && !this.location) {
this._foundTranslators = this._potentialTranslators;
this._foundTranslators = Zotero.Translators.getAllForType(this.type);
this._potentialTranslators = [];
this.complete(true);
return this._foundTranslators;
} else {
Zotero.Translate.Base.prototype._detect.call(this);
Zotero.Translate.Base.prototype.getTranslators.call(this);
}
}
@ -1156,7 +1350,7 @@ Zotero.Translate.Import.prototype._loadTranslator = function(translator) {
this._sandboxManager.importObject(this._io);
return true;
},
}
/**
* Prepare translation
@ -1182,6 +1376,9 @@ function() {
});
/**
* @class Export translation
*/
Zotero.Translate.Export = function() {
this.init();
}
@ -1235,12 +1432,13 @@ Zotero.Translate.Export.prototype.setDisplayOptions = function(displayOptions) {
Zotero.Translate.Export.prototype.complete = Zotero.Translate.Import.prototype.complete;
/**
* Overload Zotero.Translate.Base#_detect to return all translators immediately
* Overload {@link Zotero.Translate.Base#getTranslators} to return all translators immediately
*/
Zotero.Translate.Export.prototype._detect = function() {
this._foundTranslators = this._potentialTranslators;
Zotero.Translate.Export.prototype.getTranslators = function() {
this._foundTranslators = Zotero.Translators.getAllForType(this.type);
this._potentialTranslators = [];
this.complete(true);
return this._foundTranslators;
}
/**
@ -1295,6 +1493,7 @@ function() {
});
/**
* @class Search translation
* @property {Array[]} search Item (in {@link Zotero.Item#serialize} format) to extrapolate data
* (set with setSearch)
*/
@ -1307,7 +1506,7 @@ Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search";
Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search);
/**
* @borrows Zotero.Translate.Web#setCookieManager as Zotero.Translate.Search#setCookieManager
* @borrows Zotero.Translate.Web#setCookieManager
*/
Zotero.Translate.Search.prototype.setCookieManager = Zotero.Translate.Web.prototype.setCookieManager;
@ -1339,11 +1538,7 @@ Zotero.Translate.Search.prototype.setTranslator = function(translator) {
// accept a list of objects
this.translator = [];
for(var i in translator) {
if(typeof(translator[i]) == "object") {
this.translator.push(translator[i]);
} else {
this.translator.push(Zotero.Translators.get(translator[i]));
}
this.translator.push(translator[i]);
}
return true;
} else {
@ -1436,6 +1631,9 @@ Zotero.Translate.IO = {
/******* String support *******/
/**
* @class Translate backend for translating from a string
*/
Zotero.Translate.IO.String = function(string, uri, mode) {
if(string && typeof string === "string") {
this._string = string;

View file

@ -38,10 +38,9 @@ Components.utils.import("resource://gre/modules/NetUtil.jsm");
* @param {Zotero.Translate} translate
* @param {String|window} sandboxLocation
*/
Zotero.Translate.SandboxManager = function(translate, sandboxLocation) {
Zotero.Translate.SandboxManager = function(sandboxLocation) {
this.sandbox = new Components.utils.Sandbox(sandboxLocation);
this.sandbox.Zotero = {};
this._translate = translate;
// import functions missing from global scope into Fx sandbox
this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
@ -111,6 +110,7 @@ Zotero.Translate.SandboxManager.prototype = {
return object[localKey].apply(object, args);
};
attachTo[localKey].name = localKey;
// attach members
if(!(object instanceof Components.interfaces.nsISupports)) {

View file

@ -149,18 +149,153 @@ Zotero.Translators = new function() {
/**
* Gets the translator that corresponds to a given ID
* @param {String} id The ID of the translator
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned.
*/
this.get = function(id) {
this.get = function(id, callback) {
if(!_initialized) this.init();
return _translators[id] ? _translators[id] : false;
var translator = _translators[id] ? _translators[id] : false;
if(callback) {
callback(translator);
return true;
}
return translator;
}
/**
* Gets all translators for a specific type of translation
* @param {String} type The type of translators to get (import, export, web, or search)
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned.
*/
this.getAllForType = function(type, callback) {
if(!_initialized) this.init()
var translators = _cache[type].slice(0);
if(callback) {
callback(translators);
return true;
}
return translators;
}
/**
* Gets all translators for a specific type of translation
*/
this.getAllForType = function(type) {
this.getAll = function() {
if(!_initialized) this.init();
return _cache[type].slice(0);
return [translator for each(translator in _translators)];
}
/**
* Gets web translators for a specific location
* @param {String} uri The URI for which to look for translators
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned. The callback is passed a set of functions for
* converting URLs from proper to proxied forms as the second
* argument.
*/
this.getWebTranslatorsForLocation = function(uri, callback) {
var allTranslators = this.getAllForType("web");
var potentialTranslators = [];
var properHosts = [];
var proxyHosts = [];
var properURI = Zotero.Proxies.proxyToProper(uri);
var knownProxy = properURI !== uri;
if(knownProxy) {
// if we know this proxy, just use the proper URI for detection
var searchURIs = [properURI];
} else {
var searchURIs = [uri];
// if there is a subdomain that is also a TLD, also test against URI with the domain
// dropped after the TLD
// (i.e., www.nature.com.mutex.gmu.edu => www.nature.com)
var m = /^(https?:\/\/)([^\/]+)/i.exec(uri);
if(m) {
var hostnames = m[2].split(".");
for(var i=1; i<hostnames.length-2; i++) {
if(TLDS[hostnames[i].toLowerCase()]) {
var properHost = hostnames.slice(0, i+1).join(".");
searchURIs.push(m[1]+properHost+uri.substr(m[0].length));
properHosts.push(properHost);
proxyHosts.push(hostnames.slice(i+1).join("."));
}
}
}
}
Zotero.debug("Translators: Looking for translators for "+searchURIs.join(", "));
var converterFunctions = [];
for(var i=0; i<allTranslators.length; i++) {
for(var j=0; j<searchURIs.length; j++) {
if((!allTranslators[i].webRegexp
&& allTranslators[i].runMode === Zotero.Translator.RUN_MODE_IN_BROWSER)
|| (uri.length < 8192 && allTranslators[i].webRegexp.test(searchURIs[j]))) {
// add translator to list
potentialTranslators.push(allTranslators[i]);
if(j === 0) {
if(knownProxy) {
converterFunctions.push(Zotero.Proxies.properToProxy);
} else {
converterFunctions.push(null);
}
} else {
converterFunctions.push(new function() {
var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi");
var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$");
return function(uri) { return uri.replace(re, "$&."+proxyHost) };
});
}
// don't add translator more than once
break;
}
}
}
if(callback) {
callback([potentialTranslators, converterFunctions]);
return true;
}
return potentialTranslators;
}
/**
* Gets import translators for a specific location
* @param {String} location The location for which to look for translators
* @param {Function} [callback] An optional callback to be executed when translators have been
* retrieved. If no callback is specified, translators are
* returned.
*/
this.getImportTranslatorsForLocation = function(location, callback) {
var allTranslators = Zotero.Translators.getAllForType("import");
var tier1Translators = [];
var tier2Translators = [];
for(var i=0; i<allTranslators.length; i++) {
if(allTranslators[i].importRegexp.test(location)) {
tier1Translators.push(allTranslators[i]);
} else {
tier2Translators.push(allTranslators[i]);
}
}
var translators = tier1Translators.concat(tier2Translators);
if(callback) {
callback(translators);
return true;
}
return translators;
}
/**
@ -171,7 +306,6 @@ Zotero.Translators = new function() {
return Zotero.File.getValidFileName(label) + ".js";
}
/**
* @param {String} metadata
* @param {String} metadata.translatorID Translator GUID
@ -262,29 +396,6 @@ Zotero.Translators = new function() {
}
}
/**
* @class Represents an individual translator
* @constructor
* @param {nsIFile} file File from which to generate a translator object
* @property {String} translatorID Unique GUID of the translator
* @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
* @property {String} label Human-readable name of the translator
* @property {String} creator Author(s) of the translator
* @property {String} target Location that the translator processes
* @property {String} minVersion Minimum Zotero version
* @property {String} maxVersion Minimum Zotero version
* @property {Integer} priority Lower-priority translators will be selected first
* @property {String} browserSupport String indicating browser supported by the translator
* g = Gecko (Firefox)
* c = Google Chrome (WebKit & V8)
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
* i = Internet Explorer
* @property {Object} configOptions Configuration options for import/export
* @property {Object} displayOptions Display options for export
* @property {Boolean} inRepository Whether the translator may be found in the repository
* @property {String} lastUpdated SQL-style date and time of translator's last update
* @property {String} code The executable JavaScript for the translator
*/
Zotero.Translator = function(file, json, code) {
const codeGetterFunction = function() { return Zotero.File.getContents(this.file); }
// Maximum length for the info JSON in a translator
@ -355,6 +466,7 @@ Zotero.Translator = function(file, json, code) {
this._configOptions = info["configOptions"] ? info["configOptions"] : {};
this._displayOptions = info["displayOptions"] ? info["displayOptions"] : {};
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
// compile import regexp to match only file extension
@ -374,7 +486,6 @@ Zotero.Translator = function(file, json, code) {
try {
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
} catch(e) {
if(fStream) fStream.close();
this.logError("Invalid target in " + file.leafName);
this.webRegexp = null;
if(fStream) fStream.close();
@ -420,4 +531,8 @@ Zotero.Translator.prototype.logError = function(message, type, line, lineNumber,
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec);
}
}
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;

View file

@ -595,6 +595,17 @@ Zotero.Utilities = {
} catch(e) {
return false;
}
},
/**
* Escapes metacharacters in a literal so that it may be used in a regular expression
*/
"quotemeta":function(literal) {
if(typeof literal !== "string") {
throw "Argument "+literal+" must be a string in Zotero.Utilities.quotemeta()";
}
const metaRegexp = /[-[\]{}()*+?.\\^$|,#\s]/g;
return literal.replace(metaRegexp, "\\$&");
}
}
@ -746,13 +757,11 @@ Zotero.Utilities.Translate.prototype.loadDocument = function(url, succeeded, fai
* @ignore
*/
Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor, done, exception) {
if(this._translate.locationIsProxied) {
if(typeof(urls) == "string") {
urls = [this._convertURL(urls)];
} else {
for(var i in urls) {
urls[i] = this._convertURL(urls[i]);
}
if(typeof(urls) == "string") {
urls = [this._convertURL(urls)];
} else {
for(var i in urls) {
urls[i] = this._convertURL(urls[i]);
}
}
@ -776,7 +785,7 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor
* @return {Document} DOM document object
*/
Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) {
if(this._translate.locationIsProxied) url = this._convertURL(url);
url = this._convertURL(url);
var mainThread = Zotero.mainThread;
var loaded = false;
@ -822,7 +831,7 @@ Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, header
/* Apparently, a synchronous XMLHttpRequest would have the behavior of this routine in FF3, but
* in FF3.5, synchronous XHR blocks all JavaScript on the thread. See
* http://hacks.mozilla.org/2009/07/synchronous-xhr/. */
if(this._translate.locationIsProxied) url = this._convertURL(url);
url = this._convertURL(url);
if(!headers) headers = null;
if(!responseCharset) responseCharset = null;
@ -911,15 +920,25 @@ Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, header
*/
Zotero.Utilities.Translate.prototype._convertURL = function(url) {
const protocolRe = /^(?:(?:http|https|ftp):)/i;
const fileRe = /^[^:]*/;
if(this._translate.locationIsProxied) {
url = Zotero.Proxies.properToProxy(url);
// convert proxy to proper if applicable
if(this._translate.translator && this._translate.translator[0]
&& this._translate.translator[0].properToProxy) {
url = this._translate.translator[0].properToProxy(url);
}
if(protocolRe.test(url)) return url;
if(!fileRe.test(url)) {
throw "Invalid URL supplied for HTTP request";
if(Zotero.isChrome || Zotero.isSafari) {
// this code is sandboxed, so we don't worry
return url;
} else {
if(protocolRe.test(url)) return url;
if(uri.indexOf(":") !== -1) {
// don't allow protocol switches
throw "Invalid URL supplied for HTTP request";
}
// resolve relative URIs
return Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService).
newURI(this._translate.location, "", null).resolve(url);

View file

@ -36,6 +36,8 @@ const ZOTERO_CONFIG = {
PREF_BRANCH: 'extensions.zotero.'
};
const ZOTERO_METAREGEXP = /[-[\]{}()*+?.\\^$|,#\s]/g;
// Fx4.0b8+ use implicit SJOWs and get rid of explicit XPCSafeJSObjectWrapper constructor
// Ugly hack to get around this until we can just kill the XPCSafeJSObjectWrapper calls (when we
// drop Fx3.6 support)
@ -45,10 +47,17 @@ try {
eval("var XPCSafeJSObjectWrapper = function(arg) { return arg }");
}
// Load AddonManager for Firefox 4
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
getService(Components.interfaces.nsIXULAppInfo);
if(appInfo.platformVersion[0] >= 2) {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
}
/*
* Core functions
*/
var Zotero = new function(){
(function(){
// Privileged (public) methods
this.init = init;
this.stateCheck = stateCheck;
@ -173,34 +182,39 @@ var Zotero = new function(){
var _locked;
var _unlockCallbacks = [];
var _shutdownListeners = [];
var _progressMeters;
var _lastPercentage;
// whether we are waiting for another Zotero process to release its DB lock
var _waitingForDBLock = false;
// whether we are waiting for another Zotero process to initialize so we can use connector
var _waitingForInitComplete = false;
// whether we should broadcast an initComplete message when initialization finishes (we should
// do this if we forced another Zotero process to release its lock)
var _broadcastInitComplete = false;
/**
* A set of nsITimerCallbacks to be executed when Zotero.wait() completes
*/
var _waitTimerCallbacks = [];
/*
/**
* Initialize the extension
*/
function init(){
function init() {
if (this.initialized || this.skipLoading) {
return false;
}
var start = (new Date()).getTime()
var start = (new Date()).getTime();
// Register shutdown handler to call Zotero.shutdown()
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver({
observe: Zotero.shutdown
}, "quit-application", false);
// Load in the preferences branch for the extension
Zotero.Prefs.init();
Zotero.Debug.init();
this.mainThread = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
@ -214,6 +228,7 @@ var Zotero = new function(){
this.isFx31 = this.isFx35;
this.isFx36 = appInfo.platformVersion.indexOf('1.9.2') === 0;
this.isFx4 = appInfo.platformVersion[0] >= 2;
this.isFx5 = appInfo.platformVersion[0] >= 5;
this.isStandalone = appInfo.ID == ZOTERO_CONFIG['GUID'];
if(this.isStandalone) {
@ -242,6 +257,9 @@ var Zotero = new function(){
this.isLinux = (this.platform.substr(0, 5) == "Linux");
this.oscpu = win.navigator.oscpu;
// Browser
Zotero.browser = "g";
// Locale
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
@ -275,19 +293,19 @@ var Zotero = new function(){
xmlhttp.send(null);
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
if (matches && matches[0] == 'rtl') {
this.dir = 'rtl';
Zotero.dir = 'rtl';
}
else {
this.dir = 'ltr';
Zotero.dir = 'ltr';
}
try {
var dataDir = this.getZoteroDirectory();
var dataDir = Zotero.getZoteroDirectory();
}
catch (e) {
// Zotero dir not found
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
this.startupError = Zotero.getString('dataDir.notFound');
Zotero.startupError = Zotero.getString('dataDir.notFound');
_startupErrorHandler = function() {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
@ -300,7 +318,7 @@ var Zotero = new function(){
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
var index = ps.confirmEx(win,
Zotero.getString('general.error'),
this.startupError + '\n\n' +
Zotero.startupError + '\n\n' +
Zotero.getString('dataDir.previousDir') + ' '
+ Zotero.Prefs.get('lastDataDir'),
buttonFlags, null,
@ -382,7 +400,6 @@ var Zotero = new function(){
else if (index == 2) {
Zotero.chooseZoteroDirectory(true);
}
var dataDir = this.getZoteroDirectory();
}
// DEBUG: handle more startup errors
else {
@ -391,6 +408,54 @@ var Zotero = new function(){
}
}
Zotero.IPC.init();
// Load additional info for connector or not
if(Zotero.isConnector) {
Zotero.debug("Loading in connector mode");
Zotero.Connector.init();
} else {
Zotero.debug("Loading in full mode");
_initFull();
}
this.initialized = true;
// Register shutdown handler to call Zotero.shutdown()
var _shutdownObserver = {observe:Zotero.shutdown};
observerService.addObserver(_shutdownObserver, "quit-application", false);
// Add shutdown listerner to remove observer
this.addShutdownListener(function() {
observerService.removeObserver(_shutdownObserver, "quit-application", false);
});
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
if(!Zotero.isFirstLoadThisSession) {
if(Zotero.isConnector) {
// wait for initComplete message if we switched to connector because standalone was
// started
_waitingForInitComplete = true;
while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true);
}
// trigger zotero-reloaded event
Zotero.debug('Triggering "zotero-reloaded" event');
observerService.notifyObservers(Zotero, "zotero-reloaded", null);
}
// Broadcast initComplete message if desired
if(_broadcastInitComplete) Zotero.IPC.broadcast("initComplete");
return true;
}
/**
* Initialization function to be called only if Zotero is in full mode
*/
function _initFull() {
var dataDir = Zotero.getZoteroDirectory();
Zotero.VersionHeader.init();
// Check for DB restore
@ -412,7 +477,7 @@ var Zotero = new function(){
Zotero.Schema.skipDefaultData = true;
Zotero.Schema.updateSchema();
this.restoreFromServer = true;
Zotero.restoreFromServer = true;
}
catch (e) {
// Restore from backup?
@ -420,54 +485,7 @@ var Zotero = new function(){
}
}
try {
// Test read access
Zotero.DB.test();
var dbfile = Zotero.getZoteroDatabase();
// Test write access on Zotero data directory
if (!dbfile.parent.isWritable()) {
var msg = 'Cannot write to ' + dbfile.parent.path + '/';
}
// Test write access on Zotero database
else if (!dbfile.isWritable()) {
var msg = 'Cannot write to ' + dbfile.path;
}
else {
var msg = false;
}
if (msg) {
var e = {
name: 'NS_ERROR_FILE_ACCESS_DENIED',
message: msg,
toString: function () {
return this.name + ': ' + this.message;
}
};
throw (e);
}
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseCannotBeOpened'),
Zotero.getString('startupError.checkPermissions')
]);
this.startupError = msg;
} else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseInUse'),
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
]);
this.startupError = msg;
}
Components.utils.reportError(e);
this.skipLoading = true;
return;
}
if(!_initDB()) return;
// Add notifier queue callbacks to the DB layer
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
@ -477,7 +495,7 @@ var Zotero = new function(){
Zotero.Fulltext.init();
// Require >=2.1b3 database to ensure proper locking
if (this.isStandalone && Zotero.Schema.getDBVersion('system') > 0 && Zotero.Schema.getDBVersion('system') < 31) {
if (Zotero.isStandalone && Zotero.Schema.getDBVersion('system') > 0 && Zotero.Schema.getDBVersion('system') < 31) {
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
.getService(Components.interfaces.nsIAppStartup);
@ -541,7 +559,7 @@ var Zotero = new function(){
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
}
this.skipLoading = true;
Zotero.skipLoading = true;
return false;
}
@ -549,7 +567,7 @@ var Zotero = new function(){
if (Zotero.Schema.userDataUpgradeRequired()) {
var upgraded = Zotero.Schema.showUpgradeWizard();
if (!upgraded) {
this.skipLoading = true;
Zotero.skipLoading = true;
return false;
}
}
@ -567,12 +585,12 @@ var Zotero = new function(){
]) + "\n\n"
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
+ Zotero.getString('general.seeForMoreInformation', kbURL);
this.startupError = msg;
Zotero.startupError = msg;
}
else {
this.startupError = Zotero.getString('startupError.databaseUpgradeError');
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError');
}
this.skipLoading = true;
Zotero.skipLoading = true;
Components.utils.reportError(e);
return false;
}
@ -589,8 +607,8 @@ var Zotero = new function(){
// Initialize various services
Zotero.Integration.init();
if(Zotero.Prefs.get("connector.enabled")) {
Zotero.Connector.init();
if(Zotero.Prefs.get("httpServer.enabled")) {
Zotero.Server.init();
}
Zotero.Zeroconf.init();
@ -607,18 +625,108 @@ var Zotero = new function(){
// Initialize Locate Manager
Zotero.LocateManager.init();
this.initialized = true;
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
return true;
}
/**
* Initializes the DB connection
*/
function _initDB() {
try {
// Test read access
Zotero.DB.test();
var dbfile = Zotero.getZoteroDatabase();
// Test write access on Zotero data directory
if (!dbfile.parent.isWritable()) {
var msg = 'Cannot write to ' + dbfile.parent.path + '/';
}
// Test write access on Zotero database
else if (!dbfile.isWritable()) {
var msg = 'Cannot write to ' + dbfile.path;
}
else {
var msg = false;
}
if (msg) {
var e = {
name: 'NS_ERROR_FILE_ACCESS_DENIED',
message: msg,
toString: function () {
return Zotero.name + ': ' + Zotero.message;
}
};
throw (e);
}
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseCannotBeOpened'),
Zotero.getString('startupError.checkPermissions')
]);
Zotero.startupError = msg;
} else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
if(Zotero.isStandalone) {
// Standalone should force Fx to release lock
if(Zotero.IPC.broadcast("releaseLock")) {
_waitingForDBLock = true;
while(_waitingForDBLock) Zotero.mainThread.processNextEvent(true);
// we will want to broadcast when initialization completes
_broadcastInitComplete = true;
return _initDB();
}
} else {
// Fx should start as connector if Standalone is running
var haveStandalone = Zotero.IPC.broadcast("test");
if(haveStandalone) {
throw "ZOTERO_SHOULD_START_AS_CONNECTOR";
}
}
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseInUse'),
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
]);
Zotero.startupError = msg;
}
Components.utils.reportError(e);
Zotero.skipLoading = true;
return false;
}
return true;
}
/**
* Called when the DB has been released by another Zotero process to perform necessary
* initialization steps
*/
this.onDBLockReleased = function() {
if(Zotero.isConnector) {
// if DB lock is released, switch out of connector mode
switchConnectorMode(false);
} else if(_waitingForDBLock) {
// if waiting for DB lock and we get it, continue init
_waitingForDBLock = false;
}
}
/**
* Called when an accessory process has been initialized to let use get data
*/
this.onInitComplete = function() {
_waitingForInitComplete = false;
}
/*
* Check if a DB transaction is open and, if so, disable Zotero
*/
function stateCheck() {
if (Zotero.DB.transactionInProgress()) {
if(!Zotero.isConnector && Zotero.DB.transactionInProgress()) {
this.initialized = false;
this.skipLoading = true;
return false;
@ -630,7 +738,33 @@ var Zotero = new function(){
this.shutdown = function (subject, topic, data) {
Zotero.debug("Shutting down Zotero");
Zotero.removeTempDirectory();
try {
// run shutdown listener
for each(var listener in _shutdownListeners) listener();
// remove temp directory
Zotero.removeTempDirectory();
if(Zotero.initialized && Zotero.DB) {
Zotero.debug("Closing database");
// run GC to finalize open statements
// TODO remove this and finalize statements created with
// Zotero.DBConnection.getStatement() explicitly
Components.utils.forceGC();
// unlock DB
Zotero.DB.closeDatabase();
// broadcast that DB lock has been released
Zotero.IPC.broadcast("lockReleased");
}
} catch(e) {
Zotero.debug(e);
throw e;
}
return true;
}
@ -1511,6 +1645,12 @@ var Zotero = new function(){
return true;
}
/**
* Adds a listener to be called when Zotero shuts down (even if Firefox is not shut down)
*/
this.addShutdownListener = function(listener) {
_shutdownListeners.push(listener);
}
function _showWindowZoteroPaneOverlay(doc) {
doc.getElementById('zotero-collections-tree').disabled = true;
@ -1658,9 +1798,21 @@ var Zotero = new function(){
Zotero.Creators.reloadAll();
Zotero.Items.reloadAll();
}
};
/**
* Brings Zotero Standalone to the foreground
*/
this.activateStandalone = function() {
var io = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
var uri = io.newURI('zotero://select', null, null);
var handler = Components.classes['@mozilla.org/uriloader/external-protocol-service;1']
.getService(Components.interfaces.nsIExternalProtocolService)
.getProtocolHandlerInfo('zotero');
handler.preferredAction = Components.interfaces.nsIHandlerInfo.useSystemDefault;
handler.launchWithURI(uri, null);
}
}).call(Zotero);
Zotero.Prefs = new function(){
// Privileged methods

View file

@ -90,18 +90,16 @@ var ZoteroPane = new function()
var self = this;
var _loaded = false;
var titlebarcolorState, titleState;
var titlebarcolorState, titleState, observerService;
var _reloadFunctions = [];
// Also needs to be changed in collectionTreeView.js
var _lastViewedFolderRE = /^(?:(C|S|G)([0-9]+)|L)$/;
/*
/**
* Called when the window containing Zotero pane is open
*/
function init()
{
if(!Zotero || !Zotero.initialized) return;
function init() {
// Set "Report Errors..." label via property rather than DTD entity,
// since we need to reference it in script elsewhere
document.getElementById('zotero-tb-actions-reportErrors').setAttribute('label',
@ -116,9 +114,9 @@ var ZoteroPane = new function()
var zp = document.getElementById('zotero-pane');
Zotero.setFontSize(zp);
this.updateToolbarPosition();
window.addEventListener("resize", this.updateToolbarPosition, false);
window.setTimeout(this.updateToolbarPosition, 0);
ZoteroPane_Local.updateToolbarPosition();
window.addEventListener("resize", ZoteroPane_Local.updateToolbarPosition, false);
window.setTimeout(ZoteroPane_Local.updateToolbarPosition, 0);
Zotero.updateQuickSearchBox(document);
@ -136,10 +134,34 @@ var ZoteroPane = new function()
zp.setAttribute("ignoreActiveAttribute", "true");
}
// register an observer for Zotero reload
observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(_reload, "zotero-reloaded", false);
this.addReloadListener(_loadPane);
// continue loading pane
_loadPane();
}
/**
* Called on window load or when has been reloaded after switching into or out of connector
* mode
*/
function _loadPane() {
if(!Zotero || !Zotero.initialized) return;
if(Zotero.isConnector) {
ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('connector.standaloneOpen'));
return;
} else {
ZoteroPane_Local.clearItemsPaneMessage();
}
//Initialize collections view
this.collectionsView = new Zotero.CollectionTreeView();
ZoteroPane_Local.collectionsView = new Zotero.CollectionTreeView();
var collectionsTree = document.getElementById('zotero-collections-tree');
collectionsTree.view = this.collectionsView;
collectionsTree.view = ZoteroPane_Local.collectionsView;
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
@ -147,8 +169,6 @@ var ZoteroPane = new function()
itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree));
itemsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
this.buildItemTypeSubMenu();
var menu = document.getElementById("contentAreaContextMenu");
menu.addEventListener("popupshowing", ZoteroPane_Local.contextPopupShowing, false);
@ -322,6 +342,8 @@ var ZoteroPane = new function()
this.collectionsView.unregister();
if (this.itemsView)
this.itemsView.unregister();
observerService.removeObserver(_reload, "zotero-reloaded", false);
}
/**
@ -349,6 +371,7 @@ var ZoteroPane = new function()
return false;
}
this.buildItemTypeSubMenu();
this.unserializePersist();
this.updateToolbarPosition();
this.updateTagSelectorSize();
@ -3644,6 +3667,22 @@ var ZoteroPane = new function()
this.openAboutDialog = function() {
window.openDialog('chrome://zotero/content/about.xul', 'about', 'chrome');
}
/**
* Adds or removes a function to be called when Zotero is reloaded by switching into or out of
* the connector
*/
this.addReloadListener = function(/** @param {Function} **/func) {
if(_reloadFunctions.indexOf(func) === -1) _reloadFunctions.push(func);
}
/**
* Called when Zotero is reloaded (i.e., if it is switched into or out of connector mode)
*/
function _reload() {
Zotero.debug("Reloading Zotero pane");
for each(var func in _reloadFunctions) func();
}
}
/**

View file

@ -730,4 +730,6 @@ locate.libraryLookup.label = Library Lookup
locate.libraryLookup.tooltip = Look up this item using the selected OpenURL resolver
locate.manageLocateEngines = Manage Lookup Engines...
standalone.corruptInstallation = Your Zotero Standalone installation appears to be corrupted due to a failed auto-update. While Zotero may continue to function, to avoid potential bugs, please download the latest version of Zotero Standalone from http://zotero.org/support/standalone as soon as possible.
standalone.corruptInstallation = Your Zotero Standalone installation appears to be corrupted due to a failed auto-update. While Zotero may continue to function, to avoid potential bugs, please download the latest version of Zotero Standalone from http://zotero.org/support/standalone as soon as possible.
connector.standaloneOpen = Your database cannot be accessed because Zotero Standalone is currently open. Please view your items in Zotero Standalone.

View file

@ -32,51 +32,64 @@
https://developer.mozilla.org/en/Chrome/Command_Line
*/
const nsISupports = Components.interfaces.nsISupports;
const nsICategoryManager = Components.interfaces.nsICategoryManager;
const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
const nsICommandLine = Components.interfaces.nsICommandLine;
const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
const nsIFactory = Components.interfaces.nsIFactory;
const nsIModule = Components.interfaces.nsIModule;
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero-integration";
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=zotero";
const clh_CID = Components.ID("{531828f8-a16c-46be-b9aa-14845c3b010f}");
const clh_category = "m-zotero-integration";
const clh_description = "Zotero Integration Command Line Handler";
const clh_category = "m-zotero";
const clh_description = "Zotero Command Line Handler";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
/**
* The XPCOM component that implements nsICommandLineHandler.
*/
function ZoteroIntegrationCommandLineHandler() {}
ZoteroIntegrationCommandLineHandler.prototype = {
Zotero : null,
function ZoteroCommandLineHandler() {}
ZoteroCommandLineHandler.prototype = {
/* nsISupports */
QueryInterface : function(iid) {
if(iid.equals(nsICommandLineHandler) ||
iid.equals(nsIFactory) ||
iid.equals(nsISupports)) return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
QueryInterface : XPCOMUtils.generateQI([Components.interfaces.nsICommandLineHandler,
Components.interfaces.nsIFactory, Components.interfaces.nsISupports]),
/* nsICommandLineHandler */
handle : function(cmdLine) {
// handler for Zotero integration commands
// this is typically used on Windows only, via WM_COPYDATA rather than the command line
var agent = cmdLine.handleFlagWithParam("ZoteroIntegrationAgent", false);
var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false);
var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false);
if(agent && command) {
if(!this.Zotero) this.Zotero = Components.classes["@zotero.org/Zotero;1"]
.getService(Components.interfaces.nsISupports).wrappedJSObject;
var Zotero = this.Zotero;
if(agent) {
// Don't open a new window
cmdLine.preventDefault = true;
var command = cmdLine.handleFlagWithParam("ZoteroIntegrationCommand", false);
var docId = cmdLine.handleFlagWithParam("ZoteroIntegrationDocument", false);
// Not quite sure why this is necessary to get the appropriate scoping
var Zotero = this.Zotero;
var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
timer.initWithCallback({notify:function() { Zotero.Integration.execCommand(agent, command, docId) }}, 0,
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
// handler for Windows IPC commands
var param = cmdLine.handleFlagWithParam("ZoteroIPC", false);
if(param) {
// Don't open a new window
cmdLine.preventDefault = true;
this.Zotero.IPC.parsePipeInput(param);
}
// special handler for "zotero" URIs at the command line to prevent them from opening a new
// window
if(this.Zotero.isStandalone) {
var param = cmdLine.handleFlagWithParam("url", false);
if(param) {
var uri = cmdLine.resolveURI(param);
if(uri.schemeIs("zotero")) {
// Don't open a new window
cmdLine.preventDefault = true;
Components.classes["@mozilla.org/network/protocol;1?name=zotero"]
.createInstance(Components.interfaces.nsIProtocolHandler).newChannel(uri);
}
}
}
},
classDescription: clh_description,
@ -88,12 +101,20 @@ ZoteroIntegrationCommandLineHandler.prototype = {
Components.interfaces.nsISupports])
};
ZoteroCommandLineHandler.prototype.__defineGetter__("Zotero", function() {
if(!this._Zotero) {
this._Zotero = Components.classes["@zotero.org/Zotero;1"]
.getService(Components.interfaces.nsISupports).wrappedJSObject;
}
return this._Zotero;
});
/**
* XPCOMUtils.generateNSGetFactory was introduced in Mozilla 2 (Firefox 4).
* XPCOMUtils.generateNSGetModule is for Mozilla 1.9.2 (Firefox 3.6).
*/
if (XPCOMUtils.generateNSGetFactory) {
var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroIntegrationCommandLineHandler]);
var NSGetFactory = XPCOMUtils.generateNSGetFactory([ZoteroCommandLineHandler]);
} else {
var NSGetModule = XPCOMUtils.generateNSGetModule([ZoteroIntegrationCommandLineHandler]);
var NSGetModule = XPCOMUtils.generateNSGetModule([ZoteroCommandLineHandler]);
}

View file

@ -852,13 +852,21 @@ function ChromeExtensionHandler() {
var [path, queryString] = uri.path.substr(1).split('?');
var [type, id] = path.split('/');
//currently only able to select one item
// currently only able to select one item
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow(null);
var win = wm.getMostRecentWindow("navigator:browser");
// restore window if it's in the dock
if(win.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED) {
win.restore();
}
// open Zotero pane
win.ZoteroPane.show();
if(!id) return;
var lkh = Zotero.Items.parseLibraryKeyHash(id);
if (lkh) {
var item = Zotero.Items.getByLibraryAndKey(lkh.libraryID, lkh.key);
@ -1026,10 +1034,10 @@ function ChromeExtensionHandler() {
try {
var originalURI = uri.path;
originalURI = decodeURIComponent(originalURI.substr(originalURI.indexOf("/")+1));
if(!Zotero.Connector.Data[originalURI]) {
if(!Zotero.Server.Connector.Data[originalURI]) {
return null;
} else {
return new ConnectorChannel(originalURI, Zotero.Connector.Data[originalURI]);
return new ConnectorChannel(originalURI, Zotero.Server.Connector.Data[originalURI]);
}
} catch(e) {
Zotero.debug(e);

View file

@ -35,30 +35,31 @@ const ZOTERO_IID = Components.interfaces.chnmIZoteroService; //unused
const Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
getService(Components.interfaces.nsIXULAppInfo);
if(appInfo.platformVersion[0] >= 2) {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
}
// Assign the global scope to a variable to passed via wrappedJSObject
var ZoteroWrapped = this;
/********************************************************************
* Include the core objects to be stored within XPCOM
*********************************************************************/
var xpcomFiles = [
/** XPCOM files to be loaded for all modes **/
const xpcomFilesAll = [
'zotero',
'date',
'debug',
'error',
'file',
'http',
'mimeTypeHandler',
'openurl',
'ipc',
'progressWindow',
'translation/translate',
'translation/translate_firefox',
'translation/tlds',
'utilities'
];
/** XPCOM files to be loaded only for local translation and DB access **/
const xpcomFilesLocal = [
'collectionTreeView',
'annotate',
'attachments',
'cite',
'collectionTreeView',
'commons',
'connector',
'dataServer',
'data_access',
'data/dataObjects',
'data/cachedTypes',
@ -79,28 +80,21 @@ var xpcomFiles = [
'data/tags',
'date',
'db',
'debug',
'duplicate',
'enstyle',
'error',
'file',
'fulltext',
'http',
'id',
'integration',
'integration_compat',
'itemTreeView',
'locateManager',
'mime',
'mimeTypeHandler',
'notifier',
'openurl',
'progressWindow',
'proxy',
'quickCopy',
'report',
'schema',
'search',
'server',
'style',
'sync',
'storage',
@ -108,117 +102,197 @@ var xpcomFiles = [
'storage/zfs',
'storage/webdav',
'timeline',
'translation/translator',
'translation/translate',
'translation/browser_firefox',
'translation/item_local',
'uri',
'utilities',
'zeroconf'
'zeroconf',
'translation/translate_item',
'translation/translator',
'server_connector'
];
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[0] + ".js");
// Load CiteProc into Zotero.CiteProc namespace
Zotero.CiteProc = {"Zotero":Zotero};
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/citeproc.js", Zotero.CiteProc);
for (var i=1; i<xpcomFiles.length; i++) {
try {
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFiles[i] + ".js");
}
catch (e) {
Components.utils.reportError("Error loading " + xpcomFiles[i] + ".js");
throw (e);
}
}
// Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references)
var rdfXpcomFiles = [
'rdf/uri',
'rdf/term',
'rdf/identity',
'rdf/match',
'rdf/n3parser',
'rdf/rdfparser',
'rdf/serialize',
'rdf'
/** XPCOM files to be loaded only for connector translation and DB access **/
const xpcomFilesConnector = [
'connector/translate_item',
'connector/translator',
'connector/connector',
'connector/cachedTypes'
];
Zotero.RDF = {AJAW:{}};
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
for (var i=0; i<rdfXpcomFiles.length; i++) {
var instanceID = (new Date()).getTime();
var isFirstLoadThisSession = true;
var zContext = null;
ZoteroContext = function() {}
ZoteroContext.prototype = {
/**
* Convenience method to replicate window.alert()
**/
// TODO: is this still used? if so, move to zotero.js
"alert":function alert(msg){
this.Zotero.debug("alert() is deprecated from Zotero XPCOM");
Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService)
.alert(null, "", msg);
},
/**
* Convenience method to replicate window.confirm()
**/
// TODO: is this still used? if so, move to zotero.js
"confirm":function confirm(msg){
this.Zotero.debug("confirm() is deprecated from Zotero XPCOM");
return Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService)
.confirm(null, "", msg);
},
"Cc":Cc,
"Ci":Ci,
/**
* Convenience method to replicate window.setTimeout()
**/
"setTimeout":function setTimeout(func, ms){
this.Zotero.setTimeout(func, ms);
},
/**
* Switches in or out of connector mode
*/
"switchConnectorMode":function(isConnector) {
if(isConnector !== this.isConnector) {
zContext.Zotero.shutdown();
// create a new zContext
makeZoteroContext(isConnector);
zContext.Zotero.init();
}
return zContext;
}
};
/**
* The class from which the Zotero global XPCOM context is constructed
*
* @constructor
* This runs when ZoteroService is first requested to load all applicable scripts and initialize
* Zotero. Calls to other XPCOM components must be in here rather than in top-level code, as other
* components may not have yet been initialized.
*/
function makeZoteroContext(isConnector) {
if(zContext) {
// Swap out old zContext
var oldzContext = zContext;
// Create new zContext
zContext = new ZoteroContext();
// Swap in old Zotero object, so that references don't break, but empty it
zContext.Zotero = oldzContext.Zotero;
for(var key in zContext.Zotero) delete zContext.Zotero[key];
} else {
zContext = new ZoteroContext();
zContext.Zotero = function() {};
}
// Load zotero.js first
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", Zotero.RDF.AJAW);
}
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://global/content/nsTransferable.js");
/********************************************************************/
// Initialize the Zotero service
//
// This runs when ZoteroService is first requested.
// Calls to other XPCOM components must be in here rather than in top-level
// code, as other components may not have yet been initialized.
function setupService(){
try {
Zotero.init();
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[0] + ".js", zContext);
// Load CiteProc into Zotero.CiteProc namespace
zContext.Zotero.CiteProc = {"Zotero":zContext.Zotero};
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/citeproc.js", zContext.Zotero.CiteProc);
// Load remaining xpcomFiles
for (var i=1; i<xpcomFilesAll.length; i++) {
try {
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFilesAll[i] + ".js", zContext);
}
catch (e) {
Components.utils.reportError("Error loading " + xpcomFilesAll[i] + ".js", zContext);
throw (e);
}
}
catch (e) {
// Load xpcomFiles for specific mode
for each(var xpcomFile in (isConnector ? xpcomFilesConnector : xpcomFilesLocal)) {
try {
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + xpcomFile + ".js", zContext);
}
catch (e) {
Components.utils.reportError("Error loading " + xpcomFile + ".js", zContext);
throw (e);
}
}
// Load RDF files into Zotero.RDF.AJAW namespace (easier than modifying all of the references)
const rdfXpcomFiles = [
'rdf/uri',
'rdf/term',
'rdf/identity',
'rdf/match',
'rdf/n3parser',
'rdf/rdfparser',
'rdf/serialize',
'rdf'
];
zContext.Zotero.RDF = {AJAW:{Zotero:zContext.Zotero}};
for (var i=0; i<rdfXpcomFiles.length; i++) {
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://zotero/content/xpcom/" + rdfXpcomFiles[i] + ".js", zContext.Zotero.RDF.AJAW);
}
// load nsTransferable (query: do we still use this?)
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://global/content/nsTransferable.js", zContext);
// add connector-related properties
zContext.Zotero.isConnector = isConnector;
zContext.Zotero.instanceID = instanceID;
zContext.Zotero.__defineGetter__("isFirstLoadThisSession", function() isFirstLoadThisSession);
};
/**
* The class representing the Zotero service, and affiliated XPCOM goop
*/
function ZoteroService(){
try {
if(isFirstLoadThisSession) {
makeZoteroContext(false);
try {
zContext.Zotero.init();
} catch(e) {
if(e === "ZOTERO_SHOULD_START_AS_CONNECTOR") {
// if Zotero should start as a connector, reload it
zContext.Zotero.shutdown();
makeZoteroContext(true);
zContext.Zotero.init();
} else {
dump(e.toSource());
Components.utils.reportError(e);
throw e;
}
}
}
isFirstLoadThisSession = false; // no longer first load
this.wrappedJSObject = zContext.Zotero;
} catch(e) {
var msg = typeof e == 'string' ? e : e.name;
dump(e + "\n\n");
Components.utils.reportError(e);
throw (e);
throw e;
}
}
function ZoteroService(){
this.wrappedJSObject = ZoteroWrapped.Zotero;
setupService();
}
/**
* Convenience method to replicate window.alert()
**/
// TODO: is this still used? if so, move to zotero.js
function alert(msg){
Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService)
.alert(null, "", msg);
}
/**
* Convenience method to replicate window.confirm()
**/
// TODO: is this still used? if so, move to zotero.js
function confirm(msg){
return Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService)
.confirm(null, "", msg);
}
/**
* Convenience method to replicate window.setTimeout()
**/
function setTimeout(func, ms) {
Zotero.setTimeout(func, ms);
}
//
// XPCOM goop
//
@ -227,8 +301,8 @@ ZoteroService.prototype = {
contractID: ZOTERO_CONTRACTID,
classDescription: ZOTERO_CLASSNAME,
classID: ZOTERO_CID,
service: true,
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports, ZOTERO_IID])
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
Components.interfaces.nsIProtocolHandler])
}
/**

View file

@ -107,8 +107,8 @@ pref("extensions.zotero.integration.port", 50001);
pref("extensions.zotero.integration.autoRegenerate", -1); // -1 = ask; 0 = no; 1 = yes
// Connector settings
pref("extensions.zotero.connector.enabled", false);
pref("extensions.zotero.connector.port", 23119); // ascii "ZO"
pref("extensions.zotero.httpServer.enabled", false); // TODO enabled for testing only
pref("extensions.zotero.httpServer.port", 23119); // ascii "ZO"
// Zeroconf
pref("extensions.zotero.zeroconf.server.enabled", false);