zotero/chrome/content/zotero/xpcom/utilities.js
Dan Stillman f64dc8f90f Adds WebDAV file sync
- Still experimental and incomplete, with no lock support and not much error handling

Also:

- New expiry date for sync functions
- Attachment character set was being dropped during syncing
- Possibly improves sizing issues with preferences window
- Fixes problems with attachment filenames with extended characters
- Fixes some problem with tags that I don't remember
- Makes XMLHTTPRequest calls are now background requests (no auth windows or other prompts)
- Z.U.HTTP.doOptions() now takes an nsIURI instead of a URL spec
- New methods:
  - Zotero.Utilities.rand(min, max)
  - Zotero.Utilities.probability(x)
  - Zotero.Utilities.Base64.encode(str) and decode(str)
  - Zotero.getTempDirectory()
  - Zotero.Date.dateToISO(date) - convert JS Date object to ISO 8601 UTC date/time
  - Zotero.Date.isoToDate(isoDate) - convert an ISO 8601 UTC date/time to a JS Date object
2008-08-31 23:36:01 +00:00

1321 lines
33 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.opensource.org/licenses/ecl1.php
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Utilities based in part on code taken from Piggy Bank 2.1.1 (BSD-licensed)
***** END LICENSE BLOCK *****
*/
/////////////////////////////////////////////////////////////////
//
// Zotero.Utilities
//
/////////////////////////////////////////////////////////////////
Zotero.Utilities = function () {}
/*
* See Zotero.Date
*/
Zotero.Utilities.prototype.formatDate = function(date) {
return Zotero.Date.formatDate(date);
}
Zotero.Utilities.prototype.strToDate = function(date) {
return Zotero.Date.strToDate(date);
}
Zotero.Utilities.prototype.strToISO = function(date) {
return Zotero.Date.strToISO(date);
}
/*
* Cleans extraneous punctuation off an author name
*/
Zotero.Utilities._allCapsRe = /^[A-Z]+$/;
Zotero.Utilities.prototype.cleanAuthor = function(author, type, useComma) {
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 spaceIndex = author.lastIndexOf(" ");
var lastName = author.substring(spaceIndex+1);
var firstName = author.substring(0, spaceIndex);
}
if(firstName && Zotero.Utilities._allCapsRe.test(firstName) &&
firstName.length < 4 &&
(firstName.length == 1 || lastName.toUpperCase() != lastName)) {
// first name is probably initials
var newFirstName = "";
for(var i=0; i<firstName.length; i++) {
newFirstName += " "+firstName[i]+".";
}
firstName = newFirstName.substr(1);
}
return {firstName:firstName, lastName:lastName, creatorType:type};
}
/*
* Removes leading and trailing whitespace from a string
*/
Zotero.Utilities.prototype.trim = function(s) {
if (typeof(s) != "string") {
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
*/
Zotero.Utilities.prototype.trimInternal = function(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 whitespace off a string and replaces multiple spaces with one
*
* DEPRECATED: use trimInternal()
*/
Zotero.Utilities.prototype.cleanString = function(s) {
Zotero.debug("cleanString() is deprecated; use trimInternal() instead", 2);
return this.trimInternal(s);
}
/*
* Cleans any non-word non-parenthesis characters off the ends of a string
*/
Zotero.Utilities.prototype.superCleanString = function(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]+$/, "");
}
/*
* Eliminates HTML tags, replacing <br>s with /ns
*/
Zotero.Utilities.prototype.cleanTags = function(x) {
if(typeof(x) != "string") {
throw "cleanTags: argument must be a string";
}
x = x.replace(/<br[^>]*>/gi, "\n");
return x.replace(/<[^>]+>/g, "");
}
/*
* Encode special XML/HTML characters
*
* Certain entities can be inserted manually:
*
* <ZOTEROBREAK/> => <br/>
* <ZOTEROHELLIP/> => &#8230;
*/
Zotero.Utilities.prototype.htmlSpecialChars = function(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(/&lt;ZOTERO([^\/]+)\/&gt;/g, function (str, p1, offset, s) {
switch (p1) {
case 'BREAK':
return '<br/>';
case 'HELLIP':
return '&#8230;';
default:
return p1;
}
});
return newString;
}
Zotero.Utilities.prototype.unescapeHTML = function(str) {
var nsISUHTML = Components.classes["@mozilla.org/feed-unescapehtml;1"]
.getService(Components.interfaces.nsIScriptableUnescapeHTML);
return nsISUHTML.unescape(str);
}
/*
* Parses a text string for HTML/XUL markup and returns an array of parts
*
* Currently only finds HTML links (<a> tags)
*
* Returns an array of objects with the following form:
* {
* type: 'text'|'link',
* text: "text content",
* [ attributes: { key1: val [ , key2: val, ...] }
* }
*/
Zotero.Utilities.prototype.parseMarkup = function(str) {
var parts = [];
var splits = str.split(/(<a [^>]+>[^<]*<\/a>)/);
for each(var split in splits) {
// Link
if (split.indexOf('<a ') == 0) {
var matches = split.match(/<a ([^>]+)>([^<]*)<\/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: 'text',
text: split
});
}
return parts;
}
Zotero.Utilities.prototype.min3 = function (a, b, c) {
var min = a;
if (b < min) {
min = b;
}
if (c < min) {
min = c;
}
return min;
}
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] = this.min3(arr[i-1][j] + 1, arr[i][j-1] + 1, arr[i-1][j-1] + cost);
}
}
return arr[aLen][bLen];
}
/*
* Test if a string is an integer
*/
Zotero.Utilities.prototype.isInt = function(x) {
if(parseInt(x) == x) {
return true;
}
return false;
}
/**
* Generate a random integer between min and max inclusive
*
* @param {Integer} min
* @param {Integer} max
* @return {Integer}
*/
Zotero.Utilities.prototype.rand = function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Return true according to a given probability
*
* @param {Integer} x Will return true every x times on average
* @return {Boolean} On average, TRUE every x times
* the function is called
*/
Zotero.Utilities.prototype.probability = function (x) {
return this.rand(1, x) == this.rand(1, x);
}
/**
* Determine the necessary data type for SQLite parameter binding
*
* @return int 0 for string, 32 for int32, 64 for int64
*/
Zotero.Utilities.prototype.getSQLDataType = function(value) {
var strVal = value + '';
if (strVal.match(/^[1-9]+[0-9]*$/)) {
// These upper bounds also specified in Zotero.DB
//
// Store as 32-bit signed integer
if (value <= 2147483647) {
return 32;
}
// Store as 64-bit signed integer
// 2^53 is JS's upper-bound for decimal integers
else if (value < 9007199254740992) {
return 64;
}
}
return 0;
}
/*
* From http://developer.mozilla.org/en/docs/nsICryptoHash#Computing_the_Hash_of_a_String
*/
Zotero.Utilities.prototype.md5 = function(str) {
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var result = {};
var data = converter.convertToByteArray(str, result);
var ch = Components.classes["@mozilla.org/security/hash;1"]
.createInstance(Components.interfaces.nsICryptoHash);
ch.init(ch.MD5);
ch.update(data, data.length);
var hash = ch.finish(false);
// return the two-digit hexadecimal code for a byte
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
}
// convert the binary hash data to a hex string.
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
}
/*
* Get current zotero version
*/
Zotero.Utilities.prototype.getVersion = function() {
return Zotero.version;
}
/*
* Get a page range, given a user-entered set of pages
*/
Zotero.Utilities.prototype._pageRangeRegexp = /^\s*([0-9]+)-([0-9]+)\s*$/;
Zotero.Utilities.prototype.getPageRange = function(pages) {
var pageNumbers;
var m = this._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;
}
/*
* provide inArray function
*/
Zotero.Utilities.prototype.inArray = Zotero.inArray;
/*
* pads a number or other string with a given string on the left
*/
Zotero.Utilities.prototype.lpad = function(string, pad, length) {
string = string ? string + '' : '';
while(string.length < length) {
string = pad + string;
}
return string;
}
/*
* returns true if an item type exists, false if it does not
*/
Zotero.Utilities.prototype.itemTypeExists = function(type) {
if(Zotero.ItemTypes.getID(type)) {
return true;
} else {
return false;
}
}
/*
* returns an array of all (string) creatorTypes valid for a (string) itemType
*/
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;
}
/*
* returns a localized creatorType name
*/
Zotero.Utilities.prototype.getLocalizedCreatorType = function(type) {
try {
return Zotero.getString("creatorTypes."+type);
} catch(e) {
return false;
}
}
/*
* Cleans a title, capitalizing the proper words and replacing " :" with ":"
*
* Follows capitalizeTitles pref, unless |force| is true
*/
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;
}
/*
* END ZOTERO FOR FIREFOX EXTENSIONS
*/
/////////////////////////////////////////////////////////////////
//
// Zotero.Utilities.Ingester
//
/////////////////////////////////////////////////////////////////
// Zotero.Utilities.Ingester extends Zotero.Utilities, offering additional
// classes relating to data extraction specifically from HTML documents.
Zotero.Utilities.Ingester = function(translate, proxiedURL) {
this.translate = translate;
}
Zotero.Utilities.Ingester.prototype = new Zotero.Utilities();
// Takes an XPath query and returns the results
Zotero.Utilities.Ingester.prototype.gatherElementsOnXPath = function(doc, parentNode, xpath, nsResolver) {
var elmts = [];
var iterator = doc.evaluate(xpath, parentNode, nsResolver, Components.interfaces.nsIDOMXPathResult.ANY_TYPE,null);
var elmt = iterator.iterateNext();
var i = 0;
while (elmt) {
elmts[i++] = elmt;
elmt = iterator.iterateNext();
}
return elmts;
}
/*
* Gets a given node as a string containing all child nodes
*
* WARNING: This is DEPRECATED and may be removed in the final release. Use
* doc.evaluate and the "nodeValue" or "textContent" property
*/
Zotero.Utilities.Ingester.prototype.getNodeString = function(doc, contextNode, xpath, nsResolver) {
var elmts = this.gatherElementsOnXPath(doc, contextNode, xpath, nsResolver);
var returnVar = "";
for(var i=0; i<elmts.length; i++) {
returnVar += elmts[i].nodeValue;
}
return returnVar;
}
/*
* Grabs items based on URLs
*/
Zotero.Utilities.Ingester.prototype.getItemArray = function(doc, inHere, urlRe, rejectRe) {
var availableItems = new Object(); // Technically, associative arrays are objects
// Require link to match this
if(urlRe) {
if(urlRe.exec) {
var urlRegexp = urlRe;
} else {
var urlRegexp = new RegExp();
urlRegexp.compile(urlRe, "i");
}
}
// Do not allow text to match this
if(rejectRe) {
if(rejectRe.exec) {
var rejectRegexp = rejectRe;
} else {
var rejectRegexp = new RegExp();
rejectRegexp.compile(rejectRe, "i");
}
}
if(!inHere.length) {
inHere = new Array(inHere);
}
for(var j=0; j<inHere.length; j++) {
var links = inHere[j].getElementsByTagName("a");
for(var i=0; i<links.length; i++) {
if(!urlRe || urlRegexp.test(links[i].href)) {
var text = links[i].textContent;
if(text) {
text = this.trimInternal(text);
if(!rejectRe || !rejectRegexp.test(text)) {
if(availableItems[links[i].href]) {
if(text != availableItems[links[i].href]) {
availableItems[links[i].href] += " "+text;
}
} else {
availableItems[links[i].href] = text;
}
}
}
}
}
}
return availableItems;
}
Zotero.Utilities.Ingester.prototype.lookupContextObject = function(co, done, error) {
return Zotero.OpenURL.lookupContextObject(co, done, error);
}
Zotero.Utilities.Ingester.prototype.parseContextObject = function(co, item) {
return Zotero.OpenURL.parseContextObject(co, item);
}
// Ingester adapters for Zotero.Utilities.HTTP to handle proxies
Zotero.Utilities.Ingester.prototype.loadDocument = function(url, succeeded, failed) {
this.processDocuments([ url ], succeeded, null, failed);
}
Zotero.Utilities.Ingester._protocolRe = new RegExp();
Zotero.Utilities.Ingester._protocolRe.compile("^(?:(?:http|https|ftp):|[^:](?:/.*)?$)", "i");
Zotero.Utilities.Ingester.prototype.processDocuments = function(urls, processor, done, exception) {
if(this.translate.locationIsProxied) {
for(var i in urls) {
if(this.translate.locationIsProxied) {
urls[i] = Zotero.Proxies.properToProxy(urls[i]);
}
// check for a protocol colon
if(!Zotero.Utilities.Ingester._protocolRe.test(urls[i])) {
throw("invalid URL in processDocuments");
}
}
}
// unless the translator has proposed some way to handle an error, handle it
// by throwing a "scraping error" message
if(!exception) {
var translate = this.translate;
exception = function(e) {
translate.error(false, e);
}
}
Zotero.Utilities.HTTP.processDocuments(null, urls, processor, done, exception);
}
Zotero.Utilities.Ingester.HTTP = function(translate) {
this.translate = translate;
}
Zotero.Utilities.Ingester.HTTP.prototype.doGet = function(urls, processor, done, responseCharset) {
var callAgain = false;
if(typeof(urls) == "string") {
var url = urls;
} else {
if(urls.length > 1) callAgain = true;
var url = urls.shift();
}
if(this.translate.locationIsProxied) {
url = Zotero.Proxies.properToProxy(url);
}
if(!Zotero.Utilities.Ingester._protocolRe.test(url)) {
throw("invalid URL in processDocuments");
}
var me = this;
Zotero.Utilities.HTTP.doGet(url, function(xmlhttp) {
try {
if(processor) {
processor(xmlhttp.responseText, xmlhttp, url);
}
if(callAgain) {
me.doGet(urls, processor, done);
} else {
if(done) {
done();
}
}
} catch(e) {
me.translate.error(false, e);
}
}, responseCharset);
}
Zotero.Utilities.Ingester.HTTP.prototype.doPost = function(url, body, onDone, requestContentType, responseCharset) {
if(this.translate.locationIsProxied) {
url = Zotero.Proxies.properToProxy(url);
}
if(!Zotero.Utilities.Ingester._protocolRe.test(url)) {
throw("invalid URL in processDocuments");
}
var translate = this.translate;
Zotero.Utilities.HTTP.doPost(url, body, function(xmlhttp) {
try {
onDone(xmlhttp.responseText, xmlhttp);
} catch(e) {
translate.error(false, e);
}
}, requestContentType, responseCharset);
}
// These are front ends for XMLHttpRequest. XMLHttpRequest can't actually be
// accessed outside the sandbox, and even if it could, it wouldn't let scripts
// access across domains, so everything's replicated here.
Zotero.Utilities.HTTP = new function() {
this.doGet = doGet;
this.doPost = doPost;
this.doHead = doHead;
this.browserIsOffline = browserIsOffline;
this.WebDAV = {};
/**
* Send an HTTP GET request via XMLHTTPRequest
*
* Returns false if browser is offline
*
* doGet can be called as:
* Zotero.Utilities.HTTP.doGet(url, onDone)
*
* Returns the XMLHTTPRequest object
**/
function doGet(url, onDone, responseCharset) {
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);
xmlhttp.onreadystatechange = function(){
_stateChange(xmlhttp, onDone, responseCharset);
};
// Temporarily set cookieBehavior to 0 for Firefox 3
// https://www.zotero.org/trac/ticket/1070
try {
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior");
prefService.setIntPref("network.cookie.cookieBehavior", 0);
xmlhttp.send(null);
}
finally {
prefService.setIntPref("network.cookie.cookieBehavior", cookieBehavior);
}
return xmlhttp;
}
/**
* Send an HTTP POST request via XMLHTTPRequest
*
* Returns false if browser is offline
*
* doPost can be called as:
* Zotero.Utilities.HTTP.doPost(url, body, onDone)
*
* Returns the XMLHTTPRequest object
**/
function doPost(url, body, onDone, requestContentType, responseCharset) {
var bodyStart = body.substr(0, 1024);
// Don't display password in console
bodyStart = bodyStart.replace(/password=[^&]+/, 'password=********');
Zotero.debug("HTTP POST "
+ (body.length > 1024 ?
bodyStart + '... (' + body.length + ' chars)' : bodyStart)
+ " to " + 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);
xmlhttp.setRequestHeader("Content-Type", (requestContentType ? requestContentType : "application/x-www-form-urlencoded" ));
xmlhttp.onreadystatechange = function(){
_stateChange(xmlhttp, onDone, responseCharset);
};
// Temporarily set cookieBehavior to 0 for Firefox 3
// https://www.zotero.org/trac/ticket/1070
try {
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior");
prefService.setIntPref("network.cookie.cookieBehavior", 0);
xmlhttp.send(body);
}
finally {
prefService.setIntPref("network.cookie.cookieBehavior", cookieBehavior);
}
return xmlhttp;
}
function doHead(url, onDone) {
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);
xmlhttp.onreadystatechange = function(){
_stateChange(xmlhttp, onDone);
};
// Temporarily set cookieBehavior to 0 for Firefox 3
// https://www.zotero.org/trac/ticket/1070
try {
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior");
prefService.setIntPref("network.cookie.cookieBehavior", 0);
xmlhttp.send(null);
}
finally {
prefService.setIntPref("network.cookie.cookieBehavior", cookieBehavior);
}
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();
disp.password = "********";
Zotero.debug("HTTP OPTIONS 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('OPTIONS', uri.spec, true);
xmlhttp.onreadystatechange = function() {
_stateChange(xmlhttp, callback);
};
// Temporarily set cookieBehavior to 0 for Firefox 3
// https://www.zotero.org/trac/ticket/1070
try {
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var cookieBehavior = prefService.getIntPref("network.cookie.cookieBehavior");
prefService.setIntPref("network.cookie.cookieBehavior", 0);
xmlhttp.send(null);
}
finally {
prefService.setIntPref("network.cookie.cookieBehavior", cookieBehavior);
}
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();
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();
disp.password = "********";
Zotero.debug("HTTP MKCOL 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('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();
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);
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();
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);
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;
}
}
function browserIsOffline() {
return Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService).offline;
}
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;
}
}
}
// Downloads and processes documents with processor()
// firstDoc - the first document to process with the processor (if null,
// first document is processed without processor)
// urls - an array of URLs to load
// processor - a function to execute to process each document
// done - a function to execute when all document processing is complete
// exception - a function to execute if an exception occurs (exceptions are
// also logged in the Zotero for Firefox log)
// saveBrowser - whether to save the hidden browser object; usually, you don't
// want to do this, because it makes it easier to leak memory
Zotero.Utilities.HTTP.processDocuments = function(firstDoc, urls, processor, done, exception, saveBrowser) {
var hiddenBrowser = Zotero.Browser.createHiddenBrowser();
hiddenBrowser.docShell.allowImages = false;
var prevUrl, url;
if (urls.length == 0) {
if(firstDoc) {
processor(firstDoc, done);
} else {
done();
}
return;
}
var urlIndex = -1;
var removeListeners = function() {
hiddenBrowser.removeEventListener("pageshow", onLoad, true);
if(!saveBrowser) {
Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
}
}
var doLoad = function() {
urlIndex++;
if (urlIndex < urls.length) {
url = urls[urlIndex];
try {
Zotero.debug("loading "+url);
hiddenBrowser.loadURI(url);
} catch (e) {
removeListeners();
if(exception) {
exception(e);
return;
} else {
throw(e);
}
}
} else {
removeListeners();
if(done) {
done();
}
}
};
var onLoad = function() {
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();
}
};
var init = function() {
hiddenBrowser.addEventListener("pageshow", onLoad, true);
if (firstDoc) {
processor(firstDoc, doLoad);
} else {
doLoad();
}
}
init();
}
/*
* This would probably be better as a separate XPCOM service
*/
Zotero.Utilities.AutoComplete = new function(){
this.getResultComment = getResultComment;
function getResultComment(textbox){
var controller = textbox.controller;
for (var i=0; i<controller.matchCount; i++)
{
if (controller.getValueAt(i) == textbox.value)
{
return controller.getCommentAt(i);
}
}
return false;
}
}
/**
* Base64 encode / decode
* From http://www.webtoolkit.info/
*/
Zotero.Utilities.Base64 = {
// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = this._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = this._utf8_decode(output);
return output;
},
// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}