diff --git a/build/build.properties b/build/build.properties
index 3d2d417a..d60adc40 100644
--- a/build/build.properties
+++ b/build/build.properties
@@ -10,16 +10,16 @@ java.debug=true
#dependencies
dependencies.dir=${basedir}/lib
-cfml.version=5.3.9.141
+cfml.version=5.3.9.160
cfml.extensions=8D7FB0DF-08BB-1589-FE3975678F07DB17
-cfml.loader.version=2.6.20
+cfml.loader.version=2.7.2
cfml.cli.version=${cfml.loader.version}.${cfml.version}
lucee.version=${cfml.version}
# Don't bump this version. Need to remove this dependency from cfmlprojects.org
lucee.config.version=5.2.4.37
jre.version=jdk-11.0.15+10
launch4j.version=3.14
-runwar.version=4.7.7
+runwar.version=4.7.13
jline.version=3.21.0
jansi.version=2.3.2
jgit.version=5.13.0.202109080827-r
diff --git a/build/build.xml b/build/build.xml
index b5502e26..f0f6eee0 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -16,8 +16,8 @@ External Dependencies:
-
-
+
+
@@ -382,7 +382,7 @@ External Dependencies:
-
+
@@ -1071,7 +1071,7 @@ External Dependencies:
-
+
diff --git a/src/cfml/system/BaseTask.cfc b/src/cfml/system/BaseTask.cfc
index 81ccc4fe..b041d4f8 100644
--- a/src/cfml/system/BaseTask.cfc
+++ b/src/cfml/system/BaseTask.cfc
@@ -54,6 +54,46 @@ component accessors="true" extends='commandbox.system.BaseCommand' {
wirebox.getInstance( 'moduleService' ).registerAndActivateModule( moduleName, invocationPath );
}
+ /**
+ * Loads an array of module directories. All modules will be registered first. Then all will be activated.
+ */
+ function loadModules( required array moduleDirectories ) {
+ if( !moduleDirectories.len() ) {
+ return;
+ }
+ var moduleService = wirebox.getInstance( 'moduleService' );
+
+ // Do a pass to convert the array of paths into a struct where the key is the module name and the value is the invocation path
+ var modulesToLoad = moduleDirectories.reduce( (modulesToLoad,moduleDirectory)=>{
+ // Expand path relative to the task CFC.
+ moduleDirectory = resolvePath( moduleDirectory );
+
+ // A little validation...
+ if( !directoryExists( moduleDirectory ) ) {
+ error( 'Cannot load module. Path [#moduleDirectory#] doesn''t exist.' );
+ }
+
+ // Generate a CF mapping that points to the module's folder
+ var relativeModulePath = fileSystemUtil.makePathRelative( moduleDirectory );
+
+ // A dot delimited path that points to the folder containing the module
+ var invocationPath = relativeModulePath
+ .listChangeDelims( '.', '/\' )
+ .listDeleteAt( relativeModulePath.listLen( '/\' ), '.' );
+
+ // The name of the module
+ var moduleName = relativeModulePath.listLast( '/\' );
+
+ modulesToLoad[ moduleName ] = invocationPath;
+ return modulesToLoad;
+ } );
+
+ // Register all of them
+ modulesToLoad.each( (moduleName,invocationPath)=>moduleService.registerModule( moduleName, invocationPath ) );
+ // Activate all of them
+ modulesToLoad.each( (moduleName)=>moduleService.activateModule( moduleName ) );
+ }
+
/**
* This resolves an absolute or relative path using the rules of the operating system and CLI.
* It doesn't follow CF mappings and will also always return a trailing slash if pointing to
diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc
index ab40e955..1a5681f5 100644
--- a/src/cfml/system/Shell.cfc
+++ b/src/cfml/system/Shell.cfc
@@ -28,6 +28,7 @@ component accessors="true" singleton {
property name="configService" inject="configService";
property name='systemSettings' inject='SystemSettings';
property name='endpointService' inject='endpointService';
+ property name='JSONService' inject='JSONService';
/**
* The java jline reader class.
@@ -872,10 +873,7 @@ component accessors="true" singleton {
// We get to output the results ourselves
if( !isNull( result ) && !isSimpleValue( result ) ){
- if( isArray( result ) ){
- return variables.reader.getTerminal().writer().printColumns( result );
- }
- result = variables.formatterUtil.formatJson( result );
+ result = variables.formatterUtil.formatJson( JSON=result, ANSIColors=JSONService.getANSIColors() );
printString( result );
} else if( !isNull( result ) && len( result ) ) {
// If there is an active job, print our output through it
diff --git a/src/cfml/system/config/CommandBoxDSL.cfc b/src/cfml/system/config/CommandBoxDSL.cfc
index a30cd49e..c7b007aa 100644
--- a/src/cfml/system/config/CommandBoxDSL.cfc
+++ b/src/cfml/system/config/CommandBoxDSL.cfc
@@ -51,6 +51,7 @@ component implements="wirebox.system.ioc.dsl.IDSLBuilder" accessors=true{
case "ConfigSettings" : { return getInjector().getInstance( 'ConfigService' ).getConfigSettings(); }
case "interceptorService" : { return getInjector().getInstance( 'interceptorService' ); }
case "moduleService" : { return getInjector().getInstance( 'moduleService' ); }
+ case "asyncManager" : { return getInjector().getAsyncManager(); }
}
break;
diff --git a/src/cfml/system/config/server.schema.json b/src/cfml/system/config/server.schema.json
index 0092af1b..eb6251ac 100644
--- a/src/cfml/system/config/server.schema.json
+++ b/src/cfml/system/config/server.schema.json
@@ -7,46 +7,59 @@
"type": "object",
"properties": {
"label": {
- "title": "Label",
+ "title": "Tray Item Label",
"description": "Text of menu item",
"type": "string"
},
"action": {
- "title": "Action",
- "description": "Action to perform when user clicks this menu item. 'openfilesystem', 'openbrowser', or 'stopserver'",
+ "title": "Tray Item Action",
+ "description": "Action to perform when user clicks this menu item",
+ "type": "string",
+ "enum": [
+ "openfilesystem",
+ "openbrowser",
+ "stopserver",
+ "run",
+ "runAsync",
+ "runTerminal"
+ ]
+ },
+ "command": {
+ "title": "Tray Item Command",
+ "description": "A command that is run relative to webroot",
"type": "string"
},
"url": {
- "title": "URL",
- "description": "Url to open for 'openbrowser' action",
+ "title": "Tray Item URL",
+ "description": "URL to open for 'openbrowser' action",
"type": "string"
},
"disabled": {
- "title": "Disabled",
+ "title": "Tray Item Disabled",
"description": "Turn menu item grey and nothing happens when clicking on it",
"type": "boolean",
"default": false
},
"image": {
- "title": "Image",
+ "title": "Tray Item Image",
"description": "Path to PNG image to display on menu item next to the label",
"type": "string",
"default": ""
},
"hotkey": {
- "title": "Hotkey",
+ "title": "Tray Item Hotkey",
"description": "Keyboard shortcut to choose this menu item",
"type": "string",
"default": ""
},
"path": {
- "title": "Path",
+ "title": "Tray Item Path",
"description": "Filesystem path to open for 'openfilesystem' action",
"type": "string",
"default": ""
},
"items": {
- "title": "Items",
+ "title": "Tray Item Submenu Items",
"description": "Nested menu items",
"type": "array",
"minItems": 0,
@@ -63,7 +76,7 @@
"type": "object",
"properties": {
"name": {
- "title": "Name",
+ "title": "Server Name",
"description": "The name of the server",
"type": "string",
"default": ""
@@ -81,13 +94,13 @@
"default": ""
},
"startTimeout": {
- "title": "Server start timeout",
+ "title": "Server Start Timeout",
"description": "The length of time in seconds to wait for the server to start",
"type": "number",
"default": 240
},
"stopsocket": {
- "title": "Stop Socket",
+ "title": "Server Stop Socket",
"description": "The port the server listens on to receive a stop command",
"type": "number",
"default": 0
@@ -110,6 +123,22 @@
"type": "boolean",
"default": false
},
+ "profile": {
+ "title": "Server Profile",
+ "description": "Profile to assign to a server when you start it to configure the default settings",
+ "type": "string",
+ "enum": [
+ "development",
+ "production",
+ "none"
+ ]
+ },
+ "dockEnable": {
+ "title": "Dock Enable",
+ "description": "",
+ "type": "boolean",
+ "default": true
+ },
"trayEnable": {
"title": "Tray Enable",
"description": "Control whether the server has an associated icon in the system tray",
@@ -132,6 +161,16 @@
},
"default": []
},
+ "env": {
+ "title": "Environment Variables",
+ "description": "Ad-hoc environment variables",
+ "type": "object",
+ "additionalProperties": {
+ "title": "Environment Variable",
+ "description": "Ad-hoc environment variable"
+ },
+ "default": {}
+ },
"jvm": {
"title": "JVM",
"description": "JVM Options",
@@ -158,7 +197,16 @@
"args": {
"title": "JVM Arguments",
"description": "Ad-hoc JVM args for the server such as -X:name",
- "type": "string",
+ "type": [
+ "string",
+ "array"
+ ],
+ "items": {
+ "title": "JVM Argument",
+ "description": "Ad-hoc JVM arg for the server such as -X:name",
+ "type": "string",
+ "default": ""
+ },
"default": ""
},
"javaHome": {
@@ -172,6 +220,12 @@
"description": "A Java installation ID. In its entirety, it has the form _____",
"type": "string",
"default": ""
+ },
+ "properties": {
+ "title": "JVM Properties",
+ "description": "Ad-hoc Java system properties",
+ "type": "object",
+ "default": {}
}
}
},
@@ -195,29 +249,40 @@
"directoryBrowsing": {
"title": "Directory Browsing",
"description": "Enables file listing for directories with no welcome file",
- "type": "boolean",
- "default": false
+ "type": "boolean"
},
"accessLogEnable": {
"title": "Access Log Enable",
"description": "Enable web server access log",
"type": "boolean",
- "default": true
+ "default": false
},
- "GZIPEnable": {
- "title": "GZIP Enable",
- "description": "Enable GZip compression in HTTP responses",
+ "gzipEnable": {
+ "title": "Gzip Enable",
+ "description": "Enable gzip compression in HTTP responses",
"type": "boolean",
"default": true
},
+ "gzipPredicate": {
+ "title": "Gzip Predicate",
+ "description": "A custom Undertow Predicate that, when true, will trigger gzip for the request",
+ "type": "string",
+ "default": ""
+ },
"welcomeFiles": {
"title": "Welcome Files",
"description": "A comma-delimited list of files that you would like CommandBox to look for when a user hits a directory",
"type": "string",
"default": ""
},
+ "maxRequests": {
+ "title": "Web Max Requests",
+ "description": "",
+ "type": "string",
+ "default": ""
+ },
"aliases": {
- "title": "Aliases",
+ "title": "Web Aliases",
"description": "Web aliases for the web server, similar to virtual directories",
"type": "object",
"patternProperties": {
@@ -236,7 +301,7 @@
"type": "object",
"properties": {
"default": {
- "title": "Default",
+ "title": "Error Page Default",
"description": "Path to default error page",
"type": "string",
"default": ""
@@ -258,55 +323,140 @@
"type": "object",
"properties": {
"enable": {
- "title": "Enable",
+ "title": "HTTP Enable",
"description": "Enable HTTP for this serer",
"type": "boolean",
"default": true
},
"port": {
- "title": "Port",
+ "title": "HTTP Port",
"description": "HTTP port to use",
"type": "number",
"default": 0
}
}
},
+ "HTTP2": {
+ "title": "HTTP2 Settings",
+ "description": "Configure HTTP2",
+ "type": "object",
+ "properties": {
+ "enable": {
+ "title": "HTTP2 Enable",
+ "description": "Enable HTTP2 for this serer",
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
"SSL": {
"title": "SSL",
"description": "Configure the HTTPS listener on the server",
"type": "object",
"properties": {
"enable": {
- "title": "Enable",
+ "title": "SSL Enable",
"description": "Enable HTTPS for this server",
"type": "boolean",
"default": false
},
"port": {
- "title": "Port",
+ "title": "SSL Port",
"description": "HTTPS port to use",
"type": "number",
"default": 1443
},
"certFile": {
- "title": "Cert File",
+ "title": "SSL Cert File",
"description": "Path to SSL cert file",
"type": "string",
"default": ""
},
"keyFile": {
- "title": "Key File",
+ "title": "SSL Key File",
"description": "Path to SSL key file",
"type": "string",
"default": ""
},
"keyPass": {
- "title": "Key Pass",
+ "title": "SSL Key Pass",
"description": "Password for SSL key file",
"type": "string",
"default": ""
+ },
+ "forceSSLRedirect": {
+ "title": "Force SSL Redirect",
+ "description": "Whether to redirect all HTTP traffic over to HTTPS using a 301 status code",
+ "type": "boolean",
+ "default": false
+ },
+ "HSTS": {
+ "title": "HSTS",
+ "description": "HTTP Strict Transport Security configuration",
+ "type": "object",
+ "properties": {
+ "enable": {
+ "title": "HSTS Enable",
+ "description": "Whether to add a Strict-Transport-Security HTTP header",
+ "type": "boolean",
+ "default": false
+ },
+ "maxAge": {
+ "title": "HSTS Max Age",
+ "description": "How many seconds to remember to use HTTPS",
+ "type": "number",
+ "default": 31536000
+ },
+ "includeSubDomains": {
+ "title": "HSTS Include Subdomains",
+ "description": "Whether the HSTS header applies to all subdomains",
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "required": [ "enable" ]
+ },
+ "clientCert": {
+ "title": "SSL Client Cert",
+ "description": "",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "title": "Client Cert Mode",
+ "description": "",
+ "type": "string",
+ "default": ""
+ },
+ "CACertFiles": {
+ "title": "CA Cert Files",
+ "description": "",
+ "type": [
+ "string",
+ "array"
+ ],
+ "items": {
+ "title": "CA Cert File",
+ "description": "",
+ "type": "string"
+ },
+ "default": ""
+ },
+ "CATrustStoreFile": {
+ "title": "CA Trust Store File",
+ "description": "",
+ "type": "string",
+ "default": ""
+ },
+ "CATrustStorePass": {
+ "title": "CA Trust Store Pass",
+ "description": "",
+ "type": "string",
+ "default": ""
+ }
+ }
}
- }
+ },
+ "required": [ "enable" ]
},
"AJP": {
"title": "AJP",
@@ -314,18 +464,25 @@
"type": "object",
"properties": {
"enable": {
- "title": "Enable",
+ "title": "AJP Enable",
"description": "Enable AJP for this server",
"type": "boolean",
"default": false
},
"port": {
- "title": "Port",
+ "title": "AJP Port",
"description": "AJP port to use",
"type": "number",
"default": 8009
+ },
+ "secret": {
+ "title": "AJP Secret",
+ "description": "An AJP secret to ensure all requests coming into the AJP listener are from a trusted source",
+ "type": "string",
+ "default": ""
}
- }
+ },
+ "required": [ "enable" ]
},
"rewrites": {
"title": "Rewrites",
@@ -333,22 +490,21 @@
"type": "object",
"properties": {
"enable": {
- "title": "Enable",
+ "title": "Rewrites Enable",
"description": "Enable URL Rewrites on this server",
"type": "boolean",
"default": false
},
"logEnable": {
- "title": "Log Enable",
+ "title": "Rewrites Log Enable",
"description": "Enable Rewrite log file",
"type": "boolean",
"default": false
},
"config": {
- "title": "Config",
- "description": "Path to xml config file or .htaccess",
- "type": "string",
- "default": ""
+ "title": "Rewrites Config",
+ "description": "Path to XML config file or .htaccess",
+ "type": "string"
},
"statusPath": {
"title": "Tuckey Status Path",
@@ -361,21 +517,22 @@
"description": "Number of seconds to check rewrite config file for changes",
"type": "number"
}
- }
+ },
+ "required": [ "enable" ]
},
"basicAuth": {
- "title": "Configure basic authentication",
- "description": "",
+ "title": "Basic Authentication",
+ "description": "Configure basic authentication",
"type": "object",
"properties": {
"enable": {
- "title": "Enable",
+ "title": "Basic Auth Enable",
"description": "Enable basic auth for this server",
"type": "boolean",
"default": true
},
"users": {
- "title": "Users",
+ "title": "Basic Auth Users",
"description": "Users who can authenticate to basic auth",
"type": "object",
"additionalProperties": {
@@ -386,6 +543,157 @@
"default": {}
}
}
+ },
+ "blockCFAdmin": {
+ "title": "Block CF Admin",
+ "description": "",
+ "type": [
+ "boolean",
+ "string"
+ ],
+ "default": ""
+ },
+ "blockSensitivePaths": {
+ "title": "Block Sensitive Paths",
+ "description": "",
+ "type": "boolean"
+ },
+ "blockFlashRemoting": {
+ "title": "Block Flash Remoting",
+ "description": "",
+ "type": "boolean"
+ },
+ "rules": {
+ "title": "Web Rules",
+ "description": "Ad-hoc rules using Undertow predicates and handlers",
+ "type": "array",
+ "items": {
+ "title": "Web Rule",
+ "description": "Ad-hoc rule using Undertow predicates and handlers",
+ "type": "string"
+ },
+ "default": []
+ },
+ "rulesFile": {
+ "title": "Web Rules File",
+ "description": "A path or paths to files containing Undertow predicates and handlers",
+ "type": [
+ "string",
+ "array"
+ ],
+ "items": {
+ "title": "Web Rules File",
+ "description": "A path to file containing Undertow predicates and handlers",
+ "type": "string"
+ },
+ "default": []
+ },
+ "allowedExt": {
+ "title": "Web Allowed Ext",
+ "description": "A comma-delimited list of additional file extensions allowed by web server",
+ "type": "string",
+ "default": ""
+ },
+ "useProxyForwardedIP": {
+ "title": "Use Proxy Forwarded IP",
+ "description": "Whether the remote IP in your CF engine's cgi scope represents the upstream IP",
+ "type": "boolean",
+ "default": false
+ },
+ "security": {
+ "title": "Web Security",
+ "description": "Configure web security",
+ "type": "object",
+ "properties": {
+ "realm": {
+ "title": "Realm",
+ "description": "",
+ "type": "string",
+ "default": ""
+ },
+ "authPredicate": {
+ "title": "Auth Predicate",
+ "description": "",
+ "type": "string",
+ "default": ""
+ },
+ "basicAuth": {
+ "title": "Basic Authentication",
+ "description": "Configure basic authentication",
+ "type": "object",
+ "properties": {
+ "enable": {
+ "title": "Basic Auth Enable",
+ "description": "Enable basic auth for this server",
+ "type": "boolean"
+ },
+ "users": {
+ "title": "Basic Auth Users",
+ "description": "Users who can authenticate to basic auth",
+ "type": "object",
+ "additionalProperties": {
+ "title": "User",
+ "description": "The key is the user name and the value is the password.",
+ "type": "string"
+ }
+ }
+ }
+ },
+ "clientCert": {
+ "title": "Web Security Client Cert",
+ "description": "",
+ "type": "object",
+ "properties": {
+ "enable": {
+ "title": "Client Cert Enable",
+ "description": "",
+ "type": "boolean",
+ "default": false
+ },
+ "SSLRenegotiationEnable": {
+ "title": "Client Cert SSL Renegotiation Enable",
+ "description": "",
+ "type": "boolean",
+ "default": false
+ },
+ "trustUpstreamHeaders": {
+ "title": "Client Cert Trust Upstream Headers",
+ "description": "",
+ "type": "boolean",
+ "default": false
+ },
+ "subjectDNs": {
+ "title": "Client Cert Subject DNs",
+ "description": "",
+ "type": [
+ "string",
+ "array"
+ ],
+ "items": {
+ "title": "Client Cert Subject DN",
+ "description": "",
+ "type": "string"
+ },
+ "default": ""
+ },
+ "issuerDNs": {
+ "title": "Client Cert Issuer DNs",
+ "description": "",
+ "type": [
+ "string",
+ "array"
+ ],
+ "items": {
+ "title": "Client Cert Issuer DN",
+ "description": "",
+ "type": "string"
+ },
+ "default": ""
+ }
+ },
+ "required": [ "enable" ]
+ }
+ }
}
}
},
@@ -424,9 +732,21 @@
"type": "string",
"default": ""
},
+ "webXMLOverride": {
+ "title": "Web XML Override",
+ "description": "Path to web-override.xml file",
+ "type": "string",
+ "default": ""
+ },
+ "webXMLOverrideForce": {
+ "title": "Web XML Override Force",
+ "description": "Whether to override any configuration explicitly provided in the override file, as opposed to just adding or updating",
+ "type": "boolean",
+ "default": false
+ },
"WARPath": {
"title": "WAR Path",
- "description": "Path to a local WAR archive or exploded WAR folder. Mutually exclusive with cfengine.",
+ "description": "Path to a local WAR archive or exploded WAR folder. Mutually exclusive with cfengine.",
"type": "string",
"default": ""
},
@@ -438,7 +758,7 @@
},
"restMappings": {
"title": "REST Mappings",
- "description": "Comma delimited list of paths to map to the CF engine's REST servlet such as '/rest/*,/api/*'",
+ "description": "Comma-delimited list of paths to map to the CF engine's REST servlet such as '/rest/*,/api/*'",
"type": "string",
"default": ""
},
@@ -463,17 +783,131 @@
}
},
"runwar": {
- "title": "Configure RunWar",
- "description": "These settings apply to the underlying Runwar library that starts servers",
+ "title": "Configure RunWAR",
+ "description": "These settings apply to the underlying RunWAR library that starts servers",
"type": "object",
"properties": {
+ "jarPath": {
+ "title": "RunWAR JAR Path",
+ "description": "Path to RunWAR JAR",
+ "type": "string"
+ },
"args": {
- "title": "Arguments",
- "description": "Ad-hoc options for the underlying Runwar library",
- "type": "string",
+ "title": "RunWAR Arguments",
+ "description": "Ad-hoc options for the underlying RunWAR library",
+ "type": [
+ "string",
+ "array"
+ ],
+ "items": {
+ "title": "RunWAR Argument",
+ "description": "Ad-hoc option for the underlying RunWAR library",
+ "type": "string"
+ },
"default": ""
+ },
+ "XNIOOptions": {
+ "title": "XNIO Options",
+ "description": "Set of options that apply to the low level network transport functions it provides",
+ "type": "object",
+ "additionalProperties": {
+ "title": "XNIO Option",
+ "description": "Option that applies to the low level network transport functions it provides"
+ },
+ "default": {}
+ },
+ "undertowOptions": {
+ "title": "Undertow Options",
+ "description": "Settings that apply to the servlet and web server aspects of Undertow",
+ "type": "object",
+ "additionalProperties": {
+ "title": "Undertow Option",
+ "description": "Setting that applies to the servlet and web server aspects of Undertow"
+ },
+ "default": {}
}
}
+ },
+ "ModCFML": {
+ "title": "ModCFML",
+ "description": "Configuration around ModCFML standard",
+ "type": "object",
+ "properties": {
+ "enable": {
+ "title": "ModCFML Enable",
+ "description": "Whether to enable ModCFML",
+ "type": "boolean",
+ "default": false
+ },
+ "maxContexts": {
+ "title": "ModCFML Max Contexts",
+ "description": "Limits the number of contexts which can be created",
+ "type": "number",
+ "default": 200
+ },
+ "sharedKey": {
+ "title": "ModCFML Shared Key",
+ "description": "Key shared with the web server",
+ "type": "string",
+ "default": ""
+ },
+ "requireSharedKey": {
+ "title": "ModCFML Require Shared Key",
+ "description": "Whether to require the shared key header to be present",
+ "type": "boolean",
+ "default": true
+ },
+ "createVirtualDirectories": {
+ "title": "ModCFML Create Virtual Directories",
+ "description": "",
+ "type": "boolean",
+ "default": true
+ }
+ },
+ "required": [ "enable" ]
+ },
+ "scripts": {
+ "title": "Server Scripts",
+ "description": "",
+ "type": "object",
+ "properties": {
+ "preServerStart": {
+ "title": "Pre Server Start Script",
+ "description": "Runs before any configuration is resolved",
+ "type": "string"
+ },
+ "onServerStart": {
+ "title": "On Server Start Script",
+ "description": "Runs after configuration is resolved but before the actual server starts",
+ "type": "string"
+ },
+ "onServerInstall": {
+ "title": "On Server Install Script",
+ "description": "Runs when engine is being installed during server startup",
+ "type": "string"
+ },
+ "onServerStop": {
+ "title": "On Server Stop Script",
+ "description": "Runs before a server stop",
+ "type": "string"
+ },
+ "preServerForget": {
+ "title": "Pre Server Forget Script",
+ "description": "Runs before attempting to forget a server",
+ "type": "string"
+ },
+ "postServerForget": {
+ "title": "Post Server Forget Script",
+ "description": "Runs after a successful server forget",
+ "type": "string"
+ }
+ },
+ "additionalProperties": {
+ "title": "Server Script",
+ "description": "Ad-hoc server script",
+ "type": "string"
+ },
+ "default": {}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/cfml/system/endpoints/ForgeBox.cfc b/src/cfml/system/endpoints/ForgeBox.cfc
index acb6ed8d..8ddb3c10 100644
--- a/src/cfml/system/endpoints/ForgeBox.cfc
+++ b/src/cfml/system/endpoints/ForgeBox.cfc
@@ -276,7 +276,7 @@ component accessors="true" implements="IEndpointInteractive" {
// Check for no ext or .txt or .md in reverse precedence.
for( var ext in [ '', '.txt', '.md' ] ) {
// Case insensitive search for file name
- var files = directoryList( path=arguments.path, filter=function( path ){ return path contains ( item.file & ext); } );0
+ var files = directoryList( path=arguments.path, filter=function( path ){ return path contains ( item.file & ext); } );
if( arrayLen( files ) && fileExists( files[ 1 ] ) ) {
// If found, read in the first one found.
props[ item.variable ] = fileRead( files[ 1 ], 'UTF-8' );
@@ -445,7 +445,7 @@ component accessors="true" implements="IEndpointInteractive" {
* @package The full endpointID like foo@1.0.0
*/
public function parseSlug( required string package ) {
- var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
+ var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)(?!x\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
if ( arrayLen( matches.len ) < 2 ) {
throw(
type = "endpointException",
@@ -462,7 +462,7 @@ component accessors="true" implements="IEndpointInteractive" {
public function parseVersion( required string package ) {
var version = 'stable';
// foo@1.0.0
- var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
+ var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)(?!x\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
if ( matches.pos.len() >= 3 && matches.pos[ 3 ] != 0 ) {
// Note this can also be a semver range like 1.2.x, >2.0.0, or 1.0.4-2.x
// For now I'm assuming it's a specific version
diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc
index 9d5174be..5d75857f 100644
--- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc
+++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc
@@ -53,7 +53,13 @@ component {
arguments.path,
"all"
);
-
+ modelTestContent = replaceNoCase(
+ modelTestContent,
+ "|modelPath|",
+ arguments.path,
+ "all"
+ );
+
// Handle Methods
if ( len( arguments.methods ) ) {
var allTestsCases = "";
diff --git a/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc b/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc
index 42b96356..99724f48 100644
--- a/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc
+++ b/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc
@@ -1,5 +1,5 @@
/**
- * Resstart an embedded CFML server. Run command from the web root of the server or use
+ * Restart an embedded CFML server. Run command from the web root of the server or use
* the 'directory' and/or 'name' arguments.
* .
* {code:bash}
diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc
index 8a6059ad..042c457f 100644
--- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc
+++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc
@@ -231,23 +231,36 @@ component singleton accessors=true {
// Create this Task CFC
try {
+ var taskCaching = ConfigService.getSetting( 'taskCaching', false );
var mappingName = "task-" & relTaskFile;
- if( !ConfigService.getSetting( 'taskCaching', false ) && wirebox.getBinder().mappingExists( mappingName ) ) {
- // Clear it so metadata can be refreshed.
- wirebox.getBinder().unMap( mappingName );
- }
+ // If we're not caching tasks, single thread this whole block so we don't get mapping undefined errors
+ // if running the same task concurrently.
+ lock name='create-#mappingName#' type='#taskCaching ? 'readonly' : 'exclusive'#' timeout=20 {
- // Check if task mapped?
- if( !wirebox.getBinder().mappingExists( mappingName ) ){
- // feed this task to wirebox with virtual inheritance
- wirebox.registerNewInstance( name=mappingName, instancePath=relTaskFile )
- .setVirtualInheritance( "commandbox.system.BaseTask" );
- }
+ if( !taskCaching && wirebox.getBinder().mappingExists( mappingName ) ) {
+ // Clear it so metadata can be refreshed.
+ wirebox.getBinder().unMap( mappingName );
+ }
- // retrieve, build and wire from wirebox
- return wireBox.getInstance( mappingName );
+ // Check if task mapped?
+ if( !wirebox.getBinder().mappingExists( mappingName ) ){
+ // Double check lock to prevent two threads from both creating the mapping
+ // This lock will be effectivley moot if task caching is disabled since we'll already be in an
+ // exclusive lock, but will be neccessary if task caching is on
+ lock name='map-#mappingName#' type='exclusive' timeout=20 {
+ if( !wirebox.getBinder().mappingExists( mappingName ) ){
+ // feed this task to wirebox with virtual inheritance
+ wirebox.registerNewInstance( name=mappingName, instancePath=relTaskFile )
+ .setVirtualInheritance( "commandbox.system.BaseTask" );
+ }
+ }
+ }
+
+ // retrieve, build and wire from wirebox
+ return wireBox.getInstance( mappingName );
+ }
// This will catch nasty parse errors and tell us where they happened
} catch( any e ){
// Log the full exception with stack trace
diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc
index 986a56ae..090cfe1f 100644
--- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc
+++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc
@@ -154,6 +154,7 @@ component {
}
// User our Renderer to publish the nice results
+ var boxOptions = packageService.readPackageDescriptor( getCWD() ).testbox;
CLIRenderer.render(
print,
testData,
diff --git a/src/cfml/system/services/EndpointService.cfc b/src/cfml/system/services/EndpointService.cfc
index 1a9b2a50..2fd847cb 100644
--- a/src/cfml/system/services/EndpointService.cfc
+++ b/src/cfml/system/services/EndpointService.cfc
@@ -152,11 +152,23 @@ component accessors="true" singleton {
// Endpoint is specified as "endpoint:resource"
} else if( listLen( arguments.ID, ':' ) > 1 ) {
var endpointName = listFirst( arguments.ID, ':' );
+ var package = listRest( arguments.ID, ':' );
if( structKeyExists( getEndpointRegistry(), endpointName ) ) {
+ var theID = arguments.ID;
+ if( endpointName == 'file' || endpointName == 'folder' ) {
+ package = fileSystemUtil.resolvePath( package, arguments.currentWorkingDirectory );
+ if( endpointName == 'file' && !fileExists( package ) ) {
+ throw( "The file [ #package# ] does not exist.", 'endpointException' );
+ }
+ if( endpointName == 'folder' && !directoryExists( package ) ) {
+ throw( "The folder [ #package# ] does not exist.", 'endpointException' );
+ }
+ theID = endpointName & ':' & package;
+ }
return {
endpointName : endpointName,
- package : listRest( arguments.ID, ':' ),
- ID : arguments.ID
+ package : package,
+ ID : theID
};
} else {
if( listFindNoCase( 'C,D,E,F,G,H', endpointName ) ) {
diff --git a/src/cfml/system/services/JSONService.cfc b/src/cfml/system/services/JSONService.cfc
index b6fe8e3d..2e60fc78 100644
--- a/src/cfml/system/services/JSONService.cfc
+++ b/src/cfml/system/services/JSONService.cfc
@@ -168,7 +168,7 @@ component accessors="true" singleton {
var fullPropertyName = 'arguments.JSON' & toBracketNotation( arguments.property );
if( !isDefined( fullPropertyName ) ) {
-
+
throw( message='#arguments.property# does not exist.', type="JSONException");
}
// Get the array reference
@@ -187,7 +187,7 @@ component accessors="true" singleton {
last = last.right(-1).left(-1);
last = parser.unwrapQuotes( trim( last ) )
}
-
+
// path to containing struct
var everythingBut = propArray.slice( 1, propArray.len()-1 );
@@ -237,9 +237,9 @@ component accessors="true" singleton {
}
return fullPropertyName;
}
-
+
function tokenizeProp( required string str ) {
-
+
// Holds token
var tokens = [];
// Used to build up each token
@@ -277,14 +277,14 @@ component accessors="true" singleton {
if( inBrackets ) {
token &= char;
-
+
if( char == ']' ) {
inBrackets = false;
}
prevChar = char;
continue;
}
-
+
// period or break in brackets means break in token
if( ( char == '.' && !inBrackets ) || char == '[' ) {
@@ -292,7 +292,7 @@ component accessors="true" singleton {
if( ( char == '[' ) ) {
inBrackets = true;
}
-
+
if( len( token ) ) {
tokens.append( token );
token = '';
@@ -323,7 +323,7 @@ component accessors="true" singleton {
if( len( token ) ) {
tokens.append( token );
}
-
+
return tokens;
}
@@ -364,7 +364,15 @@ component accessors="true" singleton {
// Recursive function to crawl struct and create a string that represents each property.
function addProp( props, prop, safeProp, targetStruct ) {
- var propValue = ( len( prop ) ? evaluate( 'targetStruct#safeProp#' ) : targetStruct );
+ if( len( prop ) ) {
+ // Handle null key
+ if( !isDefined( 'targetStruct#safeProp#' ) ) {
+ return props;
+ }
+ var propValue = evaluate( 'targetStruct#safeProp#' )
+ } else {
+ var propValue = targetStruct;
+ }
if( isStruct( propValue ) ) {
// Add all of this struct's keys
diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc
index d333071e..d242bf47 100644
--- a/src/cfml/system/services/PackageService.cfc
+++ b/src/cfml/system/services/PackageService.cfc
@@ -590,8 +590,18 @@ component accessors="true" singleton {
var isSaving = ( arguments.save || arguments.saveDev );
var detail = dependencies[ dependency ];
+ var endpointName = 'forgebox';
+ try {
+ var endpointData = endpointService.resolveEndpointData( detail, installDirectory );
+ endpointName = endpointData.endpointName;
+ } catch ( EndpointNotFound e ) {
+ // Ignore
+ } catch( any e ) {
+ rethrow;
+ }
+
// full ID with endpoint and package like file:/opt/files/foo.zip
- if( detail contains ':' ) {
+ if( endpointName != 'forgebox' ) {
var ID = detail;
// Default ForgeBox endpoint of foo@1.0.0
} else {
@@ -1348,7 +1358,7 @@ component accessors="true" singleton {
* @package The full endpointID like foo@1.0.0
*/
private function parseSlug( required string package ) {
- var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
+ var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)(?!x\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
if ( arrayLen( matches.len ) < 2 ) {
throw(
type = "endpointException",
@@ -1365,7 +1375,7 @@ component accessors="true" singleton {
private function parseVersion( required string package ) {
var version = '';
// foo@1.0.0
- var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
+ var matches = REFindNoCase( "^([\w\-\.]+(?:\@(?!stable\b)(?!be\b)(?!x\b)[a-zA-Z][\w\-]*)?)(?:\@(.+))?$", package, 1, true );
if ( matches.pos.len() >= 3 && matches.pos[ 3 ] != 0 ) {
// Note this can also be a semver range like 1.2.x, >2.0.0, or 1.0.4-2.x
// For now I'm assuming it's a specific version
diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc
index a80f4327..09d556cd 100644
--- a/src/cfml/system/services/ServerEngineService.cfc
+++ b/src/cfml/system/services/ServerEngineService.cfc
@@ -144,11 +144,19 @@ component accessors="true" singleton="true" {
installDir : '',
initialInstall : false
};
+
+ // If CFEngine is a relateive file path, we need to know where to look for it.
+ var currentWorkingDirectory = serverInfo.webroot;
+ if( serverInfo.cfengineSource == 'serverJSON' ) {
+ currentWorkingDirectory = getDirectoryFromPath( serverInfo.serverConfigFile );
+ } else if( serverInfo.cfengineSource == 'serverProps' ) {
+ currentWorkingDirectory = shell.pwd();
+ }
var thisTempDir = tempDir & '/' & createUUID();
// Find out what endpoint will service them and ask the endpoint what their name is.
- var endpointData = endpointService.resolveEndpoint( ID, shell.pwd() );
+ var endpointData = endpointService.resolveEndpoint( ID, currentWorkingDirectory );
var endpoint = endpointData.endpoint;
var engineName = endpoint.getDefaultName( arguments.ID );
installDetails.engineName = engineName;
@@ -302,7 +310,12 @@ component accessors="true" singleton="true" {
return installDetails;
}
- if( !packageService.installPackage( ID=arguments.ID, directory=thisTempDir, save=false ) ) {
+ if( !packageService.installPackage(
+ ID=arguments.ID,
+ directory=thisTempDir,
+ save=false,
+ currentWorkingDirectory=currentWorkingDirectory )
+ ) {
throw( message='Server not installed.', type="commandException");
}
diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc
index 856bb182..217c46e6 100644
--- a/src/cfml/system/services/ServerService.cfc
+++ b/src/cfml/system/services/ServerService.cfc
@@ -173,7 +173,13 @@ component accessors="true" singleton {
'HSTS' : {
'enable' : d.web.ssl.hsts.enable ?: false,
'maxAge' : d.web.ssl.hsts.maxAge ?: 31536000,
- 'includeSubDomains' : d.web.ssl.hsts.includeSubDomains ?: false
+ 'includeSubDomains' : d.web.ssl.hsts.includeSubDomains ?: false
+ },
+ 'clientCert' : {
+ 'mode' : d.web.ssl.clientCert.mode ?: '',
+ 'CACertFiles' : d.web.ssl.clientCert.CACertFiles ?: '',
+ 'CATrustStoreFile' : d.web.ssl.clientCert.CATrustStoreFile ?: '',
+ 'CATrustStorePass' : d.web.ssl.clientCert.CATrustStorePass ?: ''
}
},
'AJP' : {
@@ -203,7 +209,22 @@ component accessors="true" singleton {
'blockSensitivePaths' : d.web.blockSensitivePaths ?: '',
'blockFlashRemoting' : d.web.blockFlashRemoting ?: '',
'allowedExt' : d.web.allowedExt ?: '',
- 'useProxyForwardedIP' : d.web.useProxyForwardedIP ?: false
+ 'useProxyForwardedIP' : d.web.useProxyForwardedIP ?: false,
+ 'security' : {
+ 'realm' : d.web.security.realm ?: '',
+ 'authPredicate' : d.web.security.authPredicate ?: '',
+ 'basicAuth' : {
+ 'enable' : d.web.security.basicAuth.enable ?: nullvalue(),
+ 'users' : d.web.security.basicAuth.users ?: nullvalue()
+ },
+ 'clientCert' : {
+ 'enable' : d.web.security.clientCert.enable ?: false,
+ 'SSLRenegotiationEnable' : d.web.security.clientCert.SSLRenegotiationEnable ?: false,
+ 'trustUpstreamHeaders' : d.web.security.clientCert.trustUpstreamHeaders ?: false,
+ 'subjectDNs' : d.web.security.clientCert.subjectDNs ?: '',
+ 'issuerDNs' : d.web.security.clientCert.issuerDNs ?: ''
+ }
+ }
},
'app' : {
'logDir' : d.app.logDir ?: '',
@@ -319,7 +340,7 @@ component accessors="true" singleton {
systemSettings.expandDeepSystemSettings( serverJSON );
systemSettings.expandDeepSystemSettings( defaults );
-
+
// Mix in environment variable overrides like BOX_SERVER_PROFILE
loadOverrides( serverJSON, serverInfo, serverProps.verbose ?: serverJSON.verbose ?: defaults.verbose ?: false );
@@ -757,6 +778,37 @@ component accessors="true" singleton {
if( len( defaults.web.SSL.keyFile ?: '' ) ) { defaults.web.SSL.keyFile = fileSystemUtil.resolvePath( defaults.web.SSL.keyFile, defaultwebroot ); }
serverInfo.SSLKeyFile = serverProps.SSLKeyFile ?: serverJSON.web.SSL.keyFile ?: defaults.web.SSL.keyFile;
+ // relative certFile in server.json is resolved relative to the server.json
+ if( isDefined( 'serverJSON.web.SSL.clientCert.CACertFiles' ) ) {
+ if( isSimpleValue( serverJSON.web.SSL.clientCert.CACertFiles ) ) {
+ serverJSON.web.SSL.clientCert.CACertFiles = listToArray( serverJSON.web.SSL.clientCert.CACertFiles );
+ }
+ serverJSON.web.SSL.clientCert.CACertFiles = serverJSON.web.SSL.clientCert.CACertFiles.map( (f)=>fileSystemUtil.resolvePath( f, defaultServerConfigFileDirectory ) );
+ }
+ // relative certFile in config setting server defaults is resolved relative to the web root
+ if( len( defaults.web.SSL.clientCert.CACertFiles ) ) {
+ if( isSimpleValue( defaults.web.SSL.clientCert.CACertFiles ) ) {
+ defaults.web.SSL.clientCert.CACertFiles = listToArray( defaults.web.SSL.clientCert.CACertFiles );
+ }
+ defaults.web.SSL.clientCert.CACertFiles = defaults.web.SSL.clientCert.CACertFiles.map( (f)=>fileSystemUtil.resolvePath( f, defaultwebroot ) );
+ } else {
+ defaults.web.SSL.clientCert.CACertFiles = [];
+ }
+ serverInfo.clientCertCACertFiles = serverJSON.web.SSL.clientCert.CACertFiles ?: defaults.web.SSL.clientCert.CACertFiles;
+
+ if( !isNull( serverJSON.web.SSL.clientCert.CATrustStoreFile ) ) {
+ serverJSON.web.SSL.clientCert.CATrustStoreFile = fileSystemUtil.resolvePath( serverJSON.web.SSL.clientCert.CATrustStoreFile, defaultServerConfigFileDirectory );
+ }
+ if( len( defaults.web.SSL.clientCert.CATrustStoreFile ) ) {
+ defaults.web.SSL.clientCert.CATrustStoreFile = fileSystemUtil.resolvePath( defaults.web.SSL.clientCert.CATrustStoreFile, defaultwebroot );
+ }
+ serverInfo.clientCertCATrustStoreFile = serverJSON.web.SSL.clientCert.CATrustStoreFile ?: defaults.web.SSL.clientCert.CATrustStoreFile;
+ serverInfo.clientCertCATrustStorePass = serverJSON.web.SSL.clientCert.CATrustStorePass ?: defaults.web.SSL.clientCert.CATrustStorePass;
+
+ serverInfo.clientCertMode = serverJSON.web.SSL.clientCert.mode ?: defaults.web.SSL.clientCert.mode;
+ serverInfo.clientCertSSLRenegotiationEnable = serverJSON.web.security.clientCert.SSLRenegotiationEnable ?: defaults.web.security.clientCert.SSLRenegotiationEnable;
+
+
serverInfo.SSLForceRedirect = serverJSON.web.SSL.forceSSLRedirect ?: defaults.web.SSL.forceSSLRedirect;
serverInfo.HSTSEnable = serverJSON.web.SSL.HSTS.enable ?: defaults.web.SSL.HSTS.enable;
serverInfo.HSTSMaxAge = serverJSON.web.SSL.HSTS.maxAge ?: defaults.web.SSL.HSTS.maxAge;
@@ -766,8 +818,59 @@ component accessors="true" singleton {
serverInfo.rewritesEnable = serverProps.rewritesEnable ?: serverJSON.web.rewrites.enable ?: defaults.web.rewrites.enable;
serverInfo.rewritesStatusPath = serverJSON.web.rewrites.statusPath ?: defaults.web.rewrites.statusPath;
serverInfo.rewritesConfigReloadSeconds = serverJSON.web.rewrites.configReloadSeconds ?: defaults.web.rewrites.configReloadSeconds;
- serverInfo.basicAuthEnable = serverJSON.web.basicAuth.enable ?: defaults.web.basicAuth.enable;
- serverInfo.basicAuthUsers = serverJSON.web.basicAuth.users ?: defaults.web.basicAuth.users;
+
+ serverInfo.basicAuthEnable = serverJSON.web.security.basicAuth.enable ?: defaults.web.security.basicAuth.enable ?: serverJSON.web.basicAuth.enable ?: defaults.web.basicAuth.enable;
+ serverInfo.basicAuthUsers = serverJSON.web.security.basicAuth.users ?: defaults.web.security.basicAuth.users ?: serverJSON.web.basicAuth.users ?: defaults.web.basicAuth.users;
+ // If there are no users, basic auth is NOT enabled
+ if( !serverInfo.basicAuthUsers.count() ) {
+ serverInfo.basicAuthEnable = false;
+ }
+
+ serverInfo.clientCertEnable = serverJSON.web.security.clientCert.enable ?: defaults.web.security.clientCert.enable;
+ serverInfo.clientCertTrustUpstreamHeaders = serverJSON.web.security.clientCert.trustUpstreamHeaders ?: defaults.web.security.clientCert.trustUpstreamHeaders;
+
+ // Default missing values
+ serverJSON.web.security.clientCert.subjectDNs = serverJSON.web.security.clientCert.subjectDNs ?: '';
+ serverJSON.web.security.clientCert.issuerDNs = serverJSON.web.security.clientCert.issuerDNs ?: '';
+
+ // Convert all strings to arrays
+ if( isSimpleValue( serverJSON.web.security.clientCert.subjectDNs ) ) {
+ if( len( serverJSON.web.security.clientCert.subjectDNs ) ) {
+ serverJSON.web.security.clientCert.subjectDNs = [ serverJSON.web.security.clientCert.subjectDNs ];
+ } else {
+ serverJSON.web.security.clientCert.subjectDNs = [];
+ }
+ }
+ if( isSimpleValue( defaults.web.security.clientCert.subjectDNs ) ) {
+ if( len( defaults.web.security.clientCert.subjectDNs ) ) {
+ defaults.web.security.clientCert.subjectDNs = [ defaults.web.security.clientCert.subjectDNs ];
+ } else {
+ defaults.web.security.clientCert.subjectDNs = [];
+ }
+ }
+ if( isSimpleValue( serverJSON.web.security.clientCert.issuerDNs ) ) {
+ if( len( serverJSON.web.security.clientCert.issuerDNs ) ) {
+ serverJSON.web.security.clientCert.issuerDNs = [ serverJSON.web.security.clientCert.issuerDNs ];
+ } else {
+ serverJSON.web.security.clientCert.issuerDNs = [];
+ }
+ }
+ if( isSimpleValue( defaults.web.security.clientCert.issuerDNs ) ) {
+ if( len( defaults.web.security.clientCert.issuerDNs ) ) {
+ defaults.web.security.clientCert.issuerDNs = [ defaults.web.security.clientCert.issuerDNs ];
+ } else {
+ defaults.web.security.clientCert.issuerDNs = [];
+ }
+ }
+
+ // Combine server defaults AND any settings in server.json
+ serverInfo.clientCertSubjectDNs = serverJSON.web.security.clientCert.subjectDNs.append( defaults.web.security.clientCert.subjectDNs, true );
+ serverInfo.clientCertIssuerDNs = serverJSON.web.security.clientCert.issuerDNs.append( defaults.web.security.clientCert.issuerDNs, true );
+
+ serverInfo.authEnabled = serverInfo.basicAuthEnable || serverInfo.clientCertEnable;
+ serverInfo.securityRealm = serverJSON.web.security.realm ?: defaults.web.security.realm;
+ serverInfo.authPredicate = serverJSON.web.security.authPredicate ?: defaults.web.security.authPredicate;
+
serverInfo.welcomeFiles = serverProps.welcomeFiles ?: serverJSON.web.welcomeFiles ?: defaults.web.welcomeFiles;
serverInfo.maxRequests = serverJSON.web.maxRequests ?: defaults.web.maxRequests;
@@ -1037,6 +1140,13 @@ component accessors="true" singleton {
}
serverInfo.cfengine = serverProps.cfengine ?: serverJSON.app.cfengine ?: defaults.app.cfengine;
+ serverInfo.cfengineSource = 'defaults';
+ if( !isNull( serverJSON.app.cfengine ) ) {
+ serverInfo.cfengineSource = 'serverJSON';
+ }
+ if( !isNull( serverProps.cfengine ) ) {
+ serverInfo.cfengineSource = 'serverProps';
+ }
serverInfo.restMappings = serverProps.restMappings ?: serverJSON.app.restMappings ?: defaults.app.restMappings;
// relative rewrite config path in server.json is resolved relative to the server.json
@@ -1559,14 +1669,30 @@ component accessors="true" singleton {
}
// Send SSL cert info if SSL is enabled and there's cert info
- if( serverInfo.SSLEnable && serverInfo.SSLCertFile.len() ) {
- args
- .append( '--ssl-cert' ).append( serverInfo.SSLCertFile )
- .append( '--ssl-key' ).append( serverInfo.SSLKeyFile );
- // Not all certs require a password
- if( serverInfo.SSLKeyPass.len() ) {
- args.append( '--ssl-keypass' ).append( serverInfo.SSLKeyPass );
+ if( serverInfo.SSLEnable ) {
+ if( serverInfo.SSLCertFile.len() ) {
+ args
+ .append( '--ssl-cert' ).append( serverInfo.SSLCertFile )
+ .append( '--ssl-key' ).append( serverInfo.SSLKeyFile );
+ // Not all certs require a password
+ if( serverInfo.SSLKeyPass.len() ) {
+ args.append( '--ssl-keypass' ).append( serverInfo.SSLKeyPass );
+ }
+ }
+ if( len( serverInfo.clientCertMode ) ){
+ args.append( '--client-cert-negotiation' ).append( serverInfo.clientCertMode );
+ }
+ if( serverInfo.clientCertSSLRenegotiationEnable ) {
+ args.append( '--client-cert-renegotiation' ).append( serverInfo.clientCertSSLRenegotiationEnable );
+ }
+ if( len( serverInfo.clientCertCATrustStoreFile ) ) {
+ args.append( '--ssl-add-ca-truststore' ).append( serverInfo.clientCertCATrustStoreFile );
+ args.append( '--ssl-add-ca-truststore-pass' ).append( serverInfo.clientCertCATrustStorePass );
}
+ if( serverInfo.clientCertCACertFiles.len() ){
+ args.append( '--ssl-add-ca-certs' ).append( serverInfo.clientCertCACertFiles.toList() );
+ }
+
}
// Incorporate rewrites to command
@@ -1579,18 +1705,40 @@ component accessors="true" singleton {
args.append( '--urlrewrite-check' ).append( serverInfo.rewritesConfigReloadSeconds );
}
- // Basic auth
- if( serverInfo.basicAuthEnable && serverInfo.basicAuthUsers.count() ) {
- // Escape commas and equals with backslash
- var sanitizeBA = function( i ) { return i.replace( ',', '\,', 'all' ).replace( '=', '\=', 'all' ); };
- var thisBasicAuthUsers = '';
- serverInfo.basicAuthUsers.each( function( i ) {
- thisBasicAuthUsers = thisBasicAuthUsers.listAppend( '#sanitizeBA( i )#=#sanitizeBA( serverInfo.basicAuthUsers[ i ] )#' );
- } );
- // user=pass,user2=pass2
- args.append( '--basicauth-users' ).append( thisBasicAuthUsers );
- }
+ if( serverInfo.authEnabled ) {
+ if( len( serverInfo.authPredicate ) ) {
+ args.append( '--auth-predicate' ).append( serverInfo.authPredicate );
+ }
+ if( !len( serverInfo.securityRealm ) ) {
+ serverInfo.securityRealm = serverInfo.name;
+ }
+ args.append( '--security-realm' ).append( serverInfo.securityRealm );
+
+ // Basic auth
+ if( serverInfo.basicAuthEnable ) {
+ // Escape commas and equals with backslash
+ var sanitizeBA = function( i ) { return i.replace( ',', '\,', 'all' ).replace( '=', '\=', 'all' ); };
+ var thisBasicAuthUsers = '';
+ serverInfo.basicAuthUsers.each( function( i ) {
+ thisBasicAuthUsers = thisBasicAuthUsers.listAppend( '#sanitizeBA( i )#=#sanitizeBA( serverInfo.basicAuthUsers[ i ] )#' );
+ } );
+ // user=pass,user2=pass2
+ args.append( '--basicauth-users' ).append( thisBasicAuthUsers );
+
+ }
+
+ // Client cert
+ if( serverInfo.clientCertEnable ) {
+ args
+ .append( '--client-cert-enable' ).append( serverInfo.clientCertEnable )
+ .append( '--client-cert-subjectdns' ).append( serializeJSON( serverInfo.clientCertSubjectDNs ) )
+ .append( '--client-cert-issuerdns' ).append( serializeJSON( serverInfo.clientCertIssuerDNs ) );
+ }
+ }
+
+ args.append( '--client-cert-trust-headers' ).append( serverInfo.clientCertTrustUpstreamHeaders )
+
if( serverInfo.rewritesEnable ){
if( !fileExists(serverInfo.rewritesConfig) ){
job.error( 'URL rewrite config not found [#serverInfo.rewritesConfig#]' );
@@ -1670,6 +1818,11 @@ component accessors="true" singleton {
job.complete( serverInfo.verbose );
return;
}
+
+
+ if( fileSystemUtil.isWindows() ) {
+ args = args.map( (a)=>replace( a, '"', '\"', 'all' ) );
+ }
processBuilder.init( args );
@@ -1847,9 +2000,9 @@ component accessors="true" singleton {
} else {
logger.error( '#e.message# #e.detail#' , e.stackTrace );
consoleLogger.error( '#e.message##chr(10)##e.detail#' );
- }
+ }
}
-
+
// Now it's time to shut-er down
variables.waitingOnConsoleStart = false;
shell.setPrompt();
@@ -2088,6 +2241,9 @@ component accessors="true" singleton {
// Get the web root out of the server.json, if specified and make it relative to the actual server.json file.
} else if( len( serverJSON.web.webroot ?: '' ) ) {
var defaultwebroot = fileSystemUtil.resolvePath( serverJSON.web.webroot, getDirectoryFromPath( defaultServerConfigFile ) );
+ // If we found a server.json by conventin and pull the web root from there, let's lock this in so we use it.
+ // Otherwise, a server.json pointing to another webroot will cause us to try and put the server.json in the external web root
+ serverProps.serverConfigFile = defaultServerConfigFile;
if( locVerbose ) { consoleLogger.debug("webroot pulled from server's JSON: #defaultwebroot#"); }
// Otherwise default to the directory the server's JSON file lives in (which defaults to the CWD)
} else {
@@ -2385,14 +2541,15 @@ component accessors="true" singleton {
**/
function isProcessAlive( required pidStr, throwOnError=false ) {
var result = "";
- var timeStart = millisecond(now());
try{
if (fileSystemUtil.isWindows() ) {
cfexecute(name='cmd', arguments='/c tasklist /FI "PID eq #pidStr#"', variable="result" timeout="10");
+ if (findNoCase("java", result) > 0 && findNoCase(pidStr, result) > 0) return true;
} else if (fileSystemUtil.isMac() || fileSystemUtil.isLinux() ) {
- cfexecute(name='ps', arguments='-p #pidStr#', variable="result" , timeout="10");
+ cfexecute(name='ps', arguments='-A -o pid,comm', variable="result" , timeout="10");
+ var matchedProcesses = reMatchNoCase("(?m)^\s*#pidStr#\s.*java",result);
+ if (matchedProcesses.len()) return true;
}
- if (findNoCase("java", result) > 0 && findNoCase(pidStr, result) > 0) return true;
} catch ( any e ){
if( throwOnError ) {
rethrow;
@@ -2405,12 +2562,26 @@ component accessors="true" singleton {
/**
* Logic to tell if a server is running
* @serverInfo.hint Struct of server information
+ * @quick When set to true, only the PID file is checked for on disk. When set to false, the OS is actually asked if the process is still running.
**/
- function isServerRunning( required struct serverInfo ){
+ function isServerRunning( required struct serverInfo, boolean quick=false ){
if(fileExists(serverInfo.pidFile)){
var serverPID = fileRead(serverInfo.pidFile);
- thread action="run" name="check_#serverPID##getTickCount()#" serverPID=serverPID pidFile=serverInfo.pidFile {
- if(!isProcessAlive(attributes.serverPID,true)) fileDelete(attributes.pidFile)
+ if( arguments.quick ) {
+ thread action="run" name="check_#serverPID##getTickCount()#" serverPID=serverPID pidFile=serverInfo.pidFile {
+ if(!isProcessAlive(attributes.serverPID,true)) {
+ fileDelete(attributes.pidFile);
+ }
+ }
+ } else {
+ if(!isProcessAlive(serverPID,true)) {
+ try {
+ fileDelete(serverInfo.pidFile);
+ } catch( any e ) {
+ // If the file didn't exist, ignore it.
+ }
+ return false;
+ }
}
return true;
}
@@ -2684,88 +2855,100 @@ component accessors="true" singleton {
'arguments' : "",
'command' : ""
},
- 'name' : "",
- 'logDir' : "",
- 'consolelogPath' : "",
- 'accessLogPath' : "",
- 'rewritesLogPath' : "",
- 'trayicon' : "",
- 'libDirs' : "",
- 'webConfigDir' : "",
- 'serverConfigDir' : "",
- 'serverHomeDirectory' : "",
- 'singleServerHome' : false,
- 'serverHome' : "",
- 'webroot' : "",
- 'webXML' : "",
- 'webXMLOverride' : "",
- 'webXMLOverrideActual' : "",
- 'webXMLOverrideForce' : false,
- 'HTTPEnable' : true,
- 'HTTP2Enable' : true,
- 'SSLEnable' : false,
- 'SSLPort' : 1443,
- 'AJPEnable' : false,
- 'AJPPort' : 8009,
- 'SSLCertFile' : "",
- 'SSLKeyFile' : "",
- 'SSLKeyPass' : "",
- 'rewritesEnable' : false,
- 'rewritesConfig' : "",
- 'rewritesStatusPath': "",
- 'rewritesConfigReloadSeconds' : "",
- 'basicAuthEnable' : true,
- 'basicAuthUsers' : {},
- 'heapSize' : '',
- 'minHeapSize' : '',
- 'javaHome' : '',
- 'javaVersion' : '',
- 'directoryBrowsing' : false,
- 'JVMargs' : "",
- 'JVMargsArray' : [],
- 'runwarArgs' : "",
- 'runwarArgsArray' : [],
- 'runwarXNIOOptions' : {},
+ 'name' : "",
+ 'logDir' : "",
+ 'consolelogPath' : "",
+ 'accessLogPath' : "",
+ 'rewritesLogPath' : "",
+ 'trayicon' : "",
+ 'libDirs' : "",
+ 'webConfigDir' : "",
+ 'serverConfigDir' : "",
+ 'serverHomeDirectory' : "",
+ 'singleServerHome' : false,
+ 'serverHome' : "",
+ 'webroot' : "",
+ 'webXML' : "",
+ 'webXMLOverride' : "",
+ 'webXMLOverrideActual' : "",
+ 'webXMLOverrideForce' : false,
+ 'HTTPEnable' : true,
+ 'HTTP2Enable' : true,
+ 'SSLEnable' : false,
+ 'SSLPort' : 1443,
+ 'AJPEnable' : false,
+ 'AJPPort' : 8009,
+ 'SSLCertFile' : "",
+ 'SSLKeyFile' : "",
+ 'SSLKeyPass' : "",
+ 'clientCertCACertFiles' : [],
+ 'clientCertMode' : '',
+ 'clientCertSSLRenegotiationEnable': false,
+ 'clientCertEnable' : false,
+ 'clientCertTrustUpstreamHeaders': false,
+ 'clientCertSubjectDNs' : [],
+ 'clientCertIssuerDNs' : [],
+ 'securityRealm' : '',
+ 'clientCertCATrustStoreFile': '',
+ 'clientCertCATrustStorePass': '',
+ 'rewritesEnable' : false,
+ 'rewritesConfig' : "",
+ 'rewritesStatusPath' : "",
+ 'rewritesConfigReloadSeconds': "",
+ 'basicAuthEnable' : true,
+ 'authPredicate' : '',
+ 'basicAuthUsers' : {},
+ 'heapSize' : '',
+ 'minHeapSize' : '',
+ 'javaHome' : '',
+ 'javaVersion' : '',
+ 'directoryBrowsing' : false,
+ 'JVMargs' : "",
+ 'JVMargsArray' : [],
+ 'runwarArgs' : "",
+ 'runwarArgsArray' : [],
+ 'runwarXNIOOptions' : {},
'runwarUndertowOptions' : {},
- 'cfengine' : "",
- 'restMappings' : "",
+ 'cfengine' : "",
+ 'cfengineSource' : 'defaults',
+ 'restMappings' : "",
'sessionCookieSecure' : false,
'sessionCookieHTTPOnly' : false,
- 'engineName' : "",
- 'engineVersion' : "",
- 'WARPath' : "",
- 'serverConfigFile' : "",
- 'aliases' : {},
- 'errorPages' : {},
- 'accessLogEnable' : false,
- 'GZipEnable' : true,
- 'GZipPredicate' : '',
- 'rewritesLogEnable' : false,
- 'trayOptions' : {},
- 'trayEnable' : true,
- 'dockEnable' : true,
- 'dateLastStarted' : '',
- 'openBrowser' : true,
- 'openBrowserURL' : '',
- 'profile' : '',
- 'customServerFolder': '',
- 'welcomeFiles' : '',
- 'maxRequests' : '',
- 'exitCode' : 0,
- 'rules' : [],
- 'rulesFile' : '',
- 'blockCFAdmin' : false,
+ 'engineName' : "",
+ 'engineVersion' : "",
+ 'WARPath' : "",
+ 'serverConfigFile' : "",
+ 'aliases' : {},
+ 'errorPages' : {},
+ 'accessLogEnable' : false,
+ 'GZipEnable' : true,
+ 'GZipPredicate' : '',
+ 'rewritesLogEnable' : false,
+ 'trayOptions' : {},
+ 'trayEnable' : true,
+ 'dockEnable' : true,
+ 'dateLastStarted' : '',
+ 'openBrowser' : true,
+ 'openBrowserURL' : '',
+ 'profile' : '',
+ 'customServerFolder' : '',
+ 'welcomeFiles' : '',
+ 'maxRequests' : '',
+ 'exitCode' : 0,
+ 'rules' : [],
+ 'rulesFile' : '',
+ 'blockCFAdmin' : false,
'blockSensitivePaths' : false,
'blockFlashRemoting' : false,
- 'allowedExt' : '',
- 'pidfile' : '',
- 'predicateFile' : '',
- 'trayOptionsFile' : '',
- 'SSLForceRedirect' : false,
- 'HSTSEnable' : false,
- 'HSTSMaxAge' : 0,
+ 'allowedExt' : '',
+ 'pidfile' : '',
+ 'predicateFile' : '',
+ 'trayOptionsFile' : '',
+ 'SSLForceRedirect' : false,
+ 'HSTSEnable' : false,
+ 'HSTSMaxAge' : 0,
'HSTSIncludeSubDomains' : false,
- 'AJPSecret' : ''
+ 'AJPSecret' : ''
};
}
diff --git a/src/cfml/system/util/ConsolePainter.cfc b/src/cfml/system/util/ConsolePainter.cfc
index 3c456b24..50d91c60 100644
--- a/src/cfml/system/util/ConsolePainter.cfc
+++ b/src/cfml/system/util/ConsolePainter.cfc
@@ -138,7 +138,7 @@ component singleton accessors=true {
);
} catch( any e ) {
- if( !(e.type contains 'interrupted') ) {
+ if( !(e.type contains 'interrupt') ) {
systemoutput( e.message & ' ' & e.detail, 1 );
systemoutput( "#e.tagContext[1].template#: line #e.tagContext[1].line#", 1 );
rethrow;
diff --git a/src/cfml/system/util/DataConverter.cfc b/src/cfml/system/util/DataConverter.cfc
index 12b1871d..903d1f7c 100644
--- a/src/cfml/system/util/DataConverter.cfc
+++ b/src/cfml/system/util/DataConverter.cfc
@@ -36,11 +36,18 @@ component singleton {
var data = isArray(rawData) ? rawData : [rawData];
return data.map((x) => {
+ if( isNull( x ) ) {
+ return [nullValue()];
+ }
+
if(isArray(x)) return x.map((y) => {
return isSimpleValue(y) ? y : cellHasFormattingEmbedded(y) ? y : serializeJSON(y)}
);
if(isStruct(x)) return x.map((k,v) => {
+ if( isNull( v ) ) {
+ return;
+ }
return isSimpleValue(v) ? v : cellHasFormattingEmbedded(v) ? v : serializeJSON(v)
});
@@ -55,13 +62,18 @@ component singleton {
* Use key names for structs
* @data Any type of data for the table.
*/
- public array function generateColumnNames (required any data, string columns="" ){
+ public array function generateColumnNames(required any data, string columns="" ){
var columnsArray = [];
if(isSimpleValue(data)){
columnsArray = ['col_1'];
} else if ( isArray(data) ){
- columnsArray = data.map((x,i) => {return 'col_' & i},true);
- arguments.columns.each(function(element,index,list) {
+ var i=0;
+ for( var x in data ) {
+ i++;
+ columnsArray.append( 'col_' & i );
+ }
+
+ arguments.columns.listEach(function(element,index,list) {
columnsArray[index] = element;
})
} else if ( isStruct(data) ){
diff --git a/src/cfml/system/util/DiskStore.cfc b/src/cfml/system/util/DiskStore.cfc
index f5d15a9a..a0285aa3 100644
--- a/src/cfml/system/util/DiskStore.cfc
+++ b/src/cfml/system/util/DiskStore.cfc
@@ -144,7 +144,12 @@ Description :
if( isJSON( fileContents ) ) {
return deserializeJSON( fileContents );
} else {
- fileDelete( thisFilePath );
+ try {
+ fileDelete( thisFilePath );
+ } catch( any e ) {
+ // If the file didn't exist, ignore it. This can happen
+ // when to CommandBox instances start at the same time.
+ }
}
}
diff --git a/src/cfml/system/util/InteractiveJob.cfc b/src/cfml/system/util/InteractiveJob.cfc
index 573c165c..64ae9650 100644
--- a/src/cfml/system/util/InteractiveJob.cfc
+++ b/src/cfml/system/util/InteractiveJob.cfc
@@ -20,7 +20,7 @@ component accessors=true singleton {
property name='dumpLog' type='boolean';
property name='startTime' type='numeric';
property name='animation' type='numeric';
-
+
// DI
property name='shell' inject='shell';
@@ -47,7 +47,7 @@ component accessors=true singleton {
[ ' ◐ ', ' ◓ ', ' ◑ ', ' ◒ ' ],
[ '> ', ' > ', ' >' ]
];
-
+
setStartTime( 0 );
setAnimation( 1 )
return this;
@@ -98,13 +98,15 @@ component accessors=true singleton {
// Break lines longer than the current terminal width into multiples
.reduce( function( result, i ) {
// Keep breaking off chunks until we're short enough to fit
- while( i.len() > termWidth ) {
- result.append( i.left( termWidth ) );
- i = i.right( -termWidth );
+ // We need to ignore ANSI formatting when rdoing this or it will throw off the widths
+ while( aStr.stripAnsi( i ).length() > termWidth ) {
+ var attributedString = aStr.fromAnsi(i);
+ result.append( attributedString.subSequence( 0, termWidth-1 ).toString() );
+ i = attributedString.subSequence( termWidth-1, attributedString.length()-1 );
}
// Add any remaining.
- if( i.len() ) {
- result.append( i );
+ if( i.length() ) {
+ result.append( i.toString() );
}
return result;
}, [] )
@@ -260,11 +262,11 @@ component accessors=true singleton {
* @finalOutput True if getting final output at the completion of the job.
*/
array function getLines( job, includeAllLogs=false, finalOutput=false ) {
-
+
if( !getActive() ) {
return [];
}
-
+
if( isNull( arguments.job ) ) {
if( !getJobs().len() ) {
throw( 'No active job' );
@@ -285,12 +287,12 @@ component accessors=true singleton {
}
if( job.status == 'Running' || includeAllLogs || ( finalOutput && job.dumpLog ) ) {
-
+
// If we're only showing one line of job logs, don't bother with the ------ dividers
if( job.logSize > 1 ) {
lines.append( aStr.fromAnsi( print.text( ' |' & repeatString( '-', min( job.name.len()+15, safeWidth-5 ) ), statusColor( job ) ) ) );
}
-
+
var relevantLogLines = [];
var thisLogLines = job.logLines;
var thisLogSize = job.logSize;
@@ -353,7 +355,7 @@ component accessors=true singleton {
return print.text( '#runningAnimation()#| ' & job.name, statusColor( job ) );
}
}
-
+
/**
* Returns a character to aninmate for running jobs
*
@@ -363,7 +365,7 @@ component accessors=true singleton {
// How long has this job been running in ms?
var runningTime = ( getTickCount() ) - getStartTime();
// Removing the amount of time it takes to completely cycle through the chars an even amount of time, how much is left in the current cycle?
- runningTime = runningTime % ( thisRunningAnimationChars.len() * 500 );
+ runningTime = runningTime % ( thisRunningAnimationChars.len() * 500 );
// Which char are we on at 500ms per char?
return thisRunningAnimationChars[ ( runningTime \ 500 ) + 1 ]
}
@@ -434,7 +436,7 @@ component accessors=true singleton {
/**
* Get number that represents the depth of the currently executing job.
*/
- private numeric function getCurrentJobDepth() {
+ numeric function getCurrentJobDepth() {
var pointer = getJobs();
var depth = 0;
if( !pointer.len() ) {
diff --git a/src/cfml/system/util/Print.cfc b/src/cfml/system/util/Print.cfc
index 03f8f2c7..74707932 100644
--- a/src/cfml/system/util/Print.cfc
+++ b/src/cfml/system/util/Print.cfc
@@ -88,11 +88,13 @@ component {
// TODO: Actually use a string buffer
var ANSIString = "";
+
+ var foundANSI = false;
// Text needing formatting
var text = arrayLen(missingMethodArguments) ? missingMethodArguments[ 1 ] : '';
// Convert complex values to a string representation
- if( isXML( text ) ) {
+ if( ( !isSimpleValue( text ) || ( left(text,1) == '<' || trim( text ).left(1) == '<' ) ) && isXML( text ) ) {
text = formatterUtil.formatXML( text );
} else if( !isSimpleValue( text ) ) {
diff --git a/src/cfml/system/util/PrintBuffer.cfc b/src/cfml/system/util/PrintBuffer.cfc
index 399dbee0..484974b9 100644
--- a/src/cfml/system/util/PrintBuffer.cfc
+++ b/src/cfml/system/util/PrintBuffer.cfc
@@ -57,9 +57,11 @@ component accessors="true" extends="Print"{
// Proxy through any methods to the actual print helper
function onMissingMethod( missingMethodName, missingMethodArguments ){
- // Don't modify the buffer if it's being printed
- lock name='printBuffer-#getObjectID()#' type="readonly" timeout="20" {
- variables.result.append( super.onMissingMethod( arguments.missingMethodName, arguments.missingMethodArguments ) );
+ var result = super.onMissingMethod( arguments.missingMethodName, arguments.missingMethodArguments );
+
+ // Don't modify the buffer if it's being printed, exclusive because StringBuilder is not thread-safe
+ lock name='printBuffer-#getObjectID()#' type="exclusive" timeout="20" {
+ variables.result.append( result );
return this;
}
}
diff --git a/src/cfml/system/util/ReaderFactory.cfc b/src/cfml/system/util/ReaderFactory.cfc
index 795883d0..66766042 100644
--- a/src/cfml/system/util/ReaderFactory.cfc
+++ b/src/cfml/system/util/ReaderFactory.cfc
@@ -60,6 +60,8 @@ component singleton{
}
// The JANSI lib will pick this up and use it
systemSettings.setSystemProperty( 'library.jansi.path', JANSI_path );
+ // https://github.com/fusesource/jansi/blob/2cf446182c823a4c110411b765a1f0367eb8a913/src/main/java/org/fusesource/jansi/internal/JansiLoader.java#L80
+ systemSettings.setSystemProperty( 'jansi.tmpdir', JANSI_path );
// And JNA will pick this up.
// https://java-native-access.github.io/jna/4.2.1/com/sun/jna/Native.html#getTempDir--
systemSettings.setSystemProperty( 'jna.tmpdir', JANSI_path );
diff --git a/src/cfml/system/util/SystemSettings.cfc b/src/cfml/system/util/SystemSettings.cfc
index 78fecf06..6445b4ca 100644
--- a/src/cfml/system/util/SystemSettings.cfc
+++ b/src/cfml/system/util/SystemSettings.cfc
@@ -213,7 +213,11 @@ component singleton {
// Loop over and process each key
for( var key in dataStructure ) {
var expandedKey = expandSystemSettings( key, context );
- dataStructure[ expandedKey ] = expandDeepSystemSettings( dataStructure[ key ], context );
+ if( isNull( dataStructure[ key ] ) ) {
+ dataStructure[ expandedKey ] = nullValue();
+ } else {
+ dataStructure[ expandedKey ] = expandDeepSystemSettings( dataStructure[ key ], context );
+ }
if( expandedKey != key ) dataStructure.delete( key );
}
return dataStructure;
@@ -223,7 +227,9 @@ component singleton {
// Loop over and process each index
for( var item in dataStructure ) {
i++;
- dataStructure[ i ] = expandDeepSystemSettings( item, context );
+ if( !isNull( item ) ) {
+ dataStructure[ i ] = expandDeepSystemSettings( item, context );
+ }
}
return dataStructure;
// If it's a string...
diff --git a/src/cfml/system/util/TablePrinter.cfc b/src/cfml/system/util/TablePrinter.cfc
index a7edf8ca..f47f75a2 100644
--- a/src/cfml/system/util/TablePrinter.cfc
+++ b/src/cfml/system/util/TablePrinter.cfc
@@ -15,6 +15,7 @@ component {
property name="print" inject="PrintBuffer";
property name="shell" inject="shell";
property name="convert" inject="DataConverter";
+ property name="job" inject="InteractiveJob";
variables.tableChars = {
"top": chr( 9552 ), // ═
@@ -119,7 +120,12 @@ component {
var headerData = arguments.headers.map( ( header, index ) => calculateColumnData( index, header, data, headerNames ), true );
var termWidth = arguments.width;
if( termWidth <= 0 ) {
- termWidth = shell.getTermWidth()-1;
+ // If this table is going to get captured in job output, ensure it will fix based on the job depth
+ if( job.getActive() ) {
+ termWidth = shell.getTermWidth()-2-( job.getCurrentJobDepth() * 4 );
+ } else {
+ termWidth = shell.getTermWidth()-1;
+ }
}
if( termWidth <= 0 ) {
termWidth = 100;
diff --git a/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc b/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc
index 1f734c68..57cf839d 100644
--- a/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc
+++ b/src/cfml/system/wirebox/system/cache/providers/CacheBoxProvider.cfc
@@ -538,7 +538,7 @@ component
lock type="exclusive" name="CacheBoxProvider.reap.#variables.cacheId#" timeout="#variables.lockTimeout#"{
// log it
- variables.logger.info( "Starting to reap CacheBoxProvider: #getName()#, id: #variables.cacheId#" );
+ variables.logger.debug( "Starting to reap CacheBoxProvider: #getName()#, id: #variables.cacheId#" );
// Run Storage reaping first, before our local algorithm
variables.objectStore.reap();
@@ -601,7 +601,7 @@ component
}
// log it
- variables.logger.info( "Finished reap in #getTickCount()-sTime#ms for CacheBoxProvider: #getName()#, id: #variables.cacheId#" );
+ variables.logger.debug( "Finished reap in #getTickCount()-sTime#ms for CacheBoxProvider: #getName()#, id: #variables.cacheId#" );
return this;
}
diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java
index a530d1d5..a1d7336c 100644
--- a/src/java/cliloader/LoaderCLIMain.java
+++ b/src/java/cliloader/LoaderCLIMain.java
@@ -292,7 +292,7 @@ && new File( cliArguments.get( 0 ) ).isFile() ) {
// Escape backslash in webroot since replace uses a regular expression
// The bootstrap is the first .cfm file we will cfinclude from the "webroot"
String bootstrap = "/" + Paths.get( uri ).toAbsolutePath().toString().replaceFirst( webroot.replace( "\\", "\\\\" ), "" );
-
+
// contextroot sets lucee's "webroot" inside the scripting engine to be our drive root
System.setProperty( "lucee.cli.contextRoot", webroot );
// These next two are the Lucee web context and server context homes
@@ -526,7 +526,7 @@ public static boolean listContains( ArrayList< String > argList, String text ){
public static int listIndexOf( ArrayList< String > argList, String text ){
int index = 0;
for( String item : argList) {
- if( item.toLowerCase().startsWith( text.toLowerCase() )
+ if( item.toLowerCase().startsWith( text.toLowerCase() )
|| item.toLowerCase().startsWith( "-" + text.toLowerCase() ) ) {
return index;
}