diff options
Diffstat (limited to 'devtools/client/webide/modules/app-validator.js')
-rw-r--r-- | devtools/client/webide/modules/app-validator.js | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/devtools/client/webide/modules/app-validator.js b/devtools/client/webide/modules/app-validator.js new file mode 100644 index 000000000..750720110 --- /dev/null +++ b/devtools/client/webide/modules/app-validator.js @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +var {Ci, Cu, CC} = require("chrome"); +const promise = require("promise"); + +const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {}); +const Services = require("Services"); +const {Task} = require("devtools/shared/task"); +var XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1"); +var strings = Services.strings.createBundle("chrome://devtools/locale/app-manager.properties"); + +function AppValidator({ type, location }) { + this.type = type; + this.location = location; + this.errors = []; + this.warnings = []; +} + +AppValidator.prototype.error = function (message) { + this.errors.push(message); +}; + +AppValidator.prototype.warning = function (message) { + this.warnings.push(message); +}; + +AppValidator.prototype._getPackagedManifestFile = function () { + let manifestFile = FileUtils.File(this.location); + if (!manifestFile.exists()) { + this.error(strings.GetStringFromName("validator.nonExistingFolder")); + return null; + } + if (!manifestFile.isDirectory()) { + this.error(strings.GetStringFromName("validator.expectProjectFolder")); + return null; + } + + let appManifestFile = manifestFile.clone(); + appManifestFile.append("manifest.webapp"); + + let jsonManifestFile = manifestFile.clone(); + jsonManifestFile.append("manifest.json"); + + let hasAppManifest = appManifestFile.exists() && appManifestFile.isFile(); + let hasJsonManifest = jsonManifestFile.exists() && jsonManifestFile.isFile(); + + if (!hasAppManifest && !hasJsonManifest) { + this.error(strings.GetStringFromName("validator.noManifestFile")); + return null; + } + + return hasAppManifest ? appManifestFile : jsonManifestFile; +}; + +AppValidator.prototype._getPackagedManifestURL = function () { + let manifestFile = this._getPackagedManifestFile(); + if (!manifestFile) { + return null; + } + return Services.io.newFileURI(manifestFile).spec; +}; + +AppValidator.checkManifest = function (manifestURL) { + let deferred = promise.defer(); + let error; + + let req = new XMLHttpRequest(); + req.overrideMimeType("text/plain"); + + try { + req.open("GET", manifestURL, true); + req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING; + } catch (e) { + error = strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1); + deferred.reject(error); + return deferred.promise; + } + + req.onload = function () { + let manifest = null; + try { + manifest = JSON.parse(req.responseText); + } catch (e) { + error = strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2); + deferred.reject(error); + } + + deferred.resolve({manifest, manifestURL}); + }; + + req.onerror = function () { + error = strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2); + deferred.reject(error); + }; + + try { + req.send(null); + } catch (e) { + error = strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2); + deferred.reject(error); + } + + return deferred.promise; +}; + +AppValidator.findManifestAtOrigin = function (manifestURL) { + let fixedManifest = Services.io.newURI(manifestURL, null, null).prePath + "/manifest.webapp"; + return AppValidator.checkManifest(fixedManifest); +}; + +AppValidator.findManifestPath = function (manifestURL) { + let deferred = promise.defer(); + + if (manifestURL.endsWith("manifest.webapp")) { + deferred.reject(); + } else { + let fixedManifest = manifestURL + "/manifest.webapp"; + deferred.resolve(AppValidator.checkManifest(fixedManifest)); + } + + return deferred.promise; +}; + +AppValidator.checkAlternateManifest = function (manifestURL) { + return Task.spawn(function* () { + let result; + try { + result = yield AppValidator.findManifestPath(manifestURL); + } catch (e) { + result = yield AppValidator.findManifestAtOrigin(manifestURL); + } + + return result; + }); +}; + +AppValidator.prototype._fetchManifest = function (manifestURL) { + let deferred = promise.defer(); + this.manifestURL = manifestURL; + + AppValidator.checkManifest(manifestURL) + .then(({manifest, manifestURL}) => { + deferred.resolve(manifest); + }, error => { + AppValidator.checkAlternateManifest(manifestURL) + .then(({manifest, manifestURL}) => { + this.manifestURL = manifestURL; + deferred.resolve(manifest); + }, () => { + this.error(error); + deferred.resolve(null); + }); + }); + + return deferred.promise; +}; + +AppValidator.prototype._getManifest = function () { + let manifestURL; + if (this.type == "packaged") { + manifestURL = this._getPackagedManifestURL(); + if (!manifestURL) + return promise.resolve(null); + } else if (this.type == "hosted") { + manifestURL = this.location; + try { + Services.io.newURI(manifestURL, null, null); + } catch (e) { + this.error(strings.formatStringFromName("validator.invalidHostedManifestURL", [manifestURL, e.message], 2)); + return promise.resolve(null); + } + } else { + this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1)); + return promise.resolve(null); + } + return this._fetchManifest(manifestURL); +}; + +AppValidator.prototype.validateManifest = function (manifest) { + if (!manifest.name) { + this.error(strings.GetStringFromName("validator.missNameManifestProperty")); + } + + if (!manifest.icons || Object.keys(manifest.icons).length === 0) { + this.warning(strings.GetStringFromName("validator.missIconsManifestProperty")); + } else if (!manifest.icons["128"]) { + this.warning(strings.GetStringFromName("validator.missIconMarketplace2")); + } +}; + +AppValidator.prototype._getOriginURL = function () { + if (this.type == "packaged") { + let manifestURL = Services.io.newURI(this.manifestURL, null, null); + return Services.io.newURI(".", null, manifestURL).spec; + } else if (this.type == "hosted") { + return Services.io.newURI(this.location, null, null).prePath; + } +}; + +AppValidator.prototype.validateLaunchPath = function (manifest) { + let deferred = promise.defer(); + // The launch_path field has to start with a `/` + if (manifest.launch_path && manifest.launch_path[0] !== "/") { + this.error(strings.formatStringFromName("validator.nonAbsoluteLaunchPath", [manifest.launch_path], 1)); + deferred.resolve(); + return deferred.promise; + } + let origin = this._getOriginURL(); + let path; + if (this.type == "packaged") { + path = "." + (manifest.launch_path || "/index.html"); + } else if (this.type == "hosted") { + path = manifest.launch_path || "/"; + } + let indexURL; + try { + indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec; + } catch (e) { + this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1)); + deferred.resolve(); + return deferred.promise; + } + + let req = new XMLHttpRequest(); + req.overrideMimeType("text/plain"); + try { + req.open("HEAD", indexURL, true); + req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING; + } catch (e) { + this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1)); + deferred.resolve(); + return deferred.promise; + } + req.onload = () => { + if (req.status >= 400) + this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2)); + deferred.resolve(); + }; + req.onerror = () => { + this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1)); + deferred.resolve(); + }; + + try { + req.send(null); + } catch (e) { + this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1)); + deferred.resolve(); + } + + return deferred.promise; +}; + +AppValidator.prototype.validateType = function (manifest) { + let appType = manifest.type || "web"; + if (["web", "privileged", "certified"].indexOf(appType) === -1) { + this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1)); + } else if (this.type == "hosted" && + ["certified", "privileged"].indexOf(appType) !== -1) { + this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1)); + } + + // certified app are not fully supported on the simulator + if (appType === "certified") { + this.warning(strings.GetStringFromName("validator.noCertifiedSupport")); + } +}; + +AppValidator.prototype.validate = function () { + this.errors = []; + this.warnings = []; + return this._getManifest(). + then((manifest) => { + if (manifest) { + this.manifest = manifest; + + // Skip validations for add-ons + if (manifest.role === "addon" || manifest.manifest_version) { + return promise.resolve(); + } + + this.validateManifest(manifest); + this.validateType(manifest); + return this.validateLaunchPath(manifest); + } + }); +}; + +exports.AppValidator = AppValidator; |