michael@0: /* Any copyright is dedicated to the Public Domain.
michael@0: * http://creativecommons.org/publicdomain/zero/1.0/
michael@0: */
michael@0:
michael@0: /**
michael@0: * Server side http server script for application update tests.
michael@0: *
michael@0: * !IMPORTANT - Since xpcshell used by the http server is launched with -v 170
michael@0: * this file must not use features greater than JavaScript 1.7.
michael@0: */
michael@0:
michael@0: const AUS_Cc = Components.classes;
michael@0: const AUS_Ci = Components.interfaces;
michael@0:
michael@0: #include ../sharedUpdateXML.js
michael@0:
michael@0: const URL_HOST = "http://example.com";
michael@0: const URL_PATH_UPDATE_XML = "/chrome/toolkit/mozapps/update/tests/chrome/update.sjs";
michael@0: const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
michael@0: const REL_PATH_DATA = "chrome/toolkit/mozapps/update/tests/data/";
michael@0: const SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + FILE_SIMPLE_MAR;
michael@0:
michael@0: const SLOW_MAR_DOWNLOAD_INTERVAL = 100;
michael@0: var gTimer;
michael@0:
michael@0: function handleRequest(aRequest, aResponse) {
michael@0: var params = { };
michael@0: if (aRequest.queryString)
michael@0: params = parseQueryString(aRequest.queryString);
michael@0:
michael@0: var statusCode = params.statusCode ? parseInt(params.statusCode) : 200;
michael@0: var statusReason = params.statusReason ? params.statusReason : "OK";
michael@0: aResponse.setStatusLine(aRequest.httpVersion, statusCode, statusReason);
michael@0: aResponse.setHeader("Cache-Control", "no-cache", false);
michael@0:
michael@0: if (params.addonID) {
michael@0: aResponse.write(getUpdateRDF(params));
michael@0: return;
michael@0: }
michael@0:
michael@0: // When a mar download is started by the update service it can finish
michael@0: // downloading before the ui has loaded. By specifying a serviceURL for the
michael@0: // update patch that points to this file and has a slowDownloadMar param the
michael@0: // mar will be downloaded asynchronously which will allow the ui to load
michael@0: // before the download completes.
michael@0: if (params.slowDownloadMar) {
michael@0: aResponse.processAsync();
michael@0: aResponse.setHeader("Content-Type", "binary/octet-stream");
michael@0: aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR);
michael@0: var marFile = AUS_Cc["@mozilla.org/file/directory_service;1"].
michael@0: getService(AUS_Ci.nsIProperties).
michael@0: get("CurWorkD", AUS_Ci.nsILocalFile);
michael@0: var path = REL_PATH_DATA + FILE_SIMPLE_MAR;
michael@0: var pathParts = path.split("/");
michael@0: for(var i = 0; i < pathParts.length; ++i)
michael@0: marFile.append(pathParts[i]);
michael@0: var contents = readFileBytes(marFile);
michael@0: gTimer = AUS_Cc["@mozilla.org/timer;1"].
michael@0: createInstance(AUS_Ci.nsITimer);
michael@0: gTimer.initWithCallback(function(aTimer) {
michael@0: aResponse.write(contents);
michael@0: aResponse.finish();
michael@0: }, SLOW_MAR_DOWNLOAD_INTERVAL, AUS_Ci.nsITimer.TYPE_ONE_SHOT);
michael@0: return;
michael@0: }
michael@0:
michael@0: if (params.uiURL) {
michael@0: var remoteType = "";
michael@0: if (!params.remoteNoTypeAttr &&
michael@0: (params.uiURL == "BILLBOARD" || params.uiURL == "LICENSE")) {
michael@0: remoteType = " " + params.uiURL.toLowerCase() + "=\"1\"";
michael@0: }
michael@0: aResponse.write("
" + params.uiURL +
michael@0: "
this is a test mar that will not affect your " +
michael@0: "build.");
michael@0: return;
michael@0: }
michael@0:
michael@0: if (params.xmlMalformed) {
michael@0: aResponse.write("xml error");
michael@0: return;
michael@0: }
michael@0:
michael@0: if (params.noUpdates) {
michael@0: aResponse.write(getRemoteUpdatesXMLString(""));
michael@0: return;
michael@0: }
michael@0:
michael@0: if (params.unsupported) {
michael@0: aResponse.write(getRemoteUpdatesXMLString(" \n"));
michael@0: return;
michael@0: }
michael@0:
michael@0: var hash;
michael@0: var patches = "";
michael@0: if (!params.partialPatchOnly) {
michael@0: hash = SHA512_HASH_SIMPLE_MAR + (params.invalidCompleteHash ? "e" : "");
michael@0: patches += getRemotePatchString("complete", SERVICE_URL, "SHA512",
michael@0: hash, SIZE_SIMPLE_MAR);
michael@0: }
michael@0:
michael@0: if (!params.completePatchOnly) {
michael@0: hash = SHA512_HASH_SIMPLE_MAR + (params.invalidPartialHash ? "e" : "");
michael@0: patches += getRemotePatchString("partial", SERVICE_URL, "SHA512",
michael@0: hash, SIZE_SIMPLE_MAR);
michael@0: }
michael@0:
michael@0: var type = params.type ? params.type : "major";
michael@0: var name = params.name ? params.name : "App Update Test";
michael@0: var appVersion = params.appVersion ? params.appVersion : "99.9";
michael@0: var displayVersion = params.displayVersion ? params.displayVersion
michael@0: : "version " + appVersion;
michael@0: var platformVersion = params.platformVersion ? params.platformVersion : "99.8";
michael@0: var buildID = params.buildID ? params.buildID : "01234567890123";
michael@0: // XXXrstrong - not specifying a detailsURL will cause a leak due to bug 470244
michael@0: // var detailsURL = params.showDetails ? URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS" : null;
michael@0: var detailsURL = URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS";
michael@0: var billboardURL = params.showBillboard ? URL_HTTP_UPDATE_SJS + "?uiURL=BILLBOARD" : null;
michael@0: if (billboardURL && params.remoteNoTypeAttr)
michael@0: billboardURL += "&remoteNoTypeAttr=1";
michael@0: if (params.billboard404)
michael@0: billboardURL = URL_HOST + "/missing.html";
michael@0: var licenseURL = params.showLicense ? URL_HTTP_UPDATE_SJS + "?uiURL=LICENSE" : null;
michael@0: if (licenseURL && params.remoteNoTypeAttr)
michael@0: licenseURL += "&remoteNoTypeAttr=1";
michael@0: if (params.license404)
michael@0: licenseURL = URL_HOST + "/missing.html";
michael@0: var showPrompt = params.showPrompt ? "true" : null;
michael@0: var showNever = params.showNever ? "true" : null;
michael@0: var promptWaitTime = params.promptWaitTime ? params.promptWaitTime : null;
michael@0: var showSurvey = params.showSurvey ? "true" : null;
michael@0:
michael@0: // For testing the deprecated update xml format
michael@0: if (params.oldFormat) {
michael@0: appVersion = null;
michael@0: displayVersion = null;
michael@0: billboardURL = null;
michael@0: showPrompt = null;
michael@0: showNever = null;
michael@0: showSurvey = null;
michael@0: detailsURL = URL_HTTP_UPDATE_SJS + "?uiURL=BILLBOARD";
michael@0: if (params.remoteNoTypeAttr)
michael@0: detailsURL += "&remoteNoTypeAttr=1";
michael@0: var extensionVersion = params.appVersion ? params.appVersion : "99.9";
michael@0: var version = params.displayVersion ? params.displayVersion
michael@0: : "version " + extensionVersion;
michael@0: }
michael@0:
michael@0: var updates = getRemoteUpdateString(patches, type, "App Update Test",
michael@0: displayVersion, appVersion,
michael@0: platformVersion, buildID, detailsURL,
michael@0: billboardURL, licenseURL, showPrompt,
michael@0: showNever, promptWaitTime, showSurvey,
michael@0: version, extensionVersion);
michael@0:
michael@0: aResponse.write(getRemoteUpdatesXMLString(updates));
michael@0: }
michael@0:
michael@0: /**
michael@0: * Helper function to create a JS object representing the url parameters from
michael@0: * the request's queryString.
michael@0: *
michael@0: * @param aQueryString
michael@0: * The request's query string.
michael@0: * @return A JS object representing the url parameters from the request's
michael@0: * queryString.
michael@0: */
michael@0: function parseQueryString(aQueryString) {
michael@0: var paramArray = aQueryString.split("&");
michael@0: var regex = /^([^=]+)=(.*)$/;
michael@0: var params = {};
michael@0: for (var i = 0, sz = paramArray.length; i < sz; i++) {
michael@0: var match = regex.exec(paramArray[i]);
michael@0: if (!match)
michael@0: throw "Bad parameter in queryString! '" + paramArray[i] + "'";
michael@0: params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
michael@0: }
michael@0:
michael@0: return params;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Helper function to gets the string representation of the contents of the
michael@0: * add-on's update manifest file.
michael@0: *
michael@0: * @param aParams
michael@0: * A JS object representing the url parameters from the request's
michael@0: * queryString.
michael@0: * @return A string representation of the contents of the add-on's update
michael@0: * manifest file.
michael@0: */
michael@0: function getUpdateRDF(aParams) {
michael@0: var addonVersion;
michael@0: var addonID = aParams.addonID;
michael@0: var addonUpdateType = addonID.split("_")[0];
michael@0: var maxVersion = aParams.platformVersion;
michael@0:
michael@0: switch (addonUpdateType) {
michael@0: case "updatecompatibility":
michael@0: // Use "1.0" for the add-on version for the compatibility update case since
michael@0: // the tests create all add-ons with "1.0" for the version.
michael@0: addonVersion = "1.0";
michael@0: break;
michael@0: case "updateversion":
michael@0: // Use "2.0" for the add-on version for the version update case since the
michael@0: // tests create all add-ons with "1.0" for the version.
michael@0: addonVersion = "2.0";
michael@0: break;
michael@0: default:
michael@0: return "\n" +
michael@0: "\n" +
michael@0: "\n";
michael@0: }
michael@0:
michael@0: return "\n" +
michael@0: "\n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " " + addonVersion + "\n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " toolkit@mozilla.org\n" +
michael@0: " 0\n" +
michael@0: " " + maxVersion + "\n" +
michael@0: " " + URL_HTTP_UPDATE_SJS + "\n" +
michael@0: " sha256:0\n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: " \n" +
michael@0: "\n";
michael@0: }
michael@0:
michael@0: /**
michael@0: * Reads the binary contents of a file and returns it as a string.
michael@0: *
michael@0: * @param aFile
michael@0: * The file to read from.
michael@0: * @return The contents of the file as a string.
michael@0: */
michael@0: function readFileBytes(aFile) {
michael@0: var fis = AUS_Cc["@mozilla.org/network/file-input-stream;1"].
michael@0: createInstance(AUS_Ci.nsIFileInputStream);
michael@0: fis.init(aFile, -1, -1, false);
michael@0: var bis = AUS_Cc["@mozilla.org/binaryinputstream;1"].
michael@0: createInstance(AUS_Ci.nsIBinaryInputStream);
michael@0: bis.setInputStream(fis);
michael@0: var data = [];
michael@0: var count = fis.available();
michael@0: while (count > 0) {
michael@0: var bytes = bis.readByteArray(Math.min(65535, count));
michael@0: data.push(String.fromCharCode.apply(null, bytes));
michael@0: count -= bytes.length;
michael@0: if (bytes.length == 0)
michael@0: throw "Nothing read from input stream!";
michael@0: }
michael@0: data.join('');
michael@0: fis.close();
michael@0: return data.toString();
michael@0: }