UNPKG

adxutil

Version:

Utilities tools for Askia Design eXtension

1,429 lines (1,235 loc) 44.5 kB
"use strict"; /* ASP Classic asp ASP.NET aspx axd asx asmx ashx axd ascx CSS css hss sass less ccss pcss Coldfusion cfm Erlang yaws Flash swf HTML html htm xhtml jhtml Java jsp jspx wss do action JavaScript js Perl pl PHP php php4 php3 phtml Python py Ruby rb rhtml rjs erb XML xml rss atom svg Other (C, perl etc.) cgi dll Executable Extension Format Operating System(s) ACTION Automator Action Mac OS APK Application Android APP Executable Mac OS BAT Batch File Windows BIN Binary Executable Windows, Mac OS, Linux CMD Command Script Windows COM Command File Windows COMMAND Terminal Command Mac OS CPL Control Panel Extension Windows CSH C Shell Script Mac OS, Linux EXE Executable Windows GADGET Windows Gadget Windows INF1 Setup Information File Windows INS Internet Communication Settings Windows INX InstallShield Compiled Script Windows IPA Application iOS ISU InstallShield Uninstaller Script Windows JOB Windows Task Scheduler Job File Windows JSE JScript Encoded File Windows KSH Unix Korn Shell Script Linux LNK File Shortcut Windows MSC Microsoft Common Console Document Windows MSI Windows Installer Package Windows MSP Windows Installer Patch Windows MST Windows Installer Setup Transform File Windows OSX Executable Mac OS OUT Executable Linux PAF Portable Application Installer File Windows PIF Program Information File Windows PRG Executable GEM PS1 Windows PowerShell Cmdlet Windows REG Registry Data File Windows RGS Registry Script Windows RUN Executable Linux SCT Windows Scriptlet Windows SHB Windows Document Shortcut Windows SHS Shell Scrap Object Windows U3P U3 Smart Application Windows VB VBScript File Windows VBE VBScript Encoded Script Windows VBS VBScript File Windows VBSCRIPT Visual Basic Script Windows WORKFLOW Automator Workflow Mac OS WS Windows Script Windows WSF Windows Script Windows Audio File Types and Formats .aif Audio Interchange File Format .iff Interchange File Format .m3u Media Playlist File .m4a MPEG-4 Audio File .mid MIDI File .mp3 MP3 Audio File .mpa MPEG-2 Audio File .ra Real Audio File .wav WAVE Audio File .wma Windows Media Audio File AddType audio/ogg ogg AddType audio/ogg oga AddType audio/webm webma Video Files Types and Formats .3g2 3GPP2 Multimedia File .3gp 3GPP Multimedia File .asf Advanced Systems Format File .asx Microsoft ASF Redirector File .avi Audio Video Interleave File .flv Flash Video File .mov Apple QuickTime Movie .mp4 MPEG-4 Video File .mpg MPEG Video File .rm Real Media File .swf Shockwave Flash Movie .vob DVD Video Object File .wmv Windows Media Video File Markdown md white xml|rss|atom|svg|js|xhtml|htm|html|swf|css|hss|sass|less|ccss|pcss|txt|csv|json| gif|jpeg|jpg|tif|tiff|png|bmp|pdf|ico|cur| aif|iff|m4a|mid|mp3|mpa|ra|wav|wma|ogg|oga|webma| 3g2|3gp|avi|flv|mov|mp4|mpg|rm|wmv|webm md| black cgi|dll|erb|rjs|rhtml|rb|py|phtml|php3|php4|php|pl|action|do|wss|jspx|jsp|jhtml|yaws|cfm|aspx|axd|asx|asmx|ashx|axd|ascx|asp|config| action|apk|app|bat|bin|cmd|com|command|cpl|csh|exe|gadget|inf1|ins|inx|ipa|isu| job|jse|ksh|lnk|msc|msi|msp|mst|ocx|osx|out|paf|pif|prg|ps1|reg|rgs|run|sct|shb|shs|u3p| vb|vbe|vbs|vbscript|workflow|ws|wsf|cs|cpp| zip|rar|sql|ini|dmg|iso|vcd|class|java|htaccess */ const fs = require('fs'); const pathHelper = require('path'); const util = require('util'); const common = require('../common/common.js'); const Configurator = require('../configurator/ADXConfigurator.js').Configurator; const errMsg = common.messages.error; const warnMsg = common.messages.warning; const successMsg = common.messages.success; const msg = common.messages.message; // Test the file extension const fileExt = { blacklist : /\.(cgi|dll|erb|rjs|rhtml|rb|py|phtml|php3|php4|php|pl|action|do|wss|jspx|jsp|jhtml|yaws|cfm|aspx|axd|asx|asmx|ashx|axd|ascx|asp|config|action|apk|app|bat|bin|cmd|com|command|cpl|csh|exe|gadget|inf1|ins|inx|ipa|isu|job|jse|ksh|lnk|msc|msi|msp|mst|ocx|osx|out|paf|pif|prg|ps1|reg|rgs|run|sct|shb|shs|u3p|vb|vbe|vbs|vbscript|workflow|ws|wsf|cs|cpp|zip|rar|sql|ini|dmg|iso|vcd|class|java|htaccess)$/gi, whitelist : /\.(xml|rss|atom|svg|js|xhtml|htm|html|swf|css|hss|sass|less|ccss|pcss|txt|csv|json|gif|jpeg|jpg|tif|tiff|png|bmp|pdf|ico|cur|aif|iff|m4a|mid|mp3|mpa|ra|wav|wma|ogg|oga|webma|3g2|3gp|avi|flv|mov|mp4|mpg|rm|wmv|ogv|webm|md)$/gi }; // Hash with all content type const contentType = { 'text' : 'text', 'html' : 'text', 'javascript': 'text', 'css' : 'text', 'binary' : 'binary', 'image' : 'binary', 'video' : 'binary', 'audio' : 'binary', 'flash' : 'binary' }; /* * Hash with the rule of the <attribute> node in the <content> * Indicates if the attribute is overridable or not * true for not-overridable */ const contentSealAttr = { 'javascript' : { 'src' : true, 'type' : false }, 'css' : { 'href' : true, 'rel' : false }, 'image' : { 'src' : true, 'alt' : false }, 'video' : { 'src' : true }, 'audio' : { 'src' : true } }; // Hash with the rule of the constraint attribute node. const constraintAttributeRules = { questions : ['chapter', 'single', 'multiple', 'open', 'numeric', 'date', 'requireParentLoop'], responses : ['min', 'max'], controls : ['label', 'textbox', 'checkbox', 'listbox', 'radiobutton', 'responseblock'] }; /** * Build a new error message * @param {String} message Error message * @return {Error} New error * @ignore */ function newError(message) { return new Error(util.format.apply(null, arguments)); } /** * Validate the ADX files structure, configuration and logical * * @class Validator * @param {String} adxDirPath Path of the ADX directory * @private */ function Validator(adxDirPath) { /** * Root dir of the current ADXUtil * * @name Validator#rootdir * @type {String} */ this.rootdir = pathHelper.resolve(__dirname, "../../"); /** * Name of the ADX * * @name Validator#adxName * @type {string} */ this.adxName = ''; /** * Path to the ADX directory * * @name Validator#adxDirectoryPath * @type {string} */ this.adxDirectoryPath = adxDirPath ? pathHelper.normalize(adxDirPath) : process.cwd(); /** * Validators * * @name Validator#valiaators * @type {{current: number, sequence: string[]}} */ this.validators = { current : -1, sequence : [ 'validatePathArg', 'validateADXDirectoryStructure', 'validateFileExtensions', 'initConfigXMLDoc', 'validateXMLAgainstXSD', 'validateADXInfo', 'validateADXInfoConstraints', 'validateADXOutputs', 'validateADXProperties', 'validateMasterPage', 'runAutoTests', 'runADXUnitTests' ] }; /** * Report of the validation * * @name Validator#report * @type {{startTime: number, endTime: number, runs: number, total: number, success: number, warnings: number, errors: number}} */ this.report = { startTime : null, endTime : null, runs : 0, total : 0, success : 0, warnings : 0, errors : 0 }; /** * Map all files in the resources directory * * @name Validator#dirResources * @type {{isExist: boolean, dynamic: {isExist: boolean}, statics: {isExist: boolean}, share: {isExist: boolean}}} */ this.dirResources = { isExist : false, dynamic : { isExist : false }, statics : { isExist : false }, share : { isExist : false } }; /** * Instance of configurator * * @name Validator#adxConfigurator * @type {Configurator} */ this.adxConfigurator = null; /** * Logger to override with an object * * @name Validator#logger * @type {{writeMessage : Function, writeSuccess : Function, writeWarning: Function, writeError : Function}} */ this.logger = null; /** * Print mode * * @name Validator#printMode * @type {String|"default"|"html"} */ this.printMode = 'default'; } /** * Create a new instance of ADX validator * * @ignore */ Validator.prototype.constructor = Validator; /** * Validate the current ADX instance * * @param {Object} [options] Options of validation * @param {String|'default'|'html'} [options.printMode='default'] Print mode (default console or html) * @param {Boolean} [options.test=true] Run unit tests * @param {Boolean} [options.autoTest=true] Run auto unit tests * @param {Boolean} [options.xml=true] Validate the config.xml file * @param {InteractiveADXShell} [options.adxShell] Interactive ADXShell process * @param {Object} [options.logger] Logger * @param {Function} [options.writeMessage] Function where regular messages will be print * @param {Function} [options.writeSuccess] Function where success messages will be print * @param {Function} [options.writeWarning] Function where warning messages will be print * @param {Function} [options.writeError] Function where error messages will be print * @param {Function} [callback] Callback function * @param {Error} [callback.err] Error * @param {Object} [callback.report] Validation report */ Validator.prototype.validate = function validate(options, callback) { // Start timer this.report.startTime = new Date().getTime(); // Reset the print mode this.printMode = 'default'; // Swap optional options arguments if (typeof options === 'function') { callback = options; options = null; } if (!options) { options = { test : true, autoTest : true, xml : true } } // Register the end callback for future usage this.validationCallback = callback; this._adxShell = (options && options.adxShell) || null; // Validate according to the options if (options) { // Set the logger if (options.logger) { this.logger = options.logger; } // --no-autoTest if (options.autoTest === false) { // Check bool value not falsy this.removeOnSequence(['runAutoTests']); } // --no-test if (options.test === false) { // Check bool value not falsy this.removeOnSequence(['runADXUnitTests']); } // --no-xml if (options.xml === false) { // Check bool value not falsy this.removeOnSequence([ 'initConfigXMLDoc', 'validateXMLAgainstXSD', 'validateADXInfo', 'validateADXInfoConstraints', 'validateADXOutputs', 'validateADXProperties', 'validateMasterPage' ]); } // print mode if (options.printMode) { this.printMode = options.printMode || 'default'; } } this.report.total = this.validators.sequence.length; this.resume(null); }; /** * Remove the specified validators on the validators sequence * * @param {Array} validators Validators to remove */ Validator.prototype.removeOnSequence = function removeOnSequence(validators) { const sequence = this.validators.sequence; validators.forEach((value) => { const index = sequence.indexOf(value); if (index !== -1) { sequence.splice(index, 1); } }); }; /** * Summarize the validation * * @param {Error} [err] Last error */ Validator.prototype.done = function done(err) { this.report.endTime = new Date().getTime(); const executionTime = this.report.endTime - this.report.startTime; const report = this.report; let message; if (err) { this.writeError(err.message); } // Write the summary this.writeMessage(msg.validationFinishedIn, executionTime); message = util.format(msg.validationReport, report.runs, report.total, report.success, report.warnings, report.errors, report.total - report.runs); if (report.errors) { this.writeError(message); } else if (report.warnings) { this.writeWarning(message); } else { this.writeSuccess(message); } if (typeof this.validationCallback === 'function') { this.validationCallback(err, this.report); } }; /** * Execute the next validation * * @param {Error|void} err Error which occurred during the previous validation */ Validator.prototype.resume = function resume(err) { if (err) { // Mark the error this.report.errors++; this.done(err); return; } const validators = this.validators; // Mark the success if (validators.current !== -1 && this[validators.sequence[validators.current]]) { this.report.success++; } validators.current++; if (validators.current >= validators.sequence.length) { this.done(err); return; } // Search the next validators (recursive call) const validatorName = validators.sequence[validators.current]; if (!this[validatorName]) { this.resume(null); return; } // Execute the find validator // Mark the runs this.report.runs++; this[validatorName](); }; /** * Write an error output in the console or in the logger * @param {String} text Text to write in the console */ Validator.prototype.writeError = function writeError(text) { const args = Array.prototype.slice.call(arguments); if (this.printMode === 'html' && args.length) { args[0] = '<div class="error">' + args[0] + '</div>'; } if (this.logger && typeof this.logger.writeError === 'function') { this.logger.writeError.apply(this.logger, args); } else { common.writeError.apply(common, args); } }; /** * Write a warning output in the console or in the logger * @param {String} text Text to write in the console */ Validator.prototype.writeWarning = function writeWarning(text) { const args = Array.prototype.slice.call(arguments); if (this.printMode === 'html' && args.length) { args[0] = '<div class="warning">' + args[0] + '</div>'; } if (this.logger && typeof this.logger.writeWarning === 'function') { this.logger.writeWarning.apply(this.logger, args); } else { common.writeWarning.apply(common, args); } }; /** * Write a success output in the console or in the logger * @param {String} text Text to write in the console */ Validator.prototype.writeSuccess = function writeSuccess(text) { const args = Array.prototype.slice.call(arguments); if (this.printMode === 'html' && args.length) { args[0] = '<div class="success">' + args[0] + '</div>'; } if (this.logger && typeof this.logger.writeSuccess === 'function') { this.logger.writeSuccess.apply(this.logger, args); } else { common.writeSuccess.apply(common, args); } }; /** * Write an arbitrary message in the console or in the logger without specific prefix or in the logger * @param {String} text Text to write in the console */ Validator.prototype.writeMessage = function writeMessage(text) { const args = Array.prototype.slice.call(arguments); if (this.printMode === 'html' && args.length) { args[0] = '<div class="message">' + args[0] + '</div>'; } if (this.logger && typeof this.logger.writeMessage === 'function') { this.logger.writeMessage.apply(this.logger, args); } else { common.writeMessage.apply(common, args); } }; /** * Validate that the `path` argument is correct */ Validator.prototype.validatePathArg = function validatePathArg() { if (!this.adxDirectoryPath) { this.resume(newError(errMsg.missingArgPath)); return; } // Validate the existence of the specify ADX directory const self = this; common.dirExists(self.adxDirectoryPath, (err, exists) => { let er; if (!exists) { er = newError(errMsg.noSuchFileOrDirectory, pathHelper.normalize(self.adxDirectoryPath)); } else { self.writeSuccess(successMsg.pathValidate); } self.resume(er); }); }; /** * Validate the structure of the ADX directory */ Validator.prototype.validateADXDirectoryStructure = function validateADXDirectoryStructure() { // Verify if the config.xml exists const self = this; fs.exists(pathHelper.join(self.adxDirectoryPath, common.CONFIG_FILE_NAME), function verifyConfigFileExist(exists) { const resourcesPath = pathHelper.join(self.adxDirectoryPath, common.RESOURCES_DIR_NAME); const dirResources = self.dirResources; // Check the resources directory function loadResources() { common.dirExists(resourcesPath, (er, find) => { if (!find) { self.resume(null); return; } dirResources.isExist = true; loadDynamic(); }); } // Check the dynamic directory function loadDynamic() { common.dirExists(pathHelper.join(resourcesPath, common.DYNAMIC_DIR_NAME), (er, find) => { const dirDynamic = dirResources.dynamic; dirDynamic.isExist = find; if (find) { try { const files = fs.readdirSync(pathHelper.join(resourcesPath, common.DYNAMIC_DIR_NAME)); files.forEach((file) => { if (common.isIgnoreFile(file)) { return; } dirDynamic[file.toLocaleLowerCase()] = file; }) } catch (ex) { // Do nothing } } loadStatic(); }); } // Check the static directory function loadStatic(){ common.dirExists(pathHelper.join(resourcesPath, common.STATIC_DIR_NAME), (er, find) => { const dirStatic = dirResources.statics; dirStatic.isExist = find; if (find) { try { const files = fs.readdirSync(pathHelper.join(resourcesPath, common.STATIC_DIR_NAME)); files.forEach((file) => { if (common.isIgnoreFile(file)) { return; } dirStatic[file.toLocaleLowerCase()] = file; }) } catch (ex) { // Do nothing } } loadShare(); }); } // Check the share directory and resume the validation function loadShare() { common.dirExists(pathHelper.join(resourcesPath, common.SHARE_DIR_NAME), (er, find) => { const dirShare = dirResources.share; dirShare.isExist = find; if (find) { try { const files = fs.readdirSync(pathHelper.join(resourcesPath, common.SHARE_DIR_NAME)); files.forEach((file) => { if (common.isIgnoreFile(file)) { return; } dirShare[file.toLocaleLowerCase()] = file; }) } catch (ex) { // Do nothing } } self.resume(null); }); } if (!exists) { self.resume(newError(errMsg.noConfigFile)); } else { self.writeSuccess(successMsg.directoryStructureValidate); loadResources(); } }); }; /** * Validate all file extension against the white list and the black list */ Validator.prototype.validateFileExtensions = function validateFileExtensions() { const dirResources = this.dirResources; const dir = [dirResources.dynamic, dirResources.statics, dirResources.share]; if (!dirResources.isExist) { this.resume(null); } for (let i = 0, l = dir.length; i < l; i++) { let current = dir[i]; if (current.isExist) { for (let key in current) { if (current.hasOwnProperty(key) && key !== 'isExist') { // Test against the black list let match = key.toString().match(fileExt.blacklist); if (match) { this.resume(newError(errMsg.fileExtensionForbidden, match[0])); return; } // Test against the white list match = key.toString().match(fileExt.whitelist); if (!match) { this.report.warnings++; this.writeWarning(warnMsg.untrustExtension, key); } } } } } this.writeSuccess(successMsg.fileExtensionValidate); this.resume(null); }; /** * Initialize the XMLDoc using the config.xml */ Validator.prototype.initConfigXMLDoc = function initConfigXMLDoc() { const self = this; self.adxConfigurator = new Configurator(self.adxDirectoryPath); self.adxConfigurator.load((err) => { if (err) { self.resume(err); return; } if (self.adxConfigurator.projectType === 'adp') { self.removeOnSequence(['validateADXInfoConstraints']); } else { self.removeOnSequence((['validateMasterPage'])); } self.report.total = self.validators.sequence.length; self.writeSuccess(successMsg.xmlInitialize); self.resume(null); }); }; /** * Validate the config.xml file of the ADX against the XSD schema */ Validator.prototype.validateXMLAgainstXSD = function validateXMLAgainstXSD() { const projectType = this.adxConfigurator.projectType; const rootEl = this.adxConfigurator.xmldoc.getroot(); const xmlns = rootEl.get('xmlns'); const isOldConfig = xmlns === "http://www.askia.com/ADCSchema"; const schemaName = projectType === 'adp' ? common.SCHEMA_ADP : (isOldConfig ? 'Config.xsd' : common.SCHEMA_ADC); const projectVersion = this.adxConfigurator.projectVersion + (isOldConfig ? 'alpha' : ''); const exec = require('child_process').exec; const xmlLintPath = pathHelper.join(this.rootdir, common.XML_LINT_PATH); const xmlSchemaPath = pathHelper.join(this.rootdir, common.SCHEMA_PATH, projectVersion, schemaName); const xmlPath = pathHelper.join(this.adxDirectoryPath, common.CONFIG_FILE_NAME); const self = this; const commandLine = '"' + xmlLintPath + '" --noout --schema "' + xmlSchemaPath + '" "' + xmlPath + '"'; exec(commandLine, (err) => { if (!err) { self.writeSuccess(successMsg.xsdValidate); } self.resume(err); }); }; /** * Validate the info of the ADX config file */ Validator.prototype.validateADXInfo = function validateADXInfo() { const xmldoc = this.adxConfigurator.xmldoc; const elInfo = xmldoc.find("info"); const elName = elInfo && elInfo.find("name"); if (!elInfo) { this.resume(newError(errMsg.missingInfoNode)); return; } if (!elName || !elName.text) { this.resume(newError(errMsg.missingOrEmptyNameNode)); return; } // The `style` and `categories` tags is mark as deprecated since the version 2.1.0 if (this.adxConfigurator.projectVersion !== "2.0.0") { const elStyle = elInfo && elInfo.find("style"); if (elStyle) { this.report.warnings++; this.writeWarning(warnMsg.deprecatedInfoStyleTag); } const elCategories = elInfo && elInfo.find("categories"); if (elCategories) { this.report.warnings++; this.writeWarning(warnMsg.deprecatedInfoCategoriesTag); } } this.adxName = elName.text; this.writeSuccess(successMsg.xmlInfoValidate); this.resume(null); }; /** * Validate the info/constraints of the ADX config file */ Validator.prototype.validateADXInfoConstraints = function validateADXInfoConstraints() { const xmldoc = this.adxConfigurator.xmldoc; const elInfo = xmldoc.find("info"); const elConstraints = elInfo && elInfo.find("constraints"); const constraintsOn = { questions : 0, responses : 0, controls : 0 }; const self = this; let exitIter = false; elConstraints.iter('constraint', (constraint) => { if (exitIter) { return; } let hasRule = false; const attrOn = constraint.get("on"); if (!attrOn) { return; } // Validate the duplicate constraints constraintsOn[attrOn]++; if (constraintsOn[attrOn] > 1) { self.resume(newError(errMsg.duplicateConstraints, attrOn)); exitIter = true; return; } // Validate the attribute logic const attr = constraint.attrib; for (let key in attr) { if (attr.hasOwnProperty(key) && key !== 'on') { if (constraintAttributeRules[attrOn].indexOf(key) === -1) { self.resume(newError(errMsg.invalidConstraintAttribute, attrOn, key)); exitIter = true; return; } if (key !== 'min' && key !== 'max') { if (attr[key] == '1' || attr[key] == 'true') { hasRule = true; } } else { hasRule = true; } } } // No rule specified if (!hasRule) { self.resume(newError(errMsg.noRuleOnConstraint, attrOn)); exitIter = true; } }); if (exitIter) { return; } if (!constraintsOn.questions) { this.resume(newError(errMsg.requireConstraintOn, 'questions')); return; } if (!constraintsOn.controls) { this.resume(newError(errMsg.requireConstraintOn, 'controls')); return; } this.writeSuccess(successMsg.xmlInfoConstraintsValidate); this.resume(null); }; /** * Validate the outputs of the ADX config file */ Validator.prototype.validateADXOutputs = function validateADXOutputs() { const projectType = this.adxConfigurator.projectType; const projectVersion = this.adxConfigurator.projectVersion; const dirResources = this.dirResources; const xmldoc = this.adxConfigurator.xmldoc; const elOutputs = xmldoc.find("outputs"); const conditions = {}; const outputsEmptyCondition = []; const self = this; let htmlFallBackCount = 0; let exitIter = false; let err; elOutputs.iter("output", (output) => { if (exitIter) { return; } const id = output.get("id"); const elCondition = output.find("condition"); const condition = elCondition && elCondition.text; const defaultGeneration = output.get("defaultGeneration") || false; // Require a `masterPage` attribute that represent an existing dynamic file for ADP if (projectType === 'adp') { const masterPage = output.get('masterPage'); // Verify the presence of a non-empty masterPage attribute if (!masterPage) { err = newError(errMsg.missingOrEmptyMasterPageAttr, id); self.resume(err); exitIter = true; return; } // Verify that the masterPage exist in the `dynamic` folder if (!dirResources.dynamic || !dirResources.dynamic[masterPage.toLocaleLowerCase()]) { err = newError(errMsg.cannotFindFileInDirectory, id, masterPage, 'dynamic'); self.resume(err); exitIter = true; return; } } if (!condition) { outputsEmptyCondition.push(id); } if (condition && conditions[condition]) { self.report.warnings++; self.writeWarning(warnMsg.duplicateOutputCondition, conditions[condition], id); } // defaultGeneration attribute is mark as deprecated in the 2.1.0 if (projectVersion !== '2.0.0') { if ("defaultGeneration" in output.attrib) { self.report.warnings++; self.writeWarning(warnMsg.deprecatedDefaultGenerationAttr); } } conditions[condition] = id; const lastOutput = { id : id, defaultGeneration : defaultGeneration, contents : output.findall("content") || [], condition : condition, dynamicContentCount : 0, javascriptContentCount : 0, flashContentCount : 0 }; err = self.validateADXContents(lastOutput); if (defaultGeneration || !lastOutput.javascriptContentCount) { htmlFallBackCount++; } if (err) { self.resume(err); exitIter = true; } }); if (exitIter) { return; } if (outputsEmptyCondition.length > 1) { err = newError(errMsg.tooManyEmptyCondition, outputsEmptyCondition.join(", ")); } if (!htmlFallBackCount) { this.report.warnings++; this.writeWarning(warnMsg.noHTMLFallBack); } if (!err) { this.writeSuccess(successMsg.xmlOutputsValidate); } this.resume(err); }; /** * Validate the contents of an ADX output * * @param {Object} output Helper output object * @return {Error|void} Return the error or null when no error. */ Validator.prototype.validateADXContents = function validateADXContents(output) { const projectType = this.adxConfigurator.projectType; const contents = output.contents; const condition = output.condition || ""; let err = null; if (contents.length && !this.dirResources.isExist) { return newError(errMsg.noResourcesDirectory); } for (let i = 0, l = contents.length; i < l; i++) { err = this.validateADXContent(output, contents[i]); if (err) { return err; } } // ADP doesn't require content, it already have at least one masterPage if (projectType !== 'adp' ) { if (!output.defaultGeneration && !output.dynamicContentCount) { return newError(errMsg.dynamicFileRequire, output.id); } if (!output.defaultGeneration && output.javascriptContentCount && !/browser\.support\("javascript"\)/gi.test(condition)) { this.report.warnings++; this.writeWarning(warnMsg.javascriptUseWithoutBrowserCheck, output.id); } if (!output.defaultGeneration && output.flashContentCount && !/browser\.support\("flash"\)/gi.test(condition)) { this.report.warnings++; this.writeWarning(warnMsg.flashUseWithoutBrowserCheck, output.id); } } return err; }; /** * Validate the content of an ADX output * * @param {Object} output Helper output object * @param {Object} content Content node * @return {Error|void} Return the error or null when no error. */ Validator.prototype.validateADXContent = function validateADXContent(output, content) { const atts = content.attrib; const type = atts.type; const position = atts.position; const mode = atts.mode; const key = (mode !== 'static') ? mode : 'statics'; const fileName = atts.fileName; const yieldNode = content.find('yield'); const dirResources = this.dirResources; // Missing directory if (!dirResources[key].isExist) { return newError(errMsg.cannotFindDirectory, mode); } // Missing file if (!dirResources[key][fileName.toLocaleLowerCase()]) { return newError(errMsg.cannotFindFileInDirectory, output.id, fileName, mode); } // A binary file could not be dynamic if (mode === 'dynamic' && contentType[type] === 'binary') { return newError(errMsg.typeCouldNotBeDynamic, output.id, type, fileName); } // A binary file require a 'yield' node or 'position=none' if (type === 'binary' && position !== 'none' && (!yieldNode || !yieldNode.text)) { return newError(errMsg.yieldRequireForBinary, output.id, fileName); } // Increment the information about the dynamic content if (position !== 'none' && mode === 'dynamic' && (type === 'javascript' || type === 'html')) { output.dynamicContentCount++; } // Increment the information about the javascript content if (type === 'javascript') { output.javascriptContentCount++; } // Increment the information about the flash content if (type === 'flash') { output.flashContentCount++; } // Validate attribute return this.validateADXContentAttribute(output, content); }; /** * Validate the attribute tag of the content node * * @param {Object} output Helper output object * @param {Object} content Content node * @return {Error|void} Return error or null when no error */ Validator.prototype.validateADXContentAttribute = function validateADXContentAttribute(output, content) { const attributes = content.findall('attribute'); if (!attributes || !attributes.length) { return null; } const atts = content.attrib; const type = atts.type; const mode = atts.mode; const fileName = atts.fileName; const yieldNode = content.find("yield"); // Attribute nodes are ignored for the following type if (/(text|binary|html|flash)/.test(type)) { this.report.warnings++; this.writeWarning(warnMsg.attributeNodeWillBeIgnored, output.id, type, fileName); } // Attribute nodes are ignored for dynamic mode if (mode === 'dynamic') { this.report.warnings++; this.writeWarning(warnMsg.attributeNodeAndDynamicContent, output.id, fileName); } // Attribute nodes are ignored with yield if (yieldNode && yieldNode.text) { this.report.warnings++; this.writeWarning(warnMsg.attributeNodeAndYieldNode, output.id, fileName); } const attMap = {}; for (let i = 0, l = attributes.length; i < l; i++) { let attribute = attributes[i]; let attName = (attribute.attrib && attribute.attrib.name && attribute.attrib.name.toLocaleLowerCase()) || ''; if (contentSealAttr[type] && contentSealAttr[type][attName]) { return newError(errMsg.attributeNotOverridable, output.id, attName, fileName); } if (attMap[attName]) { return newError(errMsg.duplicateAttributeNode, output.id, attName, fileName); } if (attName) { attMap[attName] = attName; } } return null; }; /** * Validate the ADX properties node */ Validator.prototype.validateADXProperties = function validateADXProperties() { const xmldoc = this.adxConfigurator.xmldoc; const elProperties = xmldoc.find("properties"); const categories = elProperties.findall('category'); const properties = elProperties.findall('property'); if ((!properties || !properties.length) && (!categories || !categories.length)) { this.report.warnings++; this.writeWarning(warnMsg.noProperties); } this.writeSuccess(successMsg.xmlPropertiesValidate); this.resume(null); }; /** * Validate the content of the master page of ADP */ Validator.prototype.validateMasterPage = function validateMasterPage() { const self = this; const xmldoc = this.adxConfigurator.xmldoc; const elOutputs = xmldoc.findall("./outputs/output"); const treats = {}; const len = elOutputs.length; let outputIndex = 0; // Recursive calls to validate each master page one by one function validateNextMasterPage() { const elOutput = elOutputs[outputIndex]; const masterPage = elOutput.get("masterPage"); // Don't validate the master page several time if (treats[masterPage]) { outputIndex++; if (outputIndex < len) { return validateNextMasterPage(); } return; } treats[masterPage] = true; const masterPagePath = pathHelper.join(self.adxDirectoryPath, 'resources/dynamic', masterPage); fs.readFile(masterPagePath, (err, data) => { if (err) { self.resume(err); return; } data = data.toString(); const askiaHeadCount = (data.match(/<askia\-head\s*\/?>/gi) || []).length; // <askia-head /> required if (!askiaHeadCount) { self.resume(newError(errMsg.masterPageRequireAskiaHeadTag, masterPage)); return; } if (askiaHeadCount > 1) { self.resume(newError(errMsg.masterPageRequireOnlyOneAskiaHeadTag, masterPage)); return; } // <askia-form> required const askiaFormCount = (data.match(/<askia\-form\s*>/gi) || []).length; if (!askiaFormCount) { self.resume(newError(errMsg.masterPageRequireAskiaFormTag, masterPage)); return; } if (askiaFormCount > 1) { self.resume(newError(errMsg.masterPageRequireOnlyOneAskiaFormTag, masterPage)); return; } // </askia-form> required const askiaFormCloseCount = (data.match(/<\/askia\-form\s*>/gi) || []).length; if (!askiaFormCloseCount) { self.resume(newError(errMsg.masterPageRequireAskiaFormCloseTag, masterPage)); return; } if (askiaFormCloseCount > 1) { self.resume(newError(errMsg.masterPageRequireOnlyOneAskiaFormCloseTag, masterPage)); return; } // <askia-questions/> required const askiaQuestionsCount = (data.match(/<askia\-questions\s*\/?>/gi) || []).length; if (!askiaQuestionsCount) { self.resume(newError(errMsg.masterPageRequireAskiaQuestionsTag, masterPage)); return; } if (askiaQuestionsCount > 1) { self.resume(newError(errMsg.masterPageRequireOnlyOneAskiaQuestionsTag, masterPage)); return; } // <askia-foot/> required const askiaFootCount = (data.match(/<askia\-foot\s*\/?>/gi) || []).length; if (!askiaFootCount) { self.resume(newError(errMsg.masterPageRequireAskiaFootTag, masterPage)); return; } if (askiaFootCount > 1) { self.resume(newError(errMsg.masterPageRequireOnlyOneAskiaFootTag, masterPage)); return; } // Validate that the <askia-questions/> is between <askia-form> and </askia-form> // This ultimate validation also ensure that </askia-form> appear after <askia-form> const isCorrectForm = /<askia\-form\s*>(.|\r|\n)*<askia\-questions\s*\/?>(.|\r|\n)*<\/askia\-form\s*>/mgi.test(data); if (!isCorrectForm) { self.resume(newError(errMsg.masterPageRequireAskiaQuestionsTagInsideAskiaFormTag, masterPage)); return; } outputIndex++; if (outputIndex < len) { return validateNextMasterPage(); } }); } validateNextMasterPage(); this.writeSuccess(successMsg.masterPageAskiaTagsValidate); this.resume(null); }; /** * Run the ADX Unit tests process with specify arguments * @param {Array} args * @param {String} message */ Validator.prototype.runTests = function runTests(args, message) { const self = this; // Validate the existence of the specify unit test directory common.dirExists(pathHelper.join(self.adxDirectoryPath, common.UNIT_TEST_DIR_PATH), (err, exists) => { if (!exists) { self.resume(null); return ; } function execCallback(err, data, stderr) { if (stderr) { err = new Error(stderr); } self.writeMessage(message); if (err) { self.report.warnings++; self.writeWarning("\r\n" + err.message); if (data) { self.writeMessage(data); } } else { self.writeMessage(data); self.writeSuccess(successMsg.adxUnitSucceed); } self.resume(null); } if (self.printMode === 'html' && args.indexOf('--html') === -1) { args.unshift('--html'); } if (!self._adxShell) { const execFile = require('child_process').execFile; execFile('.\\' + common.ADX_UNIT_PROCESS_NAME, args, { cwd : pathHelper.join(self.rootdir, common.ADX_UNIT_DIR_PATH), env : common.getChildProcessEnv() }, execCallback); } else { const escapedArgs = []; for (var i = 0, l = args.length; i < l; i += 1) { escapedArgs.push('"' + args[i] + '"'); } self._adxShell.exec('test ' + escapedArgs.join(' '), execCallback); } }); }; /** * Run the ADX unit tests auto-generated */ Validator.prototype.runAutoTests = function runAutoTests() { this.runTests(['--auto', this.adxDirectoryPath], msg.runningAutoUnit); }; /** * Run the ADX unit tests */ Validator.prototype.runADXUnitTests = function runADXUnitTests() { this.runTests([this.adxDirectoryPath], msg.runningADXUnit); }; // Export the Validator object exports.Validator = Validator; /* * Validate an ADX (CLI) * * @param {Command} program Commander object which hold the arguments pass to the program * @param {String} path Path to the ADX directory * @param {Function} callback Callback function to run at the end it take a single Error argument * @ignore */ exports.validate = function validate(program, path, callback) { const validator = new Validator(path); validator.validate(program, callback); };