diff --git a/chrome/content/zotero/bindings/itembox.xml b/chrome/content/zotero/bindings/itembox.xml index 46416ecb4d..cb7ba5f757 100644 --- a/chrome/content/zotero/bindings/itembox.xml +++ b/chrome/content/zotero/bindings/itembox.xml @@ -272,7 +272,7 @@ var doi = this.item.getField('DOI'); if (doi && typeof val == 'String') { // Pull out DOI, in case there's a prefix - doi = Zotero.Utilities.prototype.cleanDOI(doi);; + doi = Zotero.Utilities.cleanDOI(doi);; if (doi) { spec = "http://dx.doi.org/" + encodeURIComponent(doi); } @@ -456,7 +456,7 @@ } else if (fieldName == 'DOI' && val && typeof val == 'string') { // Pull out DOI, in case there's a prefix - var doi = Zotero.Utilities.prototype.cleanDOI(val); + var doi = Zotero.Utilities.cleanDOI(val); if (doi) { doi = "http://dx.doi.org/" + encodeURIComponent(doi); label.setAttribute("isButton", true); @@ -1466,10 +1466,16 @@ ' + Zotero.Utilities.prototype.text2html(text) + ""; + + '>' + Zotero.Utilities.text2html(text) + ""; } var item = new Zotero.Item('note'); @@ -2769,7 +2769,7 @@ var ZoteroPane = new function() return false; } - text = Zotero.Utilities.prototype.trim(text); + text = Zotero.Utilities.trim(text); if (!text.length) { return false; @@ -2777,7 +2777,7 @@ var ZoteroPane = new function() text = '' + Zotero.Utilities.prototype.text2html(text) + ""; + + '>' + Zotero.Utilities.text2html(text) + ""; var items = this.getSelectedItems(); if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) { @@ -3042,7 +3042,7 @@ var ZoteroPane = new function() Zotero.debug(e); } - Zotero.Utilities.HTTP.processDocuments([url], processor, done, exception); + Zotero.HTTP.processDocuments([url], processor, done, exception); } // Otherwise create placeholder item, attach attachment, and update from that else { diff --git a/chrome/content/zotero/preferences/preferences.js b/chrome/content/zotero/preferences/preferences.js index 934fb8cbde..3d2c700bae 100644 --- a/chrome/content/zotero/preferences/preferences.js +++ b/chrome/content/zotero/preferences/preferences.js @@ -847,14 +847,13 @@ function updatePDFToolsStatus() { // If we haven't already generated the required and documentation messages if (!converterIsRegistered && !requiredLabel.hasChildNodes()) { - var utils = new Zotero.Utilities(); // Xpdf link var str = Zotero.getString('zotero.preferences.search.pdf.toolsRequired', [Zotero.Fulltext.pdfConverterName, Zotero.Fulltext.pdfInfoName, '' + Zotero.Fulltext.pdfToolsName + '']); - var parts = utils.parseMarkup(str); + var parts = Zotero.Utilities.parseMarkup(str); for (var i=0; i'; var str = Zotero.getString('zotero.preferences.search.pdf.advancedUsers', link); - var parts = utils.parseMarkup(str); + var parts = Zotero.Utilities.parseMarkup(str); for (var i=0; i lowerLast.length) { - var firstName = Zotero.Utilities.prototype.trim(creator.substr(0, creator.length-lowerLast.length)); + var firstName = Zotero.Utilities.trim(creator.substr(0, creator.length-lowerLast.length)); if(firstName.length) { // check to see whether the first name is all initials const initialRe = /^(?:[A-Z]\.? ?)+$/; diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 56f13b3cfb..5ff671a85a 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -1445,7 +1445,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient) /* if (type == 'item') { - if (!Zotero.Utilities.prototype.isEmpty(changedCreators)) { + if (!Zotero.Utilities.isEmpty(changedCreators)) { io.dataIn.changedCreators = changedCreators; } } diff --git a/chrome/content/zotero/xpcom/commons.js b/chrome/content/zotero/xpcom/commons.js index 6f50ce2d0b..45d7d6b28c 100644 --- a/chrome/content/zotero/xpcom/commons.js +++ b/chrome/content/zotero/xpcom/commons.js @@ -165,8 +165,8 @@ Zotero.Commons = new function() { Zotero.debug("IA BUCKETS"); Zotero.debug(IABuckets); - var addBuckets = Zotero.Utilities.prototype.arrayDiff(IABuckets, currentBuckets); - var removeBuckets = Zotero.Utilities.prototype.arrayDiff(currentBuckets, IABuckets); + var addBuckets = Zotero.Utilities.arrayDiff(IABuckets, currentBuckets); + var removeBuckets = Zotero.Utilities.arrayDiff(currentBuckets, IABuckets); Zotero.debug("ADD"); Zotero.debug(addBuckets); @@ -397,7 +397,7 @@ Zotero.Commons = new function() { this.slugify = function (input) { - var slug = Zotero.Utilities.prototype.trim(input) + var slug = Zotero.Utilities.trim(input) .toLowerCase() .replace(/[^a-z0-9 ._-]/g, "") //.replace(/ /g, "_"); @@ -458,7 +458,7 @@ Zotero.Commons.Bucket.prototype.exists = function (callback, maxTries, tries) { var self = this; - Zotero.Utilities.HTTP.doHead(this.uri, function (xmlhttp) { + Zotero.HTTP.doHead(this.uri, function (xmlhttp) { switch (xmlhttp.status) { case 200: callback(1); @@ -511,7 +511,7 @@ Zotero.Commons.Bucket.prototype.getItems = function (callback) { var progressWin = null; var progressWinIcon = 'chrome://zotero/skin/treeitem-attachment-pdf.png'; - var req = Zotero.Utilities.HTTP.doGet(uri, function (xmlhttp) { + var req = Zotero.HTTP.doGet(uri, function (xmlhttp) { if (xmlhttp.status != 200) { Zotero.debug(xmlhttp.status); Zotero.debug(xmlhttp.responseText); @@ -620,7 +620,7 @@ Zotero.Commons.Bucket.prototype.getItems = function (callback) { var rdfURI = self.downloadURI + '/' + zip.rdf; - Zotero.Utilities.HTTP.doGet(rdfURI, function (xmlhttp) { + Zotero.HTTP.doGet(rdfURI, function (xmlhttp) { // If RDF not available, skip item if (xmlhttp.status != 200) { Zotero.debug("RDF not found at " + xmlhttp.channel.originalURI.spec); @@ -1049,7 +1049,7 @@ Zotero.Commons.Bucket.prototype.updateMetadata = function(action, item, callback var self = this; // get previous metadata. multiple language support difficult via IA s3. - Zotero.Utilities.HTTP.doGet(resource, function (xmlhttp) { + Zotero.HTTP.doGet(resource, function (xmlhttp) { if (xmlhttp.status == 404 || (xmlhttp.status == 200 && !xmlhttp.responseXML)) { Zotero.Commons.error("Error updating bucket metadata"); return; diff --git a/chrome/content/zotero/xpcom/connector.js b/chrome/content/zotero/xpcom/connector.js index 3eb1201c23..c766ba29e3 100755 --- a/chrome/content/zotero/xpcom/connector.js +++ b/chrome/content/zotero/xpcom/connector.js @@ -39,7 +39,7 @@ Zotero.Connector = new function() { * initializes a very rudimentary web server */ this.init = function() { - if (Zotero.Utilities.HTTP.browserIsOffline()) { + if (Zotero.HTTP.browserIsOffline()) { Zotero.debug('Browser is offline -- not initializing connector HTTP server'); _registerOnlineObserver(); return; diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index e213935438..b37440b4e0 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -113,7 +113,7 @@ Zotero.Collection.prototype._set = function (field, val) { return; case 'name': - val = Zotero.Utilities.prototype.trim(val); + val = Zotero.Utilities.trim(val); break; } @@ -843,16 +843,16 @@ Zotero.Collection.prototype.diff = function (collection, includeMatches, ignoreO var numDiffs = Zotero.Collections.diff(thisData, otherData, diff, includeMatches); // For the moment, just compare children and increase numDiffs if any differences - var d1 = Zotero.Utilities.prototype.arrayDiff( + var d1 = Zotero.Utilities.arrayDiff( thisData.childCollections, otherData.childCollections ); - var d2 = Zotero.Utilities.prototype.arrayDiff( + var d2 = Zotero.Utilities.arrayDiff( otherData.childCollections, thisData.childCollections ); - var d3 = Zotero.Utilities.prototype.arrayDiff( + var d3 = Zotero.Utilities.arrayDiff( thisData.childItems, otherData.childItems ); - var d4 = Zotero.Utilities.prototype.arrayDiff( + var d4 = Zotero.Utilities.arrayDiff( otherData.childItems, thisData.childItems ); numDiffs += d1.length + d2.length; diff --git a/chrome/content/zotero/xpcom/data/creator.js b/chrome/content/zotero/xpcom/data/creator.js index 3661ef42bd..23ae133cf2 100644 --- a/chrome/content/zotero/xpcom/data/creator.js +++ b/chrome/content/zotero/xpcom/data/creator.js @@ -104,7 +104,7 @@ Zotero.Creator.prototype._set = function (field, val) { case 'lastName': case 'shortName': if (val) { - val = Zotero.Utilities.prototype.trim(val); + val = Zotero.Utilities.trim(val); } else { val = ''; diff --git a/chrome/content/zotero/xpcom/data/creators.js b/chrome/content/zotero/xpcom/data/creators.js index e8c663e3bb..f7f3b6e8be 100644 --- a/chrome/content/zotero/xpcom/data/creators.js +++ b/chrome/content/zotero/xpcom/data/creators.js @@ -332,8 +332,8 @@ Zotero.Creators = new function() { for each(var field in Zotero.Creators.fields) { hashFields.push(fields[field]); } - var ZU = new Zotero.Utilities; - return ZU.md5(hashFields.join('_')); + + return Zotero.Utilities.Internal.md5(hashFields.join('_')); } diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 97e696713d..0080148f70 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -631,7 +631,7 @@ Zotero.Item.prototype.inCollection = function(collectionID) { */ Zotero.Item.prototype.setField = function(field, value, loadIn) { if (typeof value == 'string') { - value = Zotero.Utilities.prototype.trim(value); + value = Zotero.Utilities.trim(value); } this._disabledCheck(); @@ -1155,8 +1155,6 @@ Zotero.Item.prototype.save = function() { lastPos++; } - var ZU = new Zotero.Utilities; - Zotero.DB.beginTransaction(); var isNew = !this.id || !this.exists(); @@ -2339,7 +2337,7 @@ Zotero.Item.prototype.getNote = function() { // Convert non-HTML notes on-the-fly if (note) { if (!note.substr(0, 36).match(/^
/)) { - note = Zotero.Utilities.prototype.htmlSpecialChars(note); + note = Zotero.Utilities.htmlSpecialChars(note); note = '

' + note.replace(/\n/g, '

') .replace(/\t/g, '    ') @@ -2376,7 +2374,7 @@ Zotero.Item.prototype.setNote = function(text) { throw ("text must be a string in Zotero.Item.setNote() (was " + typeof text + ")"); } - text = Zotero.Utilities.prototype.trim(text); + text = Zotero.Utilities.trim(text); var oldText = this.getNote(); if (text == oldText) { @@ -3083,7 +3081,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentHash', function () { return undefined; } - return Zotero.Utilities.prototype.md5(file); + return Zotero.Utilities.Internal.md5(file); }); @@ -3113,7 +3111,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () { // TODO: remove post-Fx3.0 if (!str.trim) { - return Zotero.Utilities.prototype.trim(str); + return Zotero.Utilities.trim(str); } return str.trim(); @@ -3161,7 +3159,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () { else if (mimeType == 'text/html') { str = Zotero.File.getContents(file); - str = Zotero.Utilities.prototype.unescapeHTML(str); + str = Zotero.Utilities.unescapeHTML(str); } else if (mimeType == 'text/plain') { @@ -3174,7 +3172,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () { // TODO: remove post-Fx3.0 if (!str.trim) { - return Zotero.Utilities.prototype.trim(str); + return Zotero.Utilities.trim(str); } return str.trim(); @@ -3274,7 +3272,7 @@ Zotero.Item.prototype.addTag = function(name, type) { throw ('Cannot add tag to unsaved item in Item.addTag()'); } - name = Zotero.Utilities.prototype.trim(name); + name = Zotero.Utilities.trim(name); if (!name) { Zotero.debug('Not saving empty tag in Item.addTag()', 2); @@ -3426,7 +3424,7 @@ Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) { throw ('Cannot replace tag on unsaved item'); } - newTag = Zotero.Utilities.prototype.trim(newTag); + newTag = Zotero.Utilities.trim(newTag); if (!newTag) { Zotero.debug('Not replacing with empty tag', 2); diff --git a/chrome/content/zotero/xpcom/data/notes.js b/chrome/content/zotero/xpcom/data/notes.js index 822531d888..ee483d5a89 100644 --- a/chrome/content/zotero/xpcom/data/notes.js +++ b/chrome/content/zotero/xpcom/data/notes.js @@ -33,8 +33,8 @@ Zotero.Notes = new function() { * Return first line (or first MAX_LENGTH characters) of note content **/ function noteToTitle(text) { - text = Zotero.Utilities.prototype.trim(text); - text = Zotero.Utilities.prototype.unescapeHTML(text); + text = Zotero.Utilities.trim(text); + text = Zotero.Utilities.unescapeHTML(text); var max = this.MAX_TITLE_LENGTH; diff --git a/chrome/content/zotero/xpcom/data/tag.js b/chrome/content/zotero/xpcom/data/tag.js index 5e2fdfae2d..efc65577e8 100644 --- a/chrome/content/zotero/xpcom/data/tag.js +++ b/chrome/content/zotero/xpcom/data/tag.js @@ -96,7 +96,7 @@ Zotero.Tag.prototype._set = function (field, val) { return; case 'name': - val = Zotero.Utilities.prototype.trim(val); + val = Zotero.Utilities.trim(val); break; } @@ -372,8 +372,8 @@ Zotero.Tag.prototype.save = function (full) { var sql = "SELECT itemID FROM itemTags WHERE tagID=?"; var dbItemIDs = Zotero.DB.columnQuery(sql, tagID); if (dbItemIDs) { - removed = Zotero.Utilities.prototype.arrayDiff(dbItemIDs, currentIDs); - newids = Zotero.Utilities.prototype.arrayDiff(currentIDs, dbItemIDs); + removed = Zotero.Utilities.arrayDiff(dbItemIDs, currentIDs); + newids = Zotero.Utilities.arrayDiff(currentIDs, dbItemIDs); } else { newids = currentIDs; @@ -381,10 +381,10 @@ Zotero.Tag.prototype.save = function (full) { } else { if (this._previousData.linkedItems) { - removed = Zotero.Utilities.prototype.arrayDiff( + removed = Zotero.Utilities.arrayDiff( this._previousData.linkedItems, currentIDs ); - newids = Zotero.Utilities.prototype.arrayDiff( + newids = Zotero.Utilities.arrayDiff( currentIDs, this._previousData.linkedItems ); } @@ -483,10 +483,10 @@ Zotero.Tag.prototype.diff = function (tag, includeMatches, ignoreOnlyDateModifie var numDiffs = Zotero.Tags.diff(thisData, otherData, diff, includeMatches); // For the moment, just compare linked items and increase numDiffs if any differences - var d1 = Zotero.Utilities.prototype.arrayDiff( + var d1 = Zotero.Utilities.arrayDiff( otherData.linkedItems, thisData.linkedItems ); - var d2 = Zotero.Utilities.prototype.arrayDiff( + var d2 = Zotero.Utilities.arrayDiff( thisData.linkedItems, otherData.linkedItems ); numDiffs += d1.length + d2.length; diff --git a/chrome/content/zotero/xpcom/data/tags.js b/chrome/content/zotero/xpcom/data/tags.js index 298b131c90..9b641910af 100644 --- a/chrome/content/zotero/xpcom/data/tags.js +++ b/chrome/content/zotero/xpcom/data/tags.js @@ -77,7 +77,7 @@ Zotero.Tags = new function() { * Returns the tagID matching given tag and type */ function getID(name, type, libraryID) { - name = Zotero.Utilities.prototype.trim(name); + name = Zotero.Utilities.trim(name); var lcname = name.toLowerCase(); if (!libraryID) { @@ -119,7 +119,7 @@ Zotero.Tags = new function() { * Returns all tagIDs for this tag (of all types) */ function getIDs(name, libraryID) { - name = Zotero.Utilities.prototype.trim(name); + name = Zotero.Utilities.trim(name); var sql = "SELECT tagID FROM tags WHERE name=? AND libraryID"; var params = [name]; if (libraryID) { @@ -137,7 +137,7 @@ Zotero.Tags = new function() { * Returns an array of tag types for tags matching given tag */ function getTypes(name, libraryID) { - name = Zotero.Utilities.prototype.trim(name); + name = Zotero.Utilities.trim(name); var sql = "SELECT type FROM tags WHERE name=? AND libraryID"; var params = [name]; if (libraryID) { @@ -285,7 +285,7 @@ Zotero.Tags = new function() { function rename(tagID, name) { Zotero.debug('Renaming tag', 4); - name = Zotero.Utilities.prototype.trim(name); + name = Zotero.Utilities.trim(name); Zotero.DB.beginTransaction(); diff --git a/chrome/content/zotero/xpcom/dataServer.js b/chrome/content/zotero/xpcom/dataServer.js index 921c5c87a3..166efd2215 100644 --- a/chrome/content/zotero/xpcom/dataServer.js +++ b/chrome/content/zotero/xpcom/dataServer.js @@ -46,7 +46,7 @@ Zotero.DataServer = new function () { return; } - if (Zotero.Utilities.HTTP.browserIsOffline()) { + if (Zotero.HTTP.browserIsOffline()) { Zotero.debug('Browser is offline -- not initializing data HTTP server'); _registerOnlineObserver() return; diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js index 1753d3454b..14c7a0c969 100644 --- a/chrome/content/zotero/xpcom/db.js +++ b/chrome/content/zotero/xpcom/db.js @@ -1190,11 +1190,10 @@ Zotero.DBConnection.prototype._getDBConnection = function () { // Levenshtein distance UDF var lev = { - ZU: new Zotero.Utilities, onFunctionCall: function (arg) { var a = arg.getUTF8String(0); var b = arg.getUTF8String(1); - return this.ZU.levenshtein(a, b); + return Zotero.Utilities.levenshtein(a, b); } }; this._connection.createFunction('levenshtein', 2, lev); @@ -1213,7 +1212,7 @@ Zotero.DBConnection.prototype._getDBConnection = function () { var rx = { onFunctionCall: function (arg) { var str = arg.getUTF8String(0); - return Zotero.Utilities.prototype.text2html(str, true); + return Zotero.Utilities.text2html(str, true); } }; this._connection.createFunction('text2html', 1, rx); diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js new file mode 100644 index 0000000000..18b49990b1 --- /dev/null +++ b/chrome/content/zotero/xpcom/http.js @@ -0,0 +1,610 @@ +/** + * Functions for performing HTTP requests, both via XMLHTTPRequest and using a hidden browser + * @namespace + */ +Zotero.HTTP = new function() { + this.WebDAV = {}; + + + /** + * Send an HTTP GET request via XMLHTTPRequest + * + * @param {nsIURI|String} url URL to request + * @param {Function} onDone Callback to be executed upon request completion + * @param {String} responseCharset Character set to force on the response + * @return {Boolean} True if the request was sent, or false if the browser is offline + */ + this.doGet = function(url, onDone, responseCharset) { + if (url instanceof Components.interfaces.nsIURI) { + // Don't display password in console + var disp = url.clone(); + if (disp.password) { + disp.password = "********"; + } + Zotero.debug("HTTP GET " + disp.spec); + url = url.spec; + } + else { + Zotero.debug("HTTP GET " + url); + } + if (this.browserIsOffline()){ + return false; + } + + // Workaround for "Accept third-party cookies" being off in Firefox 3.0.1 + // https://www.zotero.org/trac/ticket/1070 + if (Zotero.isFx30) { + const Cc = Components.classes; + const Ci = Components.interfaces; + var ds = Cc["@mozilla.org/webshell;1"]. + createInstance(Components.interfaces.nsIDocShellTreeItem). + QueryInterface(Ci.nsIInterfaceRequestor); + ds.itemType = Ci.nsIDocShellTreeItem.typeContent; + var xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open("GET", url, true); + xmlhttp.channel.loadGroup = ds.getInterface(Ci.nsILoadGroup); + xmlhttp.channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + } + else { + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open('GET', url, true); + // Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only) + if (!Zotero.isFx35) { + var channel = xmlhttp.channel; + channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal); + channel.forceAllowThirdPartyCookie = true; + } + } + + // Don't cache GET requests + xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; + + /** @ignore */ + xmlhttp.onreadystatechange = function() { + _stateChange(xmlhttp, onDone, responseCharset); + }; + + xmlhttp.send(null); + + return xmlhttp; + } + + /** + * Send an HTTP POST request via XMLHTTPRequest + * + * @param {String} url URL to request + * @param {String} body Request body + * @param {Function} onDone Callback to be executed upon request completion + * @param {String} headers Request HTTP headers + * @param {String} responseCharset Character set to force on the response + * @return {Boolean} True if the request was sent, or false if the browser is offline + */ + this.doPost = function(url, body, onDone, headers, responseCharset) { + if (url instanceof Components.interfaces.nsIURI) { + // Don't display password in console + var disp = url.clone(); + if (disp.password) { + disp.password = "********"; + } + url = url.spec; + } + + var bodyStart = body.substr(0, 1024); + // Don't display sync password or session id in console + bodyStart = bodyStart.replace(/password=[^&]+/, 'password=********'); + bodyStart = bodyStart.replace(/sessionid=[^&]+/, 'sessionid=********'); + + Zotero.debug("HTTP POST " + + (body.length > 1024 ? + bodyStart + '... (' + body.length + ' chars)' : bodyStart) + + " to " + (disp ? disp.spec : url)); + + + if (this.browserIsOffline()){ + return false; + } + + // Workaround for "Accept third-party cookies" being off in Firefox 3.0.1 + // https://www.zotero.org/trac/ticket/1070 + if (Zotero.isFx30) { + const Cc = Components.classes; + const Ci = Components.interfaces; + var ds = Cc["@mozilla.org/webshell;1"]. + createInstance(Components.interfaces.nsIDocShellTreeItem). + QueryInterface(Ci.nsIInterfaceRequestor); + ds.itemType = Ci.nsIDocShellTreeItem.typeContent; + var xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open("POST", url, true); + xmlhttp.channel.loadGroup = ds.getInterface(Ci.nsILoadGroup); + xmlhttp.channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + } + else { + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open('POST', url, true); + // Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only) + if (!Zotero.isFx35) { + var channel = xmlhttp.channel; + channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal); + channel.forceAllowThirdPartyCookie = true; + } + } + + if (headers) { + if (typeof headers == 'string') { + var msg = "doPost() now takes a headers object rather than a requestContentType -- update your code"; + Zotero.debug(msg, 2); + Components.utils.reportError(msg); + headers = { + "Content-Type": headers + }; + } + } + else { + headers = {}; + } + + if (!headers["Content-Type"]) { + headers["Content-Type"] = "application/x-www-form-urlencoded"; + } + + for (var header in headers) { + xmlhttp.setRequestHeader(header, headers[header]); + } + + /** @ignore */ + xmlhttp.onreadystatechange = function(){ + _stateChange(xmlhttp, onDone, responseCharset); + }; + + xmlhttp.send(body); + + return xmlhttp; + } + + /** + * Send an HTTP HEAD request via XMLHTTPRequest + * + * @param {String} url URL to request + * @param {Function} onDone Callback to be executed upon request completion + * @param {Object} requestHeaders HTTP headers to include with request + * @return {Boolean} True if the request was sent, or false if the browser is offline + */ + this.doHead = function(url, onDone, requestHeaders) { + if (url instanceof Components.interfaces.nsIURI) { + // Don't display password in console + var disp = url.clone(); + if (disp.password) { + disp.password = "********"; + } + Zotero.debug("HTTP HEAD " + disp.spec); + url = url.spec; + } + else { + Zotero.debug("HTTP HEAD " + url); + + } + + if (this.browserIsOffline()){ + return false; + } + + // Workaround for "Accept third-party cookies" being off in Firefox 3.0.1 + // https://www.zotero.org/trac/ticket/1070 + if (Zotero.isFx30) { + const Cc = Components.classes; + const Ci = Components.interfaces; + var ds = Cc["@mozilla.org/webshell;1"]. + createInstance(Components.interfaces.nsIDocShellTreeItem). + QueryInterface(Ci.nsIInterfaceRequestor); + ds.itemType = Ci.nsIDocShellTreeItem.typeContent; + var xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open("HEAD", url, true); + xmlhttp.channel.loadGroup = ds.getInterface(Ci.nsILoadGroup); + xmlhttp.channel.loadFlags |= Ci.nsIChannel.LOAD_DOCUMENT_URI; + } + else { + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open('HEAD', url, true); + // Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only) + if (!Zotero.isFx35) { + var channel = xmlhttp.channel; + channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal); + channel.forceAllowThirdPartyCookie = true; + } + } + + if (requestHeaders) { + for (var header in requestHeaders) { + xmlhttp.setRequestHeader(header, requestHeaders[header]); + } + } + + // Don't cache HEAD requests + xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; + + /** @ignore */ + xmlhttp.onreadystatechange = function(){ + _stateChange(xmlhttp, onDone); + }; + + xmlhttp.send(null); + + return xmlhttp; + } + + /** + * Send an HTTP OPTIONS request via XMLHTTPRequest + * + * @param {nsIURI} url + * @param {Function} onDone + * @return {XMLHTTPRequest} + */ + this.doOptions = function (uri, callback) { + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = "********"; + } + Zotero.debug("HTTP OPTIONS for " + disp.spec); + + if (Zotero.HTTP.browserIsOffline()){ + return false; + } + + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open('OPTIONS', uri.spec, true); + /** @ignore */ + xmlhttp.onreadystatechange = function() { + _stateChange(xmlhttp, callback); + }; + xmlhttp.send(null); + return xmlhttp; + } + + // + // WebDAV methods + // + + /** + * Send a WebDAV PROP* request via XMLHTTPRequest + * + * Returns false if browser is offline + * + * @param {String} method PROPFIND or PROPPATCH + * @param {nsIURI} uri + * @param {String} body XML string + * @param {Function} callback + * @param {Object} requestHeaders e.g. { Depth: 0 } + */ + this.WebDAV.doProp = function (method, uri, body, callback, requestHeaders) { + switch (method) { + case 'PROPFIND': + case 'PROPPATCH': + break; + + default: + throw ("Invalid method '" + method + + "' in Zotero.HTTP.doProp"); + } + + if (requestHeaders && requestHeaders.depth != undefined) { + var depth = requestHeaders.depth; + } + + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = "********"; + } + + var bodyStart = body.substr(0, 1024); + Zotero.debug("HTTP " + method + " " + + (depth != undefined ? "(depth " + depth + ") " : "") + + (body.length > 1024 ? + bodyStart + "... (" + body.length + " chars)" : bodyStart) + + " to " + disp.spec); + + if (Zotero.HTTP.browserIsOffline()) { + Zotero.debug("Browser is offline", 2); + return false; + } + + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open(method, uri.spec, true); + + if (requestHeaders) { + for (var header in requestHeaders) { + xmlhttp.setRequestHeader(header, requestHeaders[header]); + } + } + + xmlhttp.setRequestHeader("Content-Type", 'text/xml; charset="utf-8"'); + + xmlhttp.onreadystatechange = function() { + _stateChange(xmlhttp, callback); + }; + + xmlhttp.send(body); + return xmlhttp; + } + + + /** + * Send a WebDAV MKCOL request via XMLHTTPRequest + * + * @param {nsIURI} url + * @param {Function} onDone + * @return {XMLHTTPRequest} + */ + this.WebDAV.doMkCol = function (uri, callback) { + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = "********"; + } + Zotero.debug("HTTP MKCOL " + disp.spec); + + if (Zotero.HTTP.browserIsOffline()) { + return false; + } + + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open('MKCOL', uri.spec, true); + xmlhttp.onreadystatechange = function() { + _stateChange(xmlhttp, callback); + }; + xmlhttp.send(null); + return xmlhttp; + } + + + /** + * Send a WebDAV PUT request via XMLHTTPRequest + * + * @param {nsIURI} url + * @param {String} body String body to PUT + * @param {Function} onDone + * @return {XMLHTTPRequest} + */ + this.WebDAV.doPut = function (uri, body, callback) { + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = "********"; + } + + var bodyStart = "'" + body.substr(0, 1024) + "'"; + Zotero.debug("HTTP PUT " + + (body.length > 1024 ? + bodyStart + "... (" + body.length + " chars)" : bodyStart) + + " to " + disp.spec); + + if (Zotero.HTTP.browserIsOffline()) { + return false; + } + + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open("PUT", uri.spec, true); + // Some servers (e.g., Jungle Disk DAV) return a 200 response code + // with Content-Length: 0, which triggers a "no element found" error + // in Firefox, so we override to text + xmlhttp.overrideMimeType("text/plain"); + xmlhttp.onreadystatechange = function() { + _stateChange(xmlhttp, callback); + }; + xmlhttp.send(body); + return xmlhttp; + } + + + /** + * Send a WebDAV PUT request via XMLHTTPRequest + * + * @param {nsIURI} url + * @param {Function} onDone + * @return {XMLHTTPRequest} + */ + this.WebDAV.doDelete = function (uri, callback) { + // Don't display password in console + var disp = uri.clone(); + if (disp.password) { + disp.password = "********"; + } + + Zotero.debug("WebDAV DELETE to " + disp.spec); + + if (Zotero.HTTP.browserIsOffline()) { + return false; + } + + var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(); + // Prevent certificate/authentication dialogs from popping up + xmlhttp.mozBackgroundRequest = true; + xmlhttp.open("DELETE", uri.spec, true); + // Firefox 3 throws a "no element found" error even with a + // 204 ("No Content") response, so we override to text + xmlhttp.overrideMimeType("text/plain"); + xmlhttp.onreadystatechange = function() { + _stateChange(xmlhttp, callback); + }; + xmlhttp.send(null); + return xmlhttp; + } + + + /** + * Get the Authorization header used by a channel + * + * As of Firefox 3.0.1 subsequent requests to higher-level directories + * seem not to authenticate properly and just return 401s, so this + * can be used to manually include the Authorization header in a request + * + * It can also be used to check whether a request was forced to + * use authentication + * + * @param {nsIChannel} channel + * @return {String|FALSE} Authorization header, or FALSE if none + */ + this.getChannelAuthorization = function (channel) { + try { + channel.QueryInterface(Components.interfaces.nsIHttpChannel); + var authHeader = channel.getRequestHeader("Authorization"); + return authHeader; + } + catch (e) { + Zotero.debug(e); + return false; + } + } + + + /** + * Checks if the browser is currently in "Offline" mode + * + * @type Boolean + */ + this.browserIsOffline = function() { + return Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService).offline; + } + + /** + * Load one or more documents in a hidden browser + * + * @param {String|String[]} urls URL(s) of documents to load + * @param {Function} processor Callback to be executed for each document loaded + * @param {Function} done Callback to be executed after all documents have been loaded + * @param {Function} exception Callback to be executed if an exception occurs + */ + this.processDocuments = function(urls, processor, done, exception) { + /** + * Removes event listener for the load event and deletes the hidden browser + */ + var removeListeners = function() { + hiddenBrowser.removeEventListener(loadEvent, onLoad, true); + Zotero.Browser.deleteHiddenBrowser(hiddenBrowser); + } + + /** + * Loads the next page + * @inner + */ + var doLoad = function() { + if(urls.length) { + var url = urls.shift(); + try { + Zotero.debug("loading "+url); + hiddenBrowser.loadURI(url); + } catch(e) { + removeListeners(); + if(exception) { + exception(e); + return; + } else { + throw(e); + } + } + } else { + removeListeners(); + if(done) done(); + } + }; + + /** + * Callback to be executed when a page load completes + * @inner + */ + var onLoad = function() { + if(hiddenBrowser.contentDocument.location.href == "about:blank") return; + Zotero.debug(hiddenBrowser.contentDocument.location.href+" has been loaded"); + if(hiddenBrowser.contentDocument.location.href != prevUrl) { // Just in case it fires too many times + prevUrl = hiddenBrowser.contentDocument.location.href; + try { + processor(hiddenBrowser.contentDocument); + } catch(e) { + removeListeners(); + if(exception) { + exception(e); + return; + } else { + throw(e); + } + } + doLoad(); + } + }; + + if(typeof(urls) == "string") urls = [urls]; + + var prevUrl; + var loadEvent = Zotero.isFx2 ? "load" : "pageshow"; + + var hiddenBrowser = Zotero.Browser.createHiddenBrowser(); + hiddenBrowser.addEventListener(loadEvent, onLoad, true); + + doLoad(); + } + + /** + * Handler for XMLHttpRequest state change + * + * @param {nsIXMLHttpRequest} XMLHttpRequest whose state just changed + * @param {Function} [onDone] Callback for request completion + * @param {String} [responseCharset] Character set to force on the response + * @private + */ + function _stateChange(xmlhttp, callback, responseCharset, data) { + switch (xmlhttp.readyState){ + // Request not yet made + case 1: + break; + + case 2: + break; + + // Called multiple times while downloading in progress + case 3: + break; + + // Download complete + case 4: + if (callback) { + // Override the content charset + if (responseCharset) { + xmlhttp.channel.contentCharset = responseCharset; + } + callback(xmlhttp, data); + } + break; + } + } +} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/ingester.js b/chrome/content/zotero/xpcom/ingester.js index 9ab9834b68..e061a007c1 100644 --- a/chrome/content/zotero/xpcom/ingester.js +++ b/chrome/content/zotero/xpcom/ingester.js @@ -402,9 +402,9 @@ Zotero.OpenURL = new function() { } if(value.indexOf(",") !== -1) { - item.creators.push(Zotero.Utilities.prototype.cleanAuthor(value, type, true)); + item.creators.push(Zotero.Utilities.cleanAuthor(value, type, true)); } else { - item.creators.push(Zotero.Utilities.prototype.cleanAuthor(value, type, false)); + item.creators.push(Zotero.Utilities.cleanAuthor(value, type, false)); } } else if(key == "rft.aucorp") { complexAu.push({lastName:value, isInstitution:true}); diff --git a/chrome/content/zotero/xpcom/integration_compat.js b/chrome/content/zotero/xpcom/integration_compat.js index 54ba586e9b..2171a93d05 100755 --- a/chrome/content/zotero/xpcom/integration_compat.js +++ b/chrome/content/zotero/xpcom/integration_compat.js @@ -43,7 +43,7 @@ Zotero.Integration.Compat = new function() { function init() { this.env = new Namespace("http://schemas.xmlsoap.org/soap/envelope/"); - if (Zotero.Utilities.HTTP.browserIsOffline()) { + if (Zotero.HTTP.browserIsOffline()) { Zotero.debug('Browser is offline -- not initializing integration HTTP server'); _registerOnlineObserver() return; diff --git a/chrome/content/zotero/xpcom/mime.js b/chrome/content/zotero/xpcom/mime.js index 0d54aac312..4120c171df 100644 --- a/chrome/content/zotero/xpcom/mime.js +++ b/chrome/content/zotero/xpcom/mime.js @@ -281,7 +281,7 @@ Zotero.MIME = new function(){ this.getMIMETypeFromURL = function (url, callback) { - Zotero.Utilities.HTTP.doHead(url, function(xmlhttp) { + Zotero.HTTP.doHead(url, function(xmlhttp) { if (xmlhttp.status != 200 && xmlhttp.status != 204) { Zotero.debug("Attachment HEAD request returned with status code " + xmlhttp.status + " in Zotero.MIME.getMIMETypeFromURL()", 2); diff --git a/chrome/content/zotero/xpcom/progressWindow.js b/chrome/content/zotero/xpcom/progressWindow.js index 797337ec5a..e9c967a88c 100644 --- a/chrome/content/zotero/xpcom/progressWindow.js +++ b/chrome/content/zotero/xpcom/progressWindow.js @@ -210,8 +210,7 @@ Zotero.ProgressWindow = function(_window){ newHB.setAttribute("class", "zotero-progress-item-hbox"); var newDescription = _progressWindow.document.createElement("description"); - var utils = new Zotero.Utilities(); - var parts = utils.parseMarkup(text); + var parts = Zotero.Utilities.parseMarkup(text); for each(var part in parts) { if (part.type == 'text') { var elem = _progressWindow.document.createTextNode(part.text); diff --git a/chrome/content/zotero/xpcom/quickCopy.js b/chrome/content/zotero/xpcom/quickCopy.js index cacede8244..9f5d7bc51f 100644 --- a/chrome/content/zotero/xpcom/quickCopy.js +++ b/chrome/content/zotero/xpcom/quickCopy.js @@ -304,7 +304,7 @@ Zotero.QuickCopy = new function() { } } - var text = Zotero.Utilities.prototype.unescapeHTML(textXML.toXMLString()); + var text = Zotero.Utilities.unescapeHTML(textXML.toXMLString()); text = text.replace(new RegExp(ztab, "g"), " "); if (text.trim) { @@ -312,7 +312,7 @@ Zotero.QuickCopy = new function() { } // TODO: Remove once >=Fx3.5 else { - text = Zotero.Utilities.prototype.trim(text) + text = Zotero.Utilities.trim(text) } // diff --git a/chrome/content/zotero/xpcom/report.js b/chrome/content/zotero/xpcom/report.js index 664cca57e1..1ec3bfe869 100644 --- a/chrome/content/zotero/xpcom/report.js +++ b/chrome/content/zotero/xpcom/report.js @@ -36,7 +36,7 @@ Zotero.Report = new function() { var escapeXML = function (str) { str = str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A'); - return Zotero.Utilities.prototype.htmlSpecialChars(str); + return Zotero.Utilities.htmlSpecialChars(str); } @@ -224,7 +224,7 @@ Zotero.Report = new function() { continue; } - arr[i] = Zotero.Utilities.prototype.trim(arr[i] + ''); + arr[i] = Zotero.Utilities.trim(arr[i] + ''); // Skip empty fields if (!arr[i]) { diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index 60bd6692e9..7912529db0 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -227,7 +227,7 @@ Zotero.Schema = new function(){ return; } - str = Zotero.Utilities.prototype.trim(str); + str = Zotero.Utilities.trim(str); Zotero.debug(str); @@ -906,7 +906,7 @@ Zotero.Schema = new function(){ } } - var get = Zotero.Utilities.HTTP.doGet(url, function (xmlhttp) { + var get = Zotero.HTTP.doGet(url, function (xmlhttp) { var updated = _updateFromRepositoryCallback(xmlhttp, !!force); if (callback) { callback(xmlhttp, updated) @@ -1449,8 +1449,6 @@ Zotero.Schema = new function(){ Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion); - var ZU = new Zotero.Utilities; - Zotero.DB.beginTransaction(); try { @@ -2458,7 +2456,7 @@ Zotero.Schema = new function(){ var rows = Zotero.DB.query("SELECT * FROM itemDataValues WHERE value REGEXP '(^\\s+|\\s+$)'"); if (rows) { for each(var row in rows) { - var trimmed = Zotero.Utilities.prototype.trim(row.value); + var trimmed = Zotero.Utilities.trim(row.value); var valueID = Zotero.DB.valueQuery("SELECT valueID FROM itemDataValues WHERE value=?", trimmed); if (valueID) { Zotero.DB.query("UPDATE OR REPLACE itemData SET valueID=? WHERE valueID=?", [valueID, row.valueID]); @@ -2494,7 +2492,7 @@ Zotero.Schema = new function(){ var rows = Zotero.DB.query("SELECT * FROM tags WHERE name REGEXP '(^\\s+|\\s+$)'"); if (rows) { for each(var row in rows) { - var trimmed = Zotero.Utilities.prototype.trim(row.name); + var trimmed = Zotero.Utilities.trim(row.name); var tagID = Zotero.DB.valueQuery("SELECT tagID FROM tags WHERE name=?", trimmed); if (tagID) { Zotero.DB.query("UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?", [tagID, row.tagID]); diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index 2c72fcf722..177b4f9878 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -114,7 +114,7 @@ Zotero.Search.prototype._set = function (field, val) { return; case 'name': - val = Zotero.Utilities.prototype.trim(val); + val = Zotero.Utilities.trim(val); break; } @@ -964,8 +964,6 @@ Zotero.Search.prototype._idsToTempTable = function (ids) { * Build the SQL query for the search */ Zotero.Search.prototype._buildQuery = function(){ - var utils = new Zotero.Utilities(); - var sql = 'SELECT itemID FROM items'; var sqlParams = []; // Separate ANY conditions for 'required' condition support @@ -1341,13 +1339,13 @@ Zotero.Search.prototype._buildQuery = function(){ // to '00' so that a search for just a year works // (and no year will just not find anything) var sqldate = dateparts.year ? - utils.lpad(dateparts.year, '0', 4) : '____'; + Zotero.Utilities.lpad(dateparts.year, '0', 4) : '____'; sqldate += '-' sqldate += dateparts.month || dateparts.month === 0 ? - utils.lpad(dateparts.month + 1, '0', 2) : alt; + Zotero.Utilities.lpad(dateparts.month + 1, '0', 2) : alt; sqldate += '-'; sqldate += dateparts.day ? - utils.lpad(dateparts.day, '0', 2) : alt; + Zotero.Utilities.lpad(dateparts.day, '0', 2) : alt; if (sqldate!='____-__-__'){ go = true; diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js index af4b429bc8..be66cdc8b5 100644 --- a/chrome/content/zotero/xpcom/storage.js +++ b/chrome/content/zotero/xpcom/storage.js @@ -1067,7 +1067,7 @@ Zotero.Sync.Storage = new function () { var entryName = entries.getNext(); var b64re = /%ZB64$/; if (entryName.match(b64re)) { - var fileName = Zotero.Utilities.Base64.decode( + var fileName = Zotero.Utilities.Internal.Base64.decode( entryName.replace(b64re, '') ); } @@ -1410,7 +1410,7 @@ Zotero.Sync.Storage = new function () { //Zotero.debug("Adding file " + fileName); - fileName = Zotero.Utilities.Base64.encode(fileName) + "%ZB64"; + fileName = Zotero.Utilities.Internal.Base64.encode(fileName) + "%ZB64"; zipWriter.addEntryFile( fileName, Components.interfaces.nsIZipWriter.COMPRESSION_DEFAULT, @@ -1814,7 +1814,7 @@ Zotero.Sync.Storage.QueueManager = new function () { var kbRemaining = Zotero.getString( 'sync.storage.kbRemaining', - Zotero.Utilities.prototype.numberFormat(remaining / 1024, 0) + Zotero.Utilities.numberFormat(remaining / 1024, 0) ); var totalRequests = queue.totalRequests; var filesRemaining = Zotero.getString( diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js index 755dde3e63..508c92b831 100644 --- a/chrome/content/zotero/xpcom/storage/webdav.js +++ b/chrome/content/zotero/xpcom/storage/webdav.js @@ -224,7 +224,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype._getStorageModificationTime = funct var self = this; - Zotero.Utilities.HTTP.doGet(uri, function (req) { + Zotero.HTTP.doGet(uri, function (req) { self._checkResponse(req, self); var funcName = "Zotero.Sync.Storage.WebDAV_getStorageModificationTime()"; @@ -288,7 +288,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype._getStorageModificationTime = funct // Delete invalid .prop files if (invalid) { - var msg = "Invalid mod date '" + Zotero.Utilities.prototype.ellipsize(mtime, 20) + var msg = "Invalid mod date '" + Zotero.Utilities.ellipsize(mtime, 20) + "' for item " + Zotero.Items.getLibraryKeyHash(item); Zotero.debug(msg, 1); Components.utils.reportError(msg); @@ -320,7 +320,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype._setStorageModificationTime = funct {hash} ; - Zotero.Utilities.HTTP.WebDAV.doPut(uri, prop.toXMLString(), function (req) { + Zotero.HTTP.WebDAV.doPut(uri, prop.toXMLString(), function (req) { switch (req.status) { case 200: case 201: @@ -707,7 +707,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.getLastSyncTime = function (callbac if (!this._cachedCredentials) { var self = this; - Zotero.Utilities.HTTP.doOptions(this.rootURI, function (req) { + Zotero.HTTP.doOptions(this.rootURI, function (req) { self._checkResponse(req, self); if (req.status != 200) { @@ -728,7 +728,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.getLastSyncTime = function (callbac var uri = this.rootURI; var successFileURI = uri.clone(); successFileURI.spec += "lastsync"; - Zotero.Utilities.HTTP.doHead(successFileURI, function (req) { + Zotero.HTTP.doHead(successFileURI, function (req) { var ts = undefined; try { if (req.responseText) { @@ -783,7 +783,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.setLastSyncTime = function (callbac var self = this; - Zotero.Utilities.HTTP.WebDAV.doPut(successFileURI, " ", function (req) { + Zotero.HTTP.WebDAV.doPut(successFileURI, " ", function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -865,7 +865,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.checkServer = function (callback) { var self = this; // Test whether URL is WebDAV-enabled - var request = Zotero.Utilities.HTTP.doOptions(uri, function (req) { + var request = Zotero.HTTP.doOptions(uri, function (req) { // Timeout if (req.status == 0) { self._checkResponse(req, self); @@ -904,12 +904,12 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.checkServer = function (callback) { // Get the Authorization header used in case we need to do a request // on the parent below - var channelAuthorization = Zotero.Utilities.HTTP.getChannelAuthorization(req.channel); + var channelAuthorization = Zotero.HTTP.getChannelAuthorization(req.channel); var headers = { Depth: 0 }; // Test whether Zotero directory exists - Zotero.Utilities.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { + Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -918,7 +918,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.checkServer = function (callback) { // Test if Zotero directory is writable var testFileURI = uri.clone(); testFileURI.spec += "zotero-test-file"; - Zotero.Utilities.HTTP.WebDAV.doPut(testFileURI, " ", function (req) { + Zotero.HTTP.WebDAV.doPut(testFileURI, " ", function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -926,7 +926,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.checkServer = function (callback) { case 200: case 201: case 204: - Zotero.Utilities.HTTP.doHead( + Zotero.HTTP.doHead( testFileURI, function (req) { Zotero.debug(req.responseText); @@ -935,7 +935,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.checkServer = function (callback) { switch (req.status) { case 200: // Delete test file - Zotero.Utilities.HTTP.WebDAV.doDelete( + Zotero.HTTP.WebDAV.doDelete( testFileURI, function (req) { Zotero.debug(req.responseText); @@ -1034,7 +1034,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.checkServer = function (callback) { // Zotero directory wasn't found, so see if at least // the parent directory exists - Zotero.Utilities.HTTP.WebDAV.doProp("PROPFIND", parentURI, xmlstr, + Zotero.HTTP.WebDAV.doProp("PROPFIND", parentURI, xmlstr, function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -1316,7 +1316,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.purgeOrphanedStorageFiles = functio var self = this; - Zotero.Utilities.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { + Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (req) { Zotero.debug(req.responseText); var funcName = "Zotero.Sync.Storage.purgeOrphanedStorageFiles()"; @@ -1413,7 +1413,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype.purgeOrphanedStorageFiles = functio */ Zotero.Sync.Storage.Session.WebDAV.prototype._createServerDirectory = function (callback) { var uri = this.rootURI; - Zotero.Utilities.HTTP.WebDAV.doMkCol(uri, function (req) { + Zotero.HTTP.WebDAV.doMkCol(uri, function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -1536,7 +1536,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype._deleteStorageFiles = function (fil deleteURI.QueryInterface(Components.interfaces.nsIURL); deleteURI.fileName = files[i]; deleteURI.QueryInterface(Components.interfaces.nsIURI); - Zotero.Utilities.HTTP.WebDAV.doDelete(deleteURI, function (req) { + Zotero.HTTP.WebDAV.doDelete(deleteURI, function (req) { switch (req.status) { case 204: // IIS 5.1 and Sakai return 200 @@ -1586,7 +1586,7 @@ Zotero.Sync.Storage.Session.WebDAV.prototype._deleteStorageFiles = function (fil } // Delete property file - Zotero.Utilities.HTTP.WebDAV.doDelete(deletePropURI, function (req) { + Zotero.HTTP.WebDAV.doDelete(deletePropURI, function (req) { switch (req.status) { case 204: // IIS 5.1 and Sakai return 200 diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js index 5fc4d2afa3..c7ccae5bc9 100644 --- a/chrome/content/zotero/xpcom/storage/zfs.js +++ b/chrome/content/zotero/xpcom/storage/zfs.js @@ -111,7 +111,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype._getStorageFileInfo = function (item, var self = this; - Zotero.Utilities.HTTP.doGet(uri, function (req) { + Zotero.HTTP.doGet(uri, function (req) { var funcName = "Zotero.Sync.Storage.Session.ZFS._getStorageFileInfo()"; if (req.status == 404) { @@ -467,7 +467,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype._getFileUploadParameters = function (i } var mtime = item.attachmentModificationTime; - var hash = Zotero.Utilities.prototype.md5(file); + var hash = Zotero.Utilities.Internal.md5(file); var body = "md5=" + hash + "&filename=" + encodeURIComponent(filename) + "&filesize=" + file.fileSize + "&mtime=" + mtime; @@ -477,7 +477,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype._getFileUploadParameters = function (i var self = this; - Zotero.Utilities.HTTP.doPost(uri, body, function (req) { + Zotero.HTTP.doPost(uri, body, function (req) { var funcName = "Zotero.Sync.Storage.Session.ZFS._getFileUploadParameters()"; if (req.status == 413) { @@ -571,7 +571,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype._getFileUploadParameters = function (i try { // Strip XML declaration and convert to E4X - var xml = new XML(Zotero.Utilities.prototype.trim(req.responseText.replace(/<\?xml.*\?>/, ''))); + var xml = new XML(Zotero.Utilities.trim(req.responseText.replace(/<\?xml.*\?>/, ''))); } catch (e) { self.onError("Invalid response retrieving file upload parameters"); @@ -748,7 +748,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype._onUploadComplete = function (httpRequ var self = this; // Register upload on server - Zotero.Utilities.HTTP.doPost(uri, body, function (req) { + Zotero.HTTP.doPost(uri, body, function (req) { if (req.status != 204) { var msg = "Unexpected file registration status " + req.status + " in Zotero.Sync.Storage._onUploadComplete()" @@ -832,7 +832,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype.getLastSyncTime = function (callback) var uri = this.rootURI; // TODO: move to root uri uri.spec += "?auth=1"; - Zotero.Utilities.HTTP.doGet(uri, function (req) { + Zotero.HTTP.doGet(uri, function (req) { if (req.status != 200) { var msg = "Unexpected status code " + req.status + " caching " + "authentication credentials in Zotero.Sync.Storage.Session.ZFS.getLastSyncTime()"; @@ -847,7 +847,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype.getLastSyncTime = function (callback) return; } - Zotero.Utilities.HTTP.doGet(successFileURI, function (req) { + Zotero.HTTP.doGet(successFileURI, function (req) { if (req.responseText) { Zotero.debug(req.responseText); } @@ -910,7 +910,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype.setLastSyncTime = function (callback, var self = this; - Zotero.Utilities.HTTP.doPost(successFileURI, "", function (req) { + Zotero.HTTP.doPost(successFileURI, "", function (req) { Zotero.debug(req.responseText); Zotero.debug(req.status); @@ -971,7 +971,7 @@ Zotero.Sync.Storage.Session.ZFS.prototype.purgeDeletedStorageFiles = function (c var self = this; - Zotero.Utilities.HTTP.doPost(uri, "", function (xmlhttp) { + Zotero.HTTP.doPost(uri, "", function (xmlhttp) { if (xmlhttp.status != 204) { if (callback) { callback(false); diff --git a/chrome/content/zotero/xpcom/style.js b/chrome/content/zotero/xpcom/style.js index 45d05b0ba1..2cb4625db6 100644 --- a/chrome/content/zotero/xpcom/style.js +++ b/chrome/content/zotero/xpcom/style.js @@ -253,7 +253,7 @@ Zotero.Styles = new function() { if(source && !_styles[source]) { // need to fetch source if(source.substr(0, 7) == "http://" || source.substr(0, 8) == "https://") { - Zotero.Utilities.HTTP.doGet(source, function(xmlhttp) { + Zotero.HTTP.doGet(source, function(xmlhttp) { var success = false; var error = null; try { diff --git a/chrome/content/zotero/xpcom/sync.js b/chrome/content/zotero/xpcom/sync.js index 214dd4fb52..a616b37547 100644 --- a/chrome/content/zotero/xpcom/sync.js +++ b/chrome/content/zotero/xpcom/sync.js @@ -395,8 +395,6 @@ Zotero.Sync.EventListener = new function () { var isItem = Zotero.Sync.getObjectTypeName(objectTypeID) == 'item'; - var ZU = new Zotero.Utilities; - Zotero.DB.beginTransaction(); if (event == 'delete') { @@ -504,7 +502,7 @@ Zotero.Sync.Runner = new function () { this.sync = function (background) { _warning = null; - if (Zotero.Utilities.HTTP.browserIsOffline()){ + if (Zotero.HTTP.browserIsOffline()){ this.clearSyncTimeout(); // DEBUG: necessary? var msg = "Zotero cannot sync while Firefox is in offline mode."; var e = new Zotero.Error(msg, 0, { dialogButtonText: null }) @@ -1241,7 +1239,7 @@ Zotero.Sync.Server = new function () { Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.loggingIn')); - Zotero.Utilities.HTTP.doPost(url, body, function (xmlhttp) { + Zotero.HTTP.doPost(url, body, function (xmlhttp) { _checkResponse(xmlhttp); var response = xmlhttp.responseXML.childNodes[0]; @@ -1325,7 +1323,7 @@ Zotero.Sync.Server = new function () { Zotero.Sync.Runner.setSyncStatus(Zotero.getString('sync.status.gettingUpdatedData')); - Zotero.Utilities.HTTP.doPost(url, body, function (xmlhttp) { + Zotero.HTTP.doPost(url, body, function (xmlhttp) { Zotero.debug(xmlhttp.responseText); _checkResponse(xmlhttp); @@ -1352,7 +1350,7 @@ Zotero.Sync.Server = new function () { _error(response.firstChild.firstChild.nodeValue); } - var xml = Zotero.Utilities.prototype.trim(xmlhttp.responseText.replace(/<\?xml.*\?>\s*/, '')); + var xml = Zotero.Utilities.trim(xmlhttp.responseText.replace(/<\?xml.*\?>\s*/, '')); // Strip XML declaration and convert to E4X xml = new XML(xml); @@ -1495,7 +1493,7 @@ Zotero.Sync.Server = new function () { var url = _serverURL + 'uploadstatus'; var body = _apiVersionComponent + '&' + Zotero.Sync.Server.sessionIDComponent; - Zotero.Utilities.HTTP.doPost(url, body, function (xmlhttp) { + Zotero.HTTP.doPost(url, body, function (xmlhttp) { uploadCallback(xmlhttp); }); break; @@ -1559,7 +1557,7 @@ Zotero.Sync.Server = new function () { + " (gzipped from " + oldLen + " bytes; " + savings + "% savings)"); - if (Zotero.Utilities.HTTP.browserIsOffline()) { + if (Zotero.HTTP.browserIsOffline()) { Zotero.debug('Browser is offline'); return false; } @@ -1609,7 +1607,7 @@ Zotero.Sync.Server = new function () { // Don't compress upload data else { - Zotero.Utilities.HTTP.doPost(url, body, uploadCallback); + Zotero.HTTP.doPost(url, body, uploadCallback); } } catch (e) { @@ -1637,7 +1635,7 @@ Zotero.Sync.Server = new function () { var body = _apiVersionComponent + '&' + Zotero.Sync.Server.sessionIDComponent; - Zotero.Utilities.HTTP.doPost(url, body, function (xmlhttp) { + Zotero.HTTP.doPost(url, body, function (xmlhttp) { if (_invalidSession(xmlhttp)) { Zotero.debug("Invalid session ID -- logging in"); _sessionID = false; @@ -1698,7 +1696,7 @@ Zotero.Sync.Server = new function () { _sessionID = null; - Zotero.Utilities.HTTP.doPost(url, body, function (xmlhttp) { + Zotero.HTTP.doPost(url, body, function (xmlhttp) { _checkResponse(xmlhttp); Zotero.debug(xmlhttp.responseText); @@ -2223,7 +2221,7 @@ Zotero.Sync.Server = new function () { if (extraInfo) { // Server errors will generally be HTML - extraInfo = Zotero.Utilities.prototype.unescapeHTML(extraInfo); + extraInfo = Zotero.Utilities.unescapeHTML(extraInfo); Components.utils.reportError(extraInfo); } @@ -2853,12 +2851,12 @@ Zotero.Sync.Server.Data = new function() { } } // Add - toAdd = Zotero.Utilities.prototype.arrayDiff(toAdd, existing); + toAdd = Zotero.Utilities.arrayDiff(toAdd, existing); var changed = toAdd.length > 0; existing = existing.concat(toAdd); var origLen = existing.length; // Remove - existing = Zotero.Utilities.prototype.arrayDiff(existing, toRemove); + existing = Zotero.Utilities.arrayDiff(existing, toRemove); changed = changed || origLen != existing.length; // Set if (changed) { @@ -3220,7 +3218,7 @@ Zotero.Sync.Server.Data = new function() { if (!itemIDs) { return false; } - var newItemIDs = Zotero.Utilities.prototype.arrayDiff(itemIDs, childItems); + var newItemIDs = Zotero.Utilities.arrayDiff(itemIDs, childItems); if (itemIDs.length == newItemIDs.length) { return false; } @@ -3462,7 +3460,7 @@ Zotero.Sync.Server.Data = new function() { }; if (type == 'item') { - if (!Zotero.Utilities.prototype.isEmpty(changedCreators)) { + if (!Zotero.Utilities.isEmpty(changedCreators)) { io.dataIn.changedCreators = changedCreators; } } diff --git a/chrome/content/zotero/xpcom/timeline.js b/chrome/content/zotero/xpcom/timeline.js index 7c3f947bf6..ca07421d84 100644 --- a/chrome/content/zotero/xpcom/timeline.js +++ b/chrome/content/zotero/xpcom/timeline.js @@ -29,8 +29,7 @@ Zotero.Timeline = new function () { this.generateXMLList = generateXMLList; function generateXMLDetails(items, dateType) { - var ZU = new Zotero.Utilities(); - var escapeXML = ZU.htmlSpecialChars; + var escapeXML = Zotero.Utilities.htmlSpecialChars; var content = '\n'; for each(var arr in items) { diff --git a/chrome/content/zotero/xpcom/translate.js b/chrome/content/zotero/xpcom/translate.js index a0592b0c9a..5bc6aae70a 100644 --- a/chrome/content/zotero/xpcom/translate.js +++ b/chrome/content/zotero/xpcom/translate.js @@ -959,7 +959,7 @@ Zotero.Translate.prototype._generateSandbox = function() { // add utilities this._sandbox.Zotero.Utilities = new Zotero.Utilities.Translate(this); - this._sandbox.Zotero.Utilities.HTTP = this._sandbox.Zotero.Utilities; + this._sandbox.Zotero.HTTP = this._sandbox.Zotero.Utilities; if(this.type == "export") { // add routines to retrieve items and collections @@ -1319,7 +1319,7 @@ Zotero.Translate.prototype._reportTranslationFailure = function(errorData) { "&lastUpdated=" + encodeURIComponent(this.translator[0].lastUpdated) + "&diagnostic=" + encodeURIComponent(Zotero.getSystemInfo()) + "&errorData=" + encodeURIComponent(errorData); - Zotero.Utilities.HTTP.doPost("http://www.zotero.org/repo/report", postBody); + Zotero.HTTP.doPost("http://www.zotero.org/repo/report", postBody); } } diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index fe03aeb30c..6da573f0a0 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -29,610 +29,484 @@ /** * @class Functions for text manipulation and other miscellaneous purposes */ -Zotero.Utilities = function () {} - -/** - * Cleans extraneous punctuation off a creator name and parse into first and last name - * - * @param {String} author Creator string - * @param {String} type Creator type string (e.g., "author" or "editor") - * @param {Boolean} useComma Whether the creator string is in inverted (Last, First) format - * @return {Object} firstName, lastName, and creatorType - */ -Zotero.Utilities.prototype.cleanAuthor = function(author, type, useComma) { - const allCapsRe = /^[A-Z\u0400-\u042f]+$/; - - if(typeof(author) != "string") { - throw "cleanAuthor: author must be a string"; - } - - author = author.replace(/^[\s\.\,\/\[\]\:]+/, ''); - author = author.replace(/[\s\,\/\[\]\:\.]+$/, ''); - author = author.replace(/ +/, ' '); - if(useComma) { - // Add spaces between periods - author = author.replace(/\.([^ ])/, ". $1"); +Zotero.Utilities = { + /** + * Cleans extraneous punctuation off a creator name and parse into first and last name + * + * @param {String} author Creator string + * @param {String} type Creator type string (e.g., "author" or "editor") + * @param {Boolean} useComma Whether the creator string is in inverted (Last, First) format + * @return {Object} firstName, lastName, and creatorType + */ + "cleanAuthor":function(author, type, useComma) { + const allCapsRe = /^[A-Z\u0400-\u042f]+$/; - var splitNames = author.split(/, ?/); - if(splitNames.length > 1) { - var lastName = splitNames[0]; - var firstName = splitNames[1]; + if(typeof(author) != "string") { + throw "cleanAuthor: author must be a string"; + } + + author = author.replace(/^[\s\.\,\/\[\]\:]+/, ''); + author = author.replace(/[\s\,\/\[\]\:\.]+$/, ''); + author = author.replace(/ +/, ' '); + if(useComma) { + // Add spaces between periods + author = author.replace(/\.([^ ])/, ". $1"); + + var splitNames = author.split(/, ?/); + if(splitNames.length > 1) { + var lastName = splitNames[0]; + var firstName = splitNames[1]; + } else { + var lastName = author; + } } else { - var lastName = author; + var spaceIndex = author.lastIndexOf(" "); + var lastName = author.substring(spaceIndex+1); + var firstName = author.substring(0, spaceIndex); } - } else { - var spaceIndex = author.lastIndexOf(" "); - var lastName = author.substring(spaceIndex+1); - var firstName = author.substring(0, spaceIndex); - } - - if(firstName && allCapsRe.test(firstName) && - firstName.length < 4 && - (firstName.length == 1 || lastName.toUpperCase() != lastName)) { - // first name is probably initials - var newFirstName = ""; - for(var i=0; i]*>/gi, "\n"); - return x.replace(/<[^>]+>/g, ""); -} - - -Zotero.Utilities.prototype.cleanDOI = function(/**String**/ x) { - if(typeof(x) != "string") { - throw "cleanDOI: argument must be a string"; - } - - return x.match(/10\.[^\s\/]+\/[^\s]+/); -} - - -Zotero.Utilities.prototype.text2html = function (/**String**/ str, singleNewlineIsParagraph) { - str = Zotero.Utilities.prototype.htmlSpecialChars(str); - - // \n =>

- if (singleNewlineIsParagraph) { - str = '

' - + str.replace(/\n/g, '

') - .replace(/\t/g, '    ') - .replace(/ /g, '  ') - + '

'; - } - // \n\n =>

, \n =>
- else { - str = Zotero.Utilities.prototype.htmlSpecialChars(str); - str = '

' - + str.replace(/\n\n/g, '

') - .replace(/\n/g, '
') - .replace(/\t/g, '    ') - .replace(/ /g, '  ') - + '

'; - } - return str.replace(/

\s*<\/p>/g, '

 

'); -} - - -/** - * Encode special XML/HTML characters
- *
- * Certain entities can be inserted manually:
- *
 <ZOTEROBREAK/> => <br/>
- * <ZOTEROHELLIP/> => &#8230;
- * @type String - */ -Zotero.Utilities.prototype.htmlSpecialChars = function(/**String*/ str) { - if (typeof str != 'string') { - throw "Argument '" + str + "' must be a string in Zotero.Utilities.htmlSpecialChars()"; - } - - if (!str) { - return ''; - } - - var chars = ['&', '"',"'",'<','>']; - var entities = ['amp', 'quot', 'apos', 'lt', 'gt']; - - var newString = str; - for (var i = 0; i < chars.length; i++) { - var re = new RegExp(chars[i], 'g'); - newString = newString.replace(re, '&' + entities[i] + ';'); - } - - newString = newString.replace(/<ZOTERO([^\/]+)\/>/g, function (str, p1, offset, s) { - switch (p1) { - case 'BREAK': - return '
'; - case 'HELLIP': - return '…'; - default: - return p1; + /** + * Removes leading and trailing whitespace from a string + * @type String + */ + "trim":function(/**String*/ s) { + if (typeof(s) != "string") { + Zotero.debug(s); + throw "trim: argument must be a string"; } - }); + + s = s.replace(/^\s+/, ""); + return s.replace(/\s+$/, ""); + }, + + /** + * Cleans whitespace off a string and replaces multiple spaces with one + * @type String + */ + "trimInternal":function(/**String*/ s) { + if (typeof(s) != "string") { + throw "trimInternal: argument must be a string"; + } + + s = s.replace(/[\xA0\r\n\s]+/g, " "); + return this.trim(s); + }, + + /** + * Cleans any non-word non-parenthesis characters off the ends of a string + * @type String + */ + "superCleanString":function(/**String*/ x) { + if(typeof(x) != "string") { + throw "superCleanString: argument must be a string"; + } + + var x = x.replace(/^[\x00-\x27\x29-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/, ""); + return x.replace(/[\x00-\x28\x2A-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+$/, ""); + }, - return newString; -} + /** + * Eliminates HTML tags, replacing <br>s with newlines + * @type String + */ + "cleanTags":function(/**String*/ x) { + if(typeof(x) != "string") { + throw "cleanTags: argument must be a string"; + } + + x = x.replace(/]*>/gi, "\n"); + return x.replace(/<[^>]+>/g, ""); + }, -/** - * Decodes HTML entities within a string, returning plain text - * @type String - */ -Zotero.Utilities.prototype.unescapeHTML = function(/**String*/ str) { - var nsISUHTML = Components.classes["@mozilla.org/feed-unescapehtml;1"] - .getService(Components.interfaces.nsIScriptableUnescapeHTML); - return nsISUHTML.unescape(str); -} + /** + * Strip info:doi prefix and any suffixes from a DOI + * @type String + */ + "cleanDOI":function(/**String**/ x) { + if(typeof(x) != "string") { + throw "cleanDOI: argument must be a string"; + } + + return x.match(/10\.[^\s\/]+\/[^\s]+/); + }, + /** + * Convert plain text to HTML by replacing special characters and replacing newlines with BRs or + * P tags + * @param {String} str Plain text string + * @param {Boolean} singleNewlineIsParagraph Whether single newlines should be considered as + * paragraphs. If true, each newline is replaced with a P tag. If false, double newlines + * are replaced with P tags, while single newlines are replaced with BR tags. + * @type String + */ + "text2html":function (/**String**/ str, /**Boolean**/ singleNewlineIsParagraph) { + str = Zotero.Utilities.htmlSpecialChars(str); + + // \n =>

+ if (singleNewlineIsParagraph) { + str = '

' + + str.replace(/\n/g, '

') + .replace(/\t/g, '    ') + .replace(/ /g, '  ') + + '

'; + } + // \n\n =>

, \n =>
+ else { + str = Zotero.Utilities.htmlSpecialChars(str); + str = '

' + + str.replace(/\n\n/g, '

') + .replace(/\n/g, '
') + .replace(/\t/g, '    ') + .replace(/ /g, '  ') + + '

'; + } + return str.replace(/

\s*<\/p>/g, '

 

'); + }, -/** - * Wrap URLs and DOIs in links in plain text - * - * Ignore URLs preceded by '>', just in case there are already links - */ -Zotero.Utilities.prototype.autoLink = function (str) { - // "http://www.google.com." - // "http://www.google.com. " - // "" (and other characters, with or without a space after) - str = str.replace(/([^>])(https?:\/\/[^\s]+)([\."'>:\]\)](\s|$))/g, '$1$2$3'); - // "http://www.google.com" - // "http://www.google.com " - str = str.replace(/([^">])(https?:\/\/[^\s]+)(\s|$)/g, '$1$2$3'); + /** + * Encode special XML/HTML characters
+ *
+ * Certain entities can be inserted manually:
+ *
 <ZOTEROBREAK/> => <br/>
+	 * <ZOTEROHELLIP/> => &#8230;
+ * @type String + */ + "htmlSpecialChars":function(/**String*/ str) { + if (typeof str != 'string') { + throw "Argument '" + str + "' must be a string in Zotero.Utilities.htmlSpecialChars()"; + } + + if (!str) { + return ''; + } + + var chars = ['&', '"',"'",'<','>']; + var entities = ['amp', 'quot', 'apos', 'lt', 'gt']; + + var newString = str; + for (var i = 0; i < chars.length; i++) { + var re = new RegExp(chars[i], 'g'); + newString = newString.replace(re, '&' + entities[i] + ';'); + } + + newString = newString.replace(/<ZOTERO([^\/]+)\/>/g, function (str, p1, offset, s) { + switch (p1) { + case 'BREAK': + return '
'; + case 'HELLIP': + return '…'; + default: + return p1; + } + }); + + return newString; + }, + + /** + * Decodes HTML entities within a string, returning plain text + * @type String + */ + "unescapeHTML":function(/**String*/ str) { + var nsISUHTML = Components.classes["@mozilla.org/feed-unescapehtml;1"] + .getService(Components.interfaces.nsIScriptableUnescapeHTML); + return nsISUHTML.unescape(str); + }, - // DOI - str = str.replace(/(doi:[ ]*)(10\.[^\s]+[0-9a-zA-Z])/g, '$1$2'); - return str; -} - - -/** - * Parses a text string for HTML/XUL markup and returns an array of parts. Currently only finds - * HTML links (<a> tags) - * - * @return {Array} An array of objects with the following form:
- *
   {
- *         type: 'text'|'link',
- *         text: "text content",
- *         [ attributes: { key1: val [ , key2: val, ...] }
- *    }
- */ -Zotero.Utilities.prototype.parseMarkup = function(/**String*/ str) { - var parts = []; - var splits = str.split(/(]+>[^<]*<\/a>)/); + /** + * Wrap URLs and DOIs in links in plain text + * + * Ignore URLs preceded by '>', just in case there are already links + * @type String + */ + "autoLink":function (/**String**/ str) { + // "http://www.google.com." + // "http://www.google.com. " + // "" (and other characters, with or without a space after) + str = str.replace(/([^>])(https?:\/\/[^\s]+)([\."'>:\]\)](\s|$))/g, '$1$2$3'); + // "http://www.google.com" + // "http://www.google.com " + str = str.replace(/([^">])(https?:\/\/[^\s]+)(\s|$)/g, '$1$2$3'); + + // DOI + str = str.replace(/(doi:[ ]*)(10\.[^\s]+[0-9a-zA-Z])/g, '$1$2'); + return str; + }, - for each(var split in splits) { - // Link - if (split.indexOf(']+)>([^<]*)<\/a>/); - if (matches) { - // Attribute pairs - var attributes = {}; - var pairs = matches[1].match(/([^ =]+)="([^"]+")/g); - for each (var pair in pairs) { - var [key, val] = pair.split(/=/); - attributes[key] = val.substr(1, val.length - 2); + /** + * Parses a text string for HTML/XUL markup and returns an array of parts. Currently only finds + * HTML links (<a> tags) + * + * @return {Array} An array of objects with the following form:
+ *
   {
+	 *         type: 'text'|'link',
+	 *         text: "text content",
+	 *         [ attributes: { key1: val [ , key2: val, ...] }
+	 *    }
+ */ + "parseMarkup":function(/**String*/ str) { + var parts = []; + var splits = str.split(/(
]+>[^<]*<\/a>)/); + + for each(var split in splits) { + // Link + if (split.indexOf(']+)>([^<]*)<\/a>/); + if (matches) { + // Attribute pairs + var attributes = {}; + var pairs = matches[1].match(/([^ =]+)="([^"]+")/g); + for each (var pair in pairs) { + var [key, val] = pair.split(/=/); + attributes[key] = val.substr(1, val.length - 2); + } + + parts.push({ + type: 'link', + text: matches[2], + attributes: attributes + }); + continue; } - - parts.push({ - type: 'link', - text: matches[2], - attributes: attributes - }); - continue; + } + + parts.push({ + type: 'text', + text: split + }); + } + + return parts; + }, + + /** + * Calculates the Levenshtein distance between two strings + * @type Number + */ + "levenshtein":function (/**String*/ a, /**String**/ b) { + var aLen = a.length; + var bLen = b.length; + + var arr = new Array(aLen+1); + var i, j, cost; + + for (i = 0; i <= aLen; i++) { + arr[i] = new Array(bLen); + arr[i][0] = i; + } + + for (j = 0; j <= bLen; j++) { + arr[0][j] = j; + } + + for (i = 1; i <= aLen; i++) { + for (j = 1; j <= bLen; j++) { + cost = (a[i-1] == b[j-1]) ? 0 : 1; + arr[i][j] = Math.min(arr[i-1][j] + 1, Math.min(arr[i][j-1] + 1, arr[i-1][j-1] + cost)); } } - parts.push({ - type: 'text', - text: split - }); - } + return arr[aLen][bLen]; + }, - return parts; -} - -Zotero.Utilities.prototype.levenshtein = function (a, b) { - var aLen = a.length; - var bLen = b.length; - - var arr = new Array(aLen+1); - var i, j, cost; - - for (i = 0; i <= aLen; i++) { - arr[i] = new Array(bLen); - arr[i][0] = i; - } - - for (j = 0; j <= bLen; j++) { - arr[0][j] = j; - } - - for (i = 1; i <= aLen; i++) { - for (j = 1; j <= bLen; j++) { - cost = (a[i-1] == b[j-1]) ? 0 : 1; - arr[i][j] = Math.min(arr[i-1][j] + 1, Math.min(arr[i][j-1] + 1, arr[i-1][j-1] + cost)); + /** + * Test if an object is empty + * + * @param {Object} obj + * @type Boolean + */ + "isEmpty":function (obj) { + for (var i in obj) { + return false; } - } - - return arr[aLen][bLen]; -} + return true; + }, - -/** - * Test if an object is empty - * - * @param {Object} - */ -Zotero.Utilities.prototype.isEmpty = function (obj) { - for (var i in obj) { - return false; - } - return true; -} - - -/** - * Compares an array with another and returns an array with - * the values from array1 that don't exist in array2 - * - * @param {Array} array1 - * @param {Array} array2 - * @param {Boolean} useIndex If true, return an array containing just - * the index of array2's elements; - * otherwise return the values - */ -Zotero.Utilities.prototype.arrayDiff = function(array1, array2, useIndex) { - if (array1.constructor.name != 'Array') { - throw ("array1 is not an array in Zotero.Utilities.arrayDiff() (" + array1 + ")"); - } - if (array2.constructor.name != 'Array') { - throw ("array2 is not an array in Zotero.Utilities.arrayDiff() (" + array2 + ")"); - } - - var val, pos, vals = []; - for (var i=0; i> 4; - ascii.push(String.fromCharCode(tens + (tens > 9 ? 87 : 48)) + String.fromCharCode(ones + (ones > 9 ? 87 : 48))); - } - return ascii.join(''); -} - - -/** - * Parse a page range - * - * @param {String} Page range to parse - * @return {Integer[]} Start and end pages - */ -Zotero.Utilities.prototype.getPageRange = function(pages) { - const pageRangeRegexp = /^\s*([0-9]+) ?[-\u2013] ?([0-9]+)\s*$/ - - var pageNumbers; - var m = pageRangeRegexp.exec(pages); - if(m) { - // A page range - pageNumbers = [m[1], m[2]]; - } else { - // Assume start and end are the same - pageNumbers = [pages, pages]; - } - return pageNumbers; -} - -/** - * Pads a number or other string with a given string on the left - * - * @param {String} string String to pad - * @param {String} pad String to use as padding - * @length {Integer} length Length of new padded string - * @type String - */ -Zotero.Utilities.prototype.lpad = function(string, pad, length) { - string = string ? string + '' : ''; - while(string.length < length) { - string = pad + string; - } - return string; -} - - -/** - * Shorten and add an ellipsis to a string if necessary - * - * @param {String} str - * @param {Integer} len - * @param {Boolean} [countChars=false] - */ -Zotero.Utilities.prototype.ellipsize = function (str, len, countChars) { - if (!len) { - throw ("Length not specified in Zotero.Utilities.ellipsize()"); - } - if (str.length > len) { - return str.substr(0, len) + '...' + (countChars ? ' (' + str.length + ' chars)' : ''); - } - return str; -} - - -/** - * Port of PHP's number_format() - * - * MIT Licensed - * - * From http://kevin.vanzonneveld.net - * + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + bugfix by: Michael White (http://getsprink.com) - * + bugfix by: Benjamin Lupton - * + bugfix by: Allan Jensen (http://www.winternet.no) - * + revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) - * + bugfix by: Howard Yeend - * * example 1: number_format(1234.5678, 2, '.', ''); - * * returns 1: 1234.57 - */ -Zotero.Utilities.prototype.numberFormat = function (number, decimals, dec_point, thousands_sep) { - var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals; - var d = dec_point == undefined ? "." : dec_point; - var t = thousands_sep == undefined ? "," : thousands_sep, s = n < 0 ? "-" : ""; - var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0; - - return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); -} - - -/** - * Tests if an item type exists - * - * @param {String} type Item type - * @type Boolean - */ -Zotero.Utilities.prototype.itemTypeExists = function(type) { - if(Zotero.ItemTypes.getID(type)) { - return true; - } else { - return false; - } -} - -/** - * Find valid creator types for a given item type - * - * @param {String} type Item type - * @return {String[]} Creator types - */ -Zotero.Utilities.prototype.getCreatorsForType = function(type) { - var types = Zotero.CreatorTypes.getTypesForItemType(Zotero.ItemTypes.getID(type)); - var cleanTypes = new Array(); - for each(var type in types) { - cleanTypes.push(type.name); - } - return cleanTypes; -} - -/** - * Gets a creator type name, localized to the current locale - * - * @param {String} type Creator type - * @param {String} Localized creator type - * @type Boolean - */ -Zotero.Utilities.prototype.getLocalizedCreatorType = function(type) { - try { - return Zotero.getString("creatorTypes."+type); - } catch(e) { - return false; - } -} - -/** - * Cleans a title, converting it to title case and replacing " :" with ":" - * - * @param {String} string - * @param {Boolean} force Forces title case conversion, even if the capitalizeTitles pref is off - * @type String - */ -Zotero.Utilities.prototype.capitalizeTitle = function(string, force) { - string = this.trimInternal(string); - if(Zotero.Prefs.get('capitalizeTitles') || force) { - // fix colons - string = string.replace(" : ", ": ", "g"); - string = Zotero.Text.titleCase(string.replace(/ : /g, ": ")); - } - return string; -} - - -/** - * Run sets of data through multiple asynchronous callbacks - * - * Each callback is passed the current set and a callback to call when done - * - * @param {Object[]} sets Sets of data - * @param {Function[]} callbacks - * @param {Function} onDone Function to call when done - */ -Zotero.Utilities.prototype.processAsync = function (sets, callbacks, onDone) { - var currentSet; - var index = 0; - - var nextSet = function () { - if (!sets.length) { - onDone(); - return; + var val, pos, vals = []; + for (var i=0; i len) { + return str.substr(0, len) + '...' + (countChars ? ' (' + str.length + ' chars)' : ''); + } + return str; + }, - // Add a final callback to proceed to the next set - callbacks[callbacks.length] = function () { + /** + * Port of PHP's number_format() + * + * MIT Licensed + * + * From http://kevin.vanzonneveld.net + * + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfix by: Michael White (http://getsprink.com) + * + bugfix by: Benjamin Lupton + * + bugfix by: Allan Jensen (http://www.winternet.no) + * + revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) + * + bugfix by: Howard Yeend + * * example 1: number_format(1234.5678, 2, '.', ''); + * * returns 1: 1234.57 + */ + "numberFormat":function (number, decimals, dec_point, thousands_sep) { + var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals; + var d = dec_point == undefined ? "." : dec_point; + var t = thousands_sep == undefined ? "," : thousands_sep, s = n < 0 ? "-" : ""; + var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0; + + return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); + }, + + /** + * Cleans a title, converting it to title case and replacing " :" with ":" + * + * @param {String} string + * @param {Boolean} force Forces title case conversion, even if the capitalizeTitles pref is off + * @type String + */ + "capitalizeTitle":function(string, force) { + string = this.trimInternal(string); + string = string.replace(" : ", ": ", "g"); + if(Zotero.Prefs.get('capitalizeTitles') || force) { + // fix colons + string = Zotero.Text.titleCase(string); + } + return string; + }, + + /** + * Run sets of data through multiple asynchronous callbacks + * + * Each callback is passed the current set and a callback to call when done + * + * @param {Object[]} sets Sets of data + * @param {Function[]} callbacks + * @param {Function} onDone Function to call when done + */ + "processAsync":function (sets, callbacks, onDone) { + var currentSet; + var index = 0; + + var nextSet = function () { + if (!sets.length) { + onDone(); + return; + } + index = 0; + currentSet = sets.shift(); + callbacks[0](currentSet, nextCallback); + }; + var nextCallback = function () { + index++; + callbacks[index](currentSet, nextCallback); + }; + + // Add a final callback to proceed to the next set + callbacks[callbacks.length] = function () { + nextSet(); + } nextSet(); } - nextSet(); } - /** * @class All functions accessible from within Zotero.Utilities namespace inside sandboxed * translators @@ -645,14 +519,23 @@ Zotero.Utilities.prototype.processAsync = function (sets, callbacks, onDone) { * @borrows Zotero.Date.strToISO as this.strToISO * @borrows Zotero.OpenURL.createContextObject as this.createContextObject * @borrows Zotero.OpenURL.parseContextObject as this.parseContextObject - * @borrows Zotero.Utilities.HTTP.processDocuments as this.processDocuments - * @borrows Zotero.Utilities.HTTP.doPost as this.doPost + * @borrows Zotero.HTTP.processDocuments as this.processDocuments + * @borrows Zotero.HTTP.doPost as this.doPost * @param {Zotero.Translate} translate */ Zotero.Utilities.Translate = function(translate) { this.translate = translate; } +var tmp = function() {}; +tmp.prototype = Zotero.Utilities; +Zotero.Utilities.Translate.prototype = new tmp(); +Zotero.Utilities.Translate.prototype.formatDate = Zotero.Date.formatDate; +Zotero.Utilities.Translate.prototype.strToDate = Zotero.Date.strToDate; +Zotero.Utilities.Translate.prototype.strToISO = Zotero.Date.strToISO; +Zotero.Utilities.Translate.prototype.createContextObject = Zotero.OpenURL.createContextObject; +Zotero.Utilities.Translate.prototype.parseContextObject = Zotero.OpenURL.parseContextObject; + /** * Gets the current Zotero version * @@ -769,7 +652,7 @@ Zotero.Utilities.Translate.prototype.loadDocument = function(url, succeeded, fai } /** - * Already documented in Zotero.Utilities.HTTP + * Already documented in Zotero.HTTP * @ignore */ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor, done, exception) { @@ -792,8 +675,7 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor } } - var hiddenBrowser = Zotero.Utilities.HTTP.processDocuments(urls, processor, done, exception); - if(this.translate.cookieManager) this.translate.cookieManager.attachToBrowser(hiddenBrowser); + Zotero.HTTP.processDocuments(urls, processor, done, exception); } /** @@ -813,7 +695,6 @@ Zotero.Utilities.Translate.prototype.retrieveDocument = function(url) { } var hiddenBrowser = Zotero.Browser.createHiddenBrowser(); - if(this.translate.cookieManager) this.translate.cookieManager.attachToBrowser(hiddenBrowser); hiddenBrowser.addEventListener("pageshow", listener, true); hiddenBrowser.loadURI(url); @@ -860,15 +741,11 @@ Zotero.Utilities.Translate.prototype.retrieveSource = function(url, body, header var listener = function(aXmlhttp) { xmlhttp = aXmlhttp }; if(body) { - var xhr = Zotero.Utilities.HTTP.doPost(url, body, listener, headers, responseCharset, true); + Zotero.HTTP.doPost(url, body, listener, headers, responseCharset); } else { - var xhr = Zotero.Utilities.HTTP.doGet(url, listener, responseCharset, true); + Zotero.HTTP.doGet(url, listener, responseCharset); } - if(this.translate.cookieManager) this.translate.cookieManager.attachToXHR(xhr); - - xhr.send(body ? body : null); - while(!xmlhttp) mainThread.processNextEvent(true); if(xmlhttp.status >= 400) throw "retrieveSource failed: "+xmlhttp.status+" "+xmlhttp.statusText; @@ -898,7 +775,7 @@ Zotero.Utilities.Translate.prototype.doGet = function(urls, processor, done, res var me = this; - var xhr = Zotero.Utilities.HTTP.doGet(url, function(xmlhttp) { + Zotero.HTTP.doGet(url, function(xmlhttp) { try { if(processor) { processor(xmlhttp.responseText, xmlhttp, url); @@ -914,30 +791,68 @@ Zotero.Utilities.Translate.prototype.doGet = function(urls, processor, done, res } catch(e) { me.translate.error(false, e); } - }, responseCharset, true); - - if(this.translate.cookieManager) this.translate.cookieManager.attachToXHR(xhr); - xhr.send(null); + }, responseCharset); } /** - * Already documented in Zotero.Utilities.HTTP + * Already documented in Zotero.HTTP * @ignore */ Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, headers, responseCharset) { url = this._convertURL(url); var translate = this.translate; - var xhr = Zotero.Utilities.HTTP.doPost(url, body, function(xmlhttp) { + Zotero.HTTP.doPost(url, body, function(xmlhttp) { try { onDone(xmlhttp.responseText, xmlhttp); } catch(e) { translate.error(false, e); } - }, headers, responseCharset, true); - - if(this.translate.cookieManager) this.translate.cookieManager.attachToXHR(xhr); - xhr.send(body); + }, headers, responseCharset); +} + +/** + * Tests if an item type exists + * + * @param {String} type Item type + * @type Boolean + */ +Zotero.Utilities.Translate.prototype.itemTypeExists = function(type) { + if(Zotero.ItemTypes.getID(type)) { + return true; + } else { + return false; + } +} + +/** + * Find valid creator types for a given item type + * + * @param {String} type Item type + * @return {String[]} Creator types + */ +Zotero.Utilities.Translate.prototype.getCreatorsForType = function(type) { + var types = Zotero.CreatorTypes.getTypesForItemType(Zotero.ItemTypes.getID(type)); + var cleanTypes = new Array(); + for each(var type in types) { + cleanTypes.push(type.name); + } + return cleanTypes; +} + +/** + * Gets a creator type name, localized to the current locale + * + * @param {String} type Creator type + * @param {String} Localized creator type + * @type Boolean + */ +Zotero.Utilities.Translate.prototype.getLocalizedCreatorType = function(type) { + try { + return Zotero.getString("creatorTypes."+type); + } catch(e) { + return false; + } } /** @@ -966,628 +881,88 @@ Zotero.Utilities.Translate.prototype._convertURL = function(url) { } /** - * Wrap all functions so that arguments are guaranteed safe + * @class Utility functions not made available to translators */ -borrowedFunctions = { - "formatDate":Zotero.Date.formatDate, - "strToDate":Zotero.Date.strToDate, - "strToISO":Zotero.Date.strToISO, - "createContextObject":Zotero.OpenURL.createContextObject, - "parseContextObject":Zotero.OpenURL.parseContextObject -}; -for each(var wrapArrayBad in [borrowedFunctions, Zotero.Utilities.prototype, Zotero.Utilities.Translate.prototype]) { - let wrapArray = wrapArrayBad; - for(var wrapFunctionBad in wrapArray) { - let wrapFunction = wrapFunctionBad; - if(!(wrapArray[wrapFunction] instanceof Function)) continue; - - let unwrappedFunction = wrapArray[wrapFunction]; - - Zotero.Utilities.Translate.prototype[wrapFunction] = function() { - let args = []; - for(let i=0; i> 4; + ascii.push(String.fromCharCode(tens + (tens > 9 ? 87 : 48)) + String.fromCharCode(ones + (ones > 9 ? 87 : 48))); + } + return ascii.join(''); } } -/** - * Functions for performing HTTP requests, both via XMLHTTPRequest and using a hidden browser - * @namespace - */ -Zotero.Utilities.HTTP = new function() { - this.WebDAV = {}; - - - /** - * Send an HTTP GET request via XMLHTTPRequest - * - * @param {nsIURI|String} url URL to request - * @param {Function} onDone Callback to be executed upon request completion. - * If false, XHR is returned unsent. - * @param {String} responseCharset Character set to force on the response - * @param {Boolean} dontSend Don't send XHR before returning - * @return {Boolean} True if the request was sent, or false if the browser is offline - */ - this.doGet = function(url, onDone, responseCharset, dontSend) { - if (url instanceof Components.interfaces.nsIURI) { - // Don't display password in console - var disp = url.clone(); - if (disp.password) { - disp.password = "********"; - } - Zotero.debug("HTTP GET " + disp.spec); - url = url.spec; - } - else { - Zotero.debug("HTTP GET " + url); - } - if (this.browserIsOffline()){ - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open('GET', url, true); - // Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only) - if (!Zotero.isFx35) { - var channel = xmlhttp.channel; - channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal); - channel.forceAllowThirdPartyCookie = true; - } - - // Don't cache GET requests - xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - - /** @ignore */ - xmlhttp.onreadystatechange = function() { - _stateChange(xmlhttp, onDone, responseCharset); - }; - - if(!dontSend) { - xmlhttp.send(null); - } - - return xmlhttp; - } - - /** - * Send an HTTP POST request via XMLHTTPRequest - * - * @param {String} url URL to request - * @param {String} body Request body - * @param {Function} onDone Callback to be executed upon request completion. - * @param {String} headers Request HTTP headers - * @param {String} responseCharset Character set to force on the response - * @param {Boolean} dontSend Don't send XHR before returning - * @return {Boolean} True if the request was sent, or false if the browser is offline - */ - this.doPost = function(url, body, onDone, headers, responseCharset, dontSend) { - if (url instanceof Components.interfaces.nsIURI) { - // Don't display password in console - var disp = url.clone(); - if (disp.password) { - disp.password = "********"; - } - url = url.spec; - } - - var bodyStart = body.substr(0, 1024); - // Don't display sync password or session id in console - bodyStart = bodyStart.replace(/password=[^&]+/, 'password=********'); - bodyStart = bodyStart.replace(/sessionid=[^&]+/, 'sessionid=********'); - - Zotero.debug("HTTP POST " - + (body.length > 1024 ? - bodyStart + '... (' + body.length + ' chars)' : bodyStart) - + " to " + (disp ? disp.spec : url)); - - - if (this.browserIsOffline()){ - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open('POST', url, true); - // Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only) - if (!Zotero.isFx35) { - var channel = xmlhttp.channel; - channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal); - channel.forceAllowThirdPartyCookie = true; - } - - if (headers) { - if (typeof headers == 'string') { - var msg = "doPost() now takes a headers object rather than a requestContentType -- update your code"; - Zotero.debug(msg, 2); - Components.utils.reportError(msg); - headers = { - "Content-Type": headers - }; - } - } - else { - headers = {}; - } - - if (!headers["Content-Type"]) { - headers["Content-Type"] = "application/x-www-form-urlencoded"; - } - - for (var header in headers) { - xmlhttp.setRequestHeader(header, headers[header]); - } - - /** @ignore */ - xmlhttp.onreadystatechange = function(){ - _stateChange(xmlhttp, onDone, responseCharset); - }; - - if(!dontSend) { - xmlhttp.send(body); - } - - return xmlhttp; - } - - /** - * Send an HTTP HEAD request via XMLHTTPRequest - * - * @param {String} url URL to request - * @param {Function} onDone Callback to be executed upon request completion - * @param {Object} requestHeaders HTTP headers to include with request - * @return {Boolean} True if the request was sent, or false if the browser is offline - */ - this.doHead = function(url, onDone, requestHeaders) { - if (url instanceof Components.interfaces.nsIURI) { - // Don't display password in console - var disp = url.clone(); - if (disp.password) { - disp.password = "********"; - } - Zotero.debug("HTTP HEAD " + disp.spec); - url = url.spec; - } - else { - Zotero.debug("HTTP HEAD " + url); - - } - - if (this.browserIsOffline()){ - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open('HEAD', url, true); - // Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only) - if (!Zotero.isFx35) { - var channel = xmlhttp.channel; - channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal); - channel.forceAllowThirdPartyCookie = true; - } - - if (requestHeaders) { - for (var header in requestHeaders) { - xmlhttp.setRequestHeader(header, requestHeaders[header]); - } - } - - // Don't cache HEAD requests - xmlhttp.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE; - - /** @ignore */ - xmlhttp.onreadystatechange = function(){ - _stateChange(xmlhttp, onDone); - }; - - xmlhttp.send(null); - - return xmlhttp; - } - - /** - * Send an HTTP OPTIONS request via XMLHTTPRequest - * - * @param {nsIURI} url - * @param {Function} onDone - * @return {XMLHTTPRequest} - */ - this.doOptions = function (uri, callback) { - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = "********"; - } - Zotero.debug("HTTP OPTIONS for " + disp.spec); - - if (Zotero.Utilities.HTTP.browserIsOffline()){ - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open('OPTIONS', uri.spec, true); - /** @ignore */ - xmlhttp.onreadystatechange = function() { - _stateChange(xmlhttp, callback); - }; - xmlhttp.send(null); - return xmlhttp; - } - - // - // WebDAV methods - // - - /** - * Send a WebDAV PROP* request via XMLHTTPRequest - * - * Returns false if browser is offline - * - * @param {String} method PROPFIND or PROPPATCH - * @param {nsIURI} uri - * @param {String} body XML string - * @param {Function} callback - * @param {Object} requestHeaders e.g. { Depth: 0 } - */ - this.WebDAV.doProp = function (method, uri, body, callback, requestHeaders) { - switch (method) { - case 'PROPFIND': - case 'PROPPATCH': - break; - - default: - throw ("Invalid method '" + method - + "' in Zotero.Utilities.HTTP.doProp"); - } - - if (requestHeaders && requestHeaders.depth != undefined) { - var depth = requestHeaders.depth; - } - - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = "********"; - } - - var bodyStart = body.substr(0, 1024); - Zotero.debug("HTTP " + method + " " - + (depth != undefined ? "(depth " + depth + ") " : "") - + (body.length > 1024 ? - bodyStart + "... (" + body.length + " chars)" : bodyStart) - + " to " + disp.spec); - - if (Zotero.Utilities.HTTP.browserIsOffline()) { - Zotero.debug("Browser is offline", 2); - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open(method, uri.spec, true); - - if (requestHeaders) { - for (var header in requestHeaders) { - xmlhttp.setRequestHeader(header, requestHeaders[header]); - } - } - - xmlhttp.setRequestHeader("Content-Type", 'text/xml; charset="utf-8"'); - - xmlhttp.onreadystatechange = function() { - _stateChange(xmlhttp, callback); - }; - - xmlhttp.send(body); - return xmlhttp; - } - - - /** - * Send a WebDAV MKCOL request via XMLHTTPRequest - * - * @param {nsIURI} url - * @param {Function} onDone - * @return {XMLHTTPRequest} - */ - this.WebDAV.doMkCol = function (uri, callback) { - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = "********"; - } - Zotero.debug("HTTP MKCOL " + disp.spec); - - if (Zotero.Utilities.HTTP.browserIsOffline()) { - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open('MKCOL', uri.spec, true); - xmlhttp.onreadystatechange = function() { - _stateChange(xmlhttp, callback); - }; - xmlhttp.send(null); - return xmlhttp; - } - - - /** - * Send a WebDAV PUT request via XMLHTTPRequest - * - * @param {nsIURI} url - * @param {String} body String body to PUT - * @param {Function} onDone - * @return {XMLHTTPRequest} - */ - this.WebDAV.doPut = function (uri, body, callback) { - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = "********"; - } - - var bodyStart = "'" + body.substr(0, 1024) + "'"; - Zotero.debug("HTTP PUT " - + (body.length > 1024 ? - bodyStart + "... (" + body.length + " chars)" : bodyStart) - + " to " + disp.spec); - - if (Zotero.Utilities.HTTP.browserIsOffline()) { - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open("PUT", uri.spec, true); - // Some servers (e.g., Jungle Disk DAV) return a 200 response code - // with Content-Length: 0, which triggers a "no element found" error - // in Firefox, so we override to text - xmlhttp.overrideMimeType("text/plain"); - xmlhttp.onreadystatechange = function() { - _stateChange(xmlhttp, callback); - }; - xmlhttp.send(body); - return xmlhttp; - } - - - /** - * Send a WebDAV PUT request via XMLHTTPRequest - * - * @param {nsIURI} url - * @param {Function} onDone - * @return {XMLHTTPRequest} - */ - this.WebDAV.doDelete = function (uri, callback) { - // Don't display password in console - var disp = uri.clone(); - if (disp.password) { - disp.password = "********"; - } - - Zotero.debug("WebDAV DELETE to " + disp.spec); - - if (Zotero.Utilities.HTTP.browserIsOffline()) { - return false; - } - - var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - // Prevent certificate/authentication dialogs from popping up - xmlhttp.mozBackgroundRequest = true; - xmlhttp.open("DELETE", uri.spec, true); - // Firefox 3 throws a "no element found" error even with a - // 204 ("No Content") response, so we override to text - xmlhttp.overrideMimeType("text/plain"); - xmlhttp.onreadystatechange = function() { - _stateChange(xmlhttp, callback); - }; - xmlhttp.send(null); - return xmlhttp; - } - - - /** - * Get the Authorization header used by a channel - * - * As of Firefox 3.0.1 subsequent requests to higher-level directories - * seem not to authenticate properly and just return 401s, so this - * can be used to manually include the Authorization header in a request - * - * It can also be used to check whether a request was forced to - * use authentication - * - * @param {nsIChannel} channel - * @return {String|FALSE} Authorization header, or FALSE if none - */ - this.getChannelAuthorization = function (channel) { - try { - channel.QueryInterface(Components.interfaces.nsIHttpChannel); - var authHeader = channel.getRequestHeader("Authorization"); - return authHeader; - } - catch (e) { - Zotero.debug(e); - return false; - } - } - - - /** - * Checks if the browser is currently in "Offline" mode - * - * @type Boolean - */ - this.browserIsOffline = function() { - return Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService).offline; - } - - /** - * Load one or more documents in a hidden browser - * - * @param {String|String[]} urls URL(s) of documents to load - * @param {Function} processor Callback to be executed for each document loaded - * @param {Function} done Callback to be executed after all documents have been loaded - * @param {Function} exception Callback to be executed if an exception occurs - */ - this.processDocuments = function(urls, processor, done, exception) { - /** - * Removes event listener for the load event and deletes the hidden browser - */ - var removeListeners = function() { - hiddenBrowser.removeEventListener(loadEvent, onLoad, true); - Zotero.Browser.deleteHiddenBrowser(hiddenBrowser); - } - - /** - * Loads the next page - * @inner - */ - var doLoad = function() { - if(urls.length) { - var url = urls.shift(); - try { - Zotero.debug("loading "+url); - hiddenBrowser.loadURI(url); - } catch(e) { - removeListeners(); - if(exception) { - exception(e); - return; - } else { - throw(e); - } - } - } else { - removeListeners(); - if(done) done(); - } - }; - - /** - * Callback to be executed when a page load completes - * @inner - */ - var onLoad = function() { - if(hiddenBrowser.contentDocument.location.href == "about:blank") return; - Zotero.debug(hiddenBrowser.contentDocument.location.href+" has been loaded"); - if(hiddenBrowser.contentDocument.location.href != prevUrl) { // Just in case it fires too many times - prevUrl = hiddenBrowser.contentDocument.location.href; - try { - processor(hiddenBrowser.contentDocument); - } catch(e) { - removeListeners(); - if(exception) { - exception(e); - return; - } else { - throw(e); - } - } - doLoad(); - } - }; - - if(typeof(urls) == "string") urls = [urls]; - - var prevUrl; - var loadEvent = "pageshow"; - - var hiddenBrowser = Zotero.Browser.createHiddenBrowser(); - hiddenBrowser.addEventListener(loadEvent, onLoad, true); - - doLoad(); - return hiddenBrowser; - } - - /** - * Handler for XMLHttpRequest state change - * - * @param {nsIXMLHttpRequest} XMLHttpRequest whose state just changed - * @param {Function} [onDone] Callback for request completion - * @param {String} [responseCharset] Character set to force on the response - * @private - */ - function _stateChange(xmlhttp, callback, responseCharset, data) { - switch (xmlhttp.readyState){ - // Request not yet made - case 1: - break; - - case 2: - break; - - // Called multiple times while downloading in progress - case 3: - break; - - // Download complete - case 4: - if (callback) { - // Override the content charset - if (responseCharset) { - xmlhttp.channel.contentCharset = responseCharset; - } - callback(xmlhttp, data); - } - break; - } - } -} - -/** - * @namespace - */ -// This would probably be better as a separate XPCOM service -Zotero.Utilities.AutoComplete = new function() { - this.getResultComment = function (textbox){ - var controller = textbox.controller; - - for (var i=0; i\s*/, '')); + str = Zotero.Utilities.trim(str.replace(/<\?xml.*\?>\s*/, '')); Zotero.debug(str); var confirm = ps.confirm( @@ -1912,13 +1912,12 @@ Zotero.Date = new function(){ return date.toLocaleFormat('%Y-%m-%d %H:%M:%S'); } - var utils = new Zotero.Utilities(); - year = utils.lpad(year, '0', 4); - month = utils.lpad(month + 1, '0', 2); - day = utils.lpad(day, '0', 2); - hours = utils.lpad(hours, '0', 2); - minutes = utils.lpad(minutes, '0', 2); - seconds = utils.lpad(seconds, '0', 2); + year = Zotero.Utilities.lpad(year, '0', 4); + month = Zotero.Utilities.lpad(month + 1, '0', 2); + day = Zotero.Utilities.lpad(day, '0', 2); + hours = Zotero.Utilities.lpad(hours, '0', 2); + minutes = Zotero.Utilities.lpad(minutes, '0', 2); + seconds = Zotero.Utilities.lpad(seconds, '0', 2); return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds; @@ -1945,13 +1944,12 @@ Zotero.Date = new function(){ var minutes = date.getUTCMinutes(); var seconds = date.getUTCSeconds(); - var utils = new Zotero.Utilities(); - year = utils.lpad(year, '0', 4); - month = utils.lpad(month + 1, '0', 2); - day = utils.lpad(day, '0', 2); - hours = utils.lpad(hours, '0', 2); - minutes = utils.lpad(minutes, '0', 2); - seconds = utils.lpad(seconds, '0', 2); + year = Zotero.Utilities.lpad(year, '0', 4); + month = Zotero.Utilities.lpad(month + 1, '0', 2); + day = Zotero.Utilities.lpad(day, '0', 2); + hours = Zotero.Utilities.lpad(hours, '0', 2); + minutes = Zotero.Utilities.lpad(minutes, '0', 2); + seconds = Zotero.Utilities.lpad(seconds, '0', 2); return year + '-' + month + '-' + day + 'T' + hours + ':' + minutes + ':' + seconds + 'Z'; @@ -2217,11 +2215,11 @@ Zotero.Date = new function(){ var date = Zotero.Date.strToDate(str); if(date.year) { - var dateString = Zotero.Utilities.prototype.lpad(date.year, "0", 4); + var dateString = Zotero.Utilities.lpad(date.year, "0", 4); if(date.month) { - dateString += "-"+Zotero.Utilities.prototype.lpad(date.month+1, "0", 2); + dateString += "-"+Zotero.Utilities.lpad(date.month+1, "0", 2); if(date.day) { - dateString += "-"+Zotero.Utilities.prototype.lpad(date.day, "0", 2); + dateString += "-"+Zotero.Utilities.lpad(date.day, "0", 2); } } return dateString; @@ -2234,8 +2232,6 @@ Zotero.Date = new function(){ return ''; } - var utils = new Zotero.Utilities(); - var parts = strToDate(str); // FIXME: Until we have a better BCE date solution, @@ -2249,9 +2245,9 @@ Zotero.Date = new function(){ parts.month = typeof parts.month != "undefined" ? parts.month + 1 : ''; - var multi = (parts.year ? utils.lpad(parts.year, '0', 4) : '0000') + '-' - + utils.lpad(parts.month, '0', 2) + '-' - + (parts.day ? utils.lpad(parts.day, '0', 2) : '00') + var multi = (parts.year ? Zotero.Utilities.lpad(parts.year, '0', 4) : '0000') + '-' + + Zotero.Utilities.lpad(parts.month, '0', 2) + '-' + + (parts.day ? Zotero.Utilities.lpad(parts.day, '0', 2) : '00') + ' ' + str; return multi; diff --git a/components/zotero-service.js b/components/zotero-service.js index 1954a36d50..d5a41167a3 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -84,6 +84,7 @@ var xpcomFiles = [ 'error', 'file', 'fulltext', + 'http', 'id', 'ingester', 'integration',