<commit_before>/**
 * Functions for performing HTTP requests, both via XMLHTTPRequest and using a hidden browser
 * @namespace
 */
Zotero.HTTP = new function() {
	this.lastGoogleScholarQueryTime = 0;

	/**
	 * Exception returned for unexpected status when promise* is used
	 * @constructor
	 */
	this.UnexpectedStatusException = function(xmlhttp, msg) {
		this.xmlhttp = xmlhttp;
		this.status = xmlhttp.status;
		this.message = msg;
		
		// Hide password from debug output
		//
		// Password also shows up in channel.name (nsIRequest.name), but that's
		// read-only and has to be handled in Zotero.varDump()
		try {
			if (xmlhttp.channel.URI.password) {
				xmlhttp.channel.URI.password = "********";
			}
		}
		catch (e) {
			Zotero.debug(e, 1);
		}
	};
	
	this.UnexpectedStatusException.prototype.toString = function() {
		return this.message;
	};
	
	/**
	 * Exception returned if the browser is offline when promise* is used
	 * @constructor
	 */
	this.BrowserOfflineException = function() {
		this.message = "XMLHttpRequest could not complete because the browser is offline";
	};
	this.BrowserOfflineException.prototype.toString = function() {
		return this.message;
	};
	
	/**
	 * Get a promise for a HTTP request
	 * 
	 * @param {String} method The method of the request ("GET", "POST", "HEAD", or "OPTIONS")
	 * @param {nsIURI|String}	url				URL to request
	 * @param {Object} [options] Options for HTTP request:<ul>
	 *         <li>body - The body of a POST request</li>
	 *         <li>cookieSandbox - The sandbox from which cookies should be taken</li>
	 *         <li>debug - Log response text and status code</li>
	 *         <li>dontCache - If set, specifies that the request should not be fulfilled from the cache</li>
	 *         <li>headers - HTTP headers to include in the request</li>
	 *         <li>requestObserver - Callback to receive XMLHttpRequest after open()</li>
	 *         <li>responseType - The type of the response. See XHR 2 documentation for legal values</li>
	 *         <li>responseCharset - The charset the response should be interpreted as</li>
	 *         <li>successCodes - HTTP status codes that are considered successful</li>
	 *     </ul>
	 * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
	 * @return {Promise} A promise resolved with the XMLHttpRequest object if the request
	 *     succeeds, or rejected if the browser is offline or a non-2XX status response
	 *     code is received (or a code not in options.successCodes if provided).
	 */
	this.promise = function promise(method, url, options) {
		if (url instanceof Components.interfaces.nsIURI) {
			// Don't display password in console
			var dispURL = url.clone();
			if (dispURL.password) {
				dispURL.password = "********";
			}
			url = url.spec;
			dispURL = dispURL.spec;
		}
		else {
			var dispURL = url;
		}
		
		if(options && options.body) {
			var bodyStart = options.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 "+method+" "
				+ (options.body.length > 1024 ?
					bodyStart + '... (' + options.body.length + ' chars)' : bodyStart)
				+ " to " + dispURL);
		} else {
			Zotero.debug("HTTP " + method + " " + dispURL);
		}
		
		if (this.browserIsOffline()) {
			return Q.fcall(function() {
				Zotero.debug("HTTP " + method + " " + dispURL + " failed: "
					+ "Browser is offline");
				throw new this.BrowserOfflineException();
			});
		}
		
		var deferred = Q.defer();
		
		var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
					.createInstance();
		// Prevent certificate/authentication dialogs from popping up
		xmlhttp.mozBackgroundRequest = true;
		xmlhttp.open(method, url, true);
		
		// Pass the request to a callback
		if (options && options.requestObserver) {
			options.requestObserver(xmlhttp);
		}
		
		if (method == 'PUT') {
			// 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");
		}
		
		// Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only)
		var channel = xmlhttp.channel;
		channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
		channel.forceAllowThirdPartyCookie = true;
		
		// Set charset
		if (options && options.responseCharset) {
			channel.contentCharset = responseCharset;
		}
		
		// Disable caching if requested
		if(options && options.dontCache) {
			channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
		}

		// Set responseType
		if(options && options.responseType) {
			xmlhttp.responseType = options.responseType;
		}
		
		// Send headers
		var headers = (options && options.headers) || {};
		if (options && options.body && !headers["Content-Type"]) {
			headers["Content-Type"] = "application/x-www-form-urlencoded";
		}
		for (var header in headers) {
			xmlhttp.setRequestHeader(header, headers[header]);
		}
		
		xmlhttp.onloadend = function() {
			var status = xmlhttp.status;
			
			if (options && options.successCodes) {
				var success = options.successCodes.indexOf(status) != -1;
			}
			else {
				var success = status >= 200 && status < 300;
			}
			
			if(success) {
				if (options && options.debug) {
					Zotero.debug("HTTP " + method + " " + dispURL
						+ " succeeded with " + xmlhttp.status);
					Zotero.debug(xmlhttp.responseText);
				}
				deferred.resolve(xmlhttp);
			} else {
				var msg = "HTTP " + method + " " + dispURL + " failed: "
					+ "Unexpected status code " + xmlhttp.status;
				Zotero.debug(msg, 1);
				if (options && options.debug) {
					Zotero.debug(xmlhttp.responseText);
				}
				deferred.reject(new Zotero.HTTP.UnexpectedStatusException(xmlhttp, msg));
			}
		};
		
		if(options && options.cookieSandbox) {<commit_msg>Support file:/// URIs in Zotero.HTTP.promise()<commit_after>/**
 * Functions for performing HTTP requests, both via XMLHTTPRequest and using a hidden browser
 * @namespace
 */
Zotero.HTTP = new function() {
	this.lastGoogleScholarQueryTime = 0;

	/**
	 * Exception returned for unexpected status when promise* is used
	 * @constructor
	 */
	this.UnexpectedStatusException = function(xmlhttp, msg) {
		this.xmlhttp = xmlhttp;
		this.status = xmlhttp.status;
		this.message = msg;
		
		// Hide password from debug output
		//
		// Password also shows up in channel.name (nsIRequest.name), but that's
		// read-only and has to be handled in Zotero.varDump()
		try {
			if (xmlhttp.channel.URI.password) {
				xmlhttp.channel.URI.password = "********";
			}
		}
		catch (e) {
			Zotero.debug(e, 1);
		}
	};
	
	this.UnexpectedStatusException.prototype.toString = function() {
		return this.message;
	};
	
	/**
	 * Exception returned if the browser is offline when promise* is used
	 * @constructor
	 */
	this.BrowserOfflineException = function() {
		this.message = "XMLHttpRequest could not complete because the browser is offline";
	};
	this.BrowserOfflineException.prototype.toString = function() {
		return this.message;
	};
	
	/**
	 * Get a promise for a HTTP request
	 * 
	 * @param {String} method The method of the request ("GET", "POST", "HEAD", or "OPTIONS")
	 * @param {nsIURI|String}	url				URL to request
	 * @param {Object} [options] Options for HTTP request:<ul>
	 *         <li>body - The body of a POST request</li>
	 *         <li>cookieSandbox - The sandbox from which cookies should be taken</li>
	 *         <li>debug - Log response text and status code</li>
	 *         <li>dontCache - If set, specifies that the request should not be fulfilled from the cache</li>
	 *         <li>headers - HTTP headers to include in the request</li>
	 *         <li>requestObserver - Callback to receive XMLHttpRequest after open()</li>
	 *         <li>responseType - The type of the response. See XHR 2 documentation for legal values</li>
	 *         <li>responseCharset - The charset the response should be interpreted as</li>
	 *         <li>successCodes - HTTP status codes that are considered successful</li>
	 *     </ul>
	 * @param {Zotero.CookieSandbox} [cookieSandbox] Cookie sandbox object
	 * @return {Promise} A promise resolved with the XMLHttpRequest object if the request
	 *     succeeds, or rejected if the browser is offline or a non-2XX status response
	 *     code is received (or a code not in options.successCodes if provided).
	 */
	this.promise = function promise(method, url, options) {
		if (url instanceof Components.interfaces.nsIURI) {
			// Don't display password in console
			var dispURL = url.clone();
			if (dispURL.password) {
				dispURL.password = "********";
			}
			url = url.spec;
			dispURL = dispURL.spec;
		}
		else {
			var dispURL = url;
		}
		
		if(options && options.body) {
			var bodyStart = options.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 "+method+" "
				+ (options.body.length > 1024 ?
					bodyStart + '... (' + options.body.length + ' chars)' : bodyStart)
				+ " to " + dispURL);
		} else {
			Zotero.debug("HTTP " + method + " " + dispURL);
		}
		
		if (this.browserIsOffline()) {
			return Q.fcall(function() {
				Zotero.debug("HTTP " + method + " " + dispURL + " failed: "
					+ "Browser is offline");
				throw new this.BrowserOfflineException();
			});
		}
		
		var deferred = Q.defer();
		
		var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
					.createInstance();
		// Prevent certificate/authentication dialogs from popping up
		xmlhttp.mozBackgroundRequest = true;
		xmlhttp.open(method, url, true);
		
		// Pass the request to a callback
		if (options && options.requestObserver) {
			options.requestObserver(xmlhttp);
		}
		
		if (method == 'PUT') {
			// 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");
		}
		
		// Send cookie even if "Allow third-party cookies" is disabled (>=Fx3.6 only)
		var channel = xmlhttp.channel,
			isFile = channel instanceof Components.interfaces.nsIFileChannel;
		if(channel instanceof Components.interfaces.nsIHttpChannelInternal) {
			channel.forceAllowThirdPartyCookie = true;
			
			// Set charset
			if (options && options.responseCharset) {
				channel.contentCharset = responseCharset;
			}
			
			// Disable caching if requested
			if(options && options.dontCache) {
				channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
			}
		}

		// Set responseType
		if(options && options.responseType) {
			xmlhttp.responseType = options.responseType;
		}
		
		// Send headers
		var headers = (options && options.headers) || {};
		if (options && options.body && !headers["Content-Type"]) {
			headers["Content-Type"] = "application/x-www-form-urlencoded";
		}
		for (var header in headers) {
			xmlhttp.setRequestHeader(header, headers[header]);
		}
		
		xmlhttp.onloadend = function() {
			var status = xmlhttp.status;
			
			if (options && options.successCodes) {
				var success = options.successCodes.indexOf(status) != -1;
			}
			else if(isFile) {
				var success = status == 200 || status == 0;
			}
			else {
				var success = status >= 200 && status < 300;
			}
			
			if(success) {
				if (options && options.debug) {
					Zotero.debug("HTTP " + method + " " + dispURL
						+ " succeeded with " + xmlhttp.status);
					Zotero.debug(xmlhttp.responseText);
				}
				deferred.resolve(xmlhttp);
			} else {
				var msg = "HTTP " + method + " " + dispURL + " failed: "
					+ "Unexpected status code " + xmlhttp.status;
				Zotero.debug(msg, 1);
				if (options && options.debug) {
					Zotero.debug(xmlhttp.responseText);
				}
				deferred.reject(new Zotero.HTTP.UnexpectedStatusException(xmlhttp, msg));
			}
		};
		
		if(options && options.cookieSandbox) {
{'fraction_non_alphanumeric': 0.13070182622225862, 'fraction_numerical': 0.004913602489558595, 'mean_word_length': 3.117329737019555, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 48}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1062359', 'n_tokens_mistral': 4357, 'n_tokens_neox': 3648, 'n_words': 1030}