FarCry in a Shared Hosting environment

My hosting company urges me to upgrade my web site running FarCry 7.2.9 on CF11 to CF2016 or, preferred, CF2018.

However, as it seems, FarCry needs to be granted the rights to load and execute the Java classes in the core (first of all being the JavaLoader class. Not being a Java developer at all, I understand that this jar file serves as the foundation to dynamically load any other jar files (for example the UUID creation algorithm) without the need to change/edit/alter the Java path settings on the ColdFusion server.

As it seems, the provider considers enabling this setting (BTW, which one ist it exactly?) to be a security issue.

Now, the main question is:

  1. What are my options? Is it possible to run FarCry without those Java classes being enabled (even if I would have to do without some functionality)? For example, I guess I could live without the Internet Address locator class.

  2. Which parts would I need to re-develop? Which Java classes are currently used?

  3. Where to find those? Is there any documentation available?

I am well aware of the fact that going this route could require quite some time to re-develop those features. However, with everybody obviously being scared because of possible effects on sever security, using FarCry in a Shared Hosting environment doesnā€™t seem to have a bright future. (Which I would pity because I very much like the CMS.)

Since I am already negotiating the topic with the hosting company, any pointers, suggestions, remarks, support, etc., are highly appreciated.

Thanks,
Thomas

1 Like

We in the process of dealing with Java 11 and JavaLoader related issues (JavaLoader no longer works because CORBA deprecated in Java 11).

In the p740 branch on GitHub JavaLoader has been removed and has been replaced with calls to creating normal Java objects, and the java.net.InetAddress hostname lookups in the Application.cfc have also been removed.

Hereā€™s the list of tickets completed so far;
https://farcry.jira.com/projects/FC/versions/15004/tab/release-report-all-issues

Can you give the p740 branch a try and see if it solves the problems you currently have, and if not perhaps provide more details (with stack traces if possible) so that I can see if weā€™re able to address them?

1 Like

I should also mention, p740 is currently considered BETA as we might need to make further changes in p740 that could involve the schema, so weā€™ll need to do that before we officially release 7.4.0. It could be several weeks away before itā€™s ready for release, but any schema changes will be mentioned in the release notes.

1 Like

Justin,

Thank you for the quick reply.
Iā€™ll check out the p740 branch on the weekend and give it a try. Letā€™s see how far this will get me. Anyway, it is great to learn that I might not need to do all the work by myself. (And of course I will follow up with any findings.)

1 Like

I finally got the time to download the current p740 branch (as of yesterday) and replace the core directory on my system after updating to ColdFusion 2018, Update 4. (The local system currently runs macOS 1014.5 and CF 2018 Update 4 on top of the built-in Apache web server 2.4.34/Feb 22 2019.) I didnā€™t run any scripts, just restarted the CF server.

Unfortunately, any request to the site root (/index.cfm) or the Webtop breaks down with an error message I cannot even trace back to any FarCry-related code:

PATH is already defined in argument scope.
Use local to define a local variable with same name.

Resources:
Check the ColdFusion documentation to verify that you are using the correct syntax.
Search the Knowledge Base to find a solution to your problem.
Browser  	Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15
Remote Address  	::1
Referrer  	
Date/Time  	11-Aug-19 11:12 AM
Stack Trace


coldfusion.compiler.ASTvariableDefinition$VarAlreadyDefinedException: PATH is already defined in argument scope.
	at coldfusion.compiler.ASTvariableDefinition.register(ASTvariableDefinition.java:110)
	at coldfusion.compiler.SemanticAnalyzer.transform(SemanticAnalyzer.java:393)
	at coldfusion.compiler.Treewalker.postorder(Treewalker.java:124)
	at coldfusion.compiler.Treewalker.postorder(Treewalker.java:27)
	at coldfusion.compiler.Treewalker.postorder(Treewalker.java:27)
	at coldfusion.compiler.Treewalker.postorder(Treewalker.java:27)
	at coldfusion.compiler.Treewalker.postorder(Treewalker.java:27)
	at coldfusion.compiler.NeoTranslator.parseAndTransform(NeoTranslator.java:472)
	at coldfusion.compiler.NeoTranslator.translateJava(NeoTranslator.java:407)
	at coldfusion.compiler.NeoTranslator.translateJava(NeoTranslator.java:160)
	at coldfusion.runtime.TemplateClassLoader$TemplateCache$1.fetch(TemplateClassLoader.java:478)
	at coldfusion.util.LruCache.get(LruCache.java:180)
	at coldfusion.runtime.TemplateClassLoader$TemplateCache.fetchSerial(TemplateClassLoader.java:404)
	at coldfusion.util.AbstractCache.fetch(AbstractCache.java:58)
	at coldfusion.util.SoftCache.get_statsOff(SoftCache.java:142)
	at coldfusion.util.SoftCache.get(SoftCache.java:81)
	at coldfusion.runtime.TemplateClassLoader.findClass(TemplateClassLoader.java:672)
	at coldfusion.runtime.TemplateClassLoader.newInstance(TemplateClassLoader.java:609)
	at coldfusion.runtime.TemplateClassLoader.newInstance(TemplateClassLoader.java:592)
	at coldfusion.runtime.TemplateProxyFactory.getCFCInstance(TemplateProxyFactory.java:289)
	at coldfusion.runtime.TemplateProxyFactory.resolveName(TemplateProxyFactory.java:182)
	at coldfusion.runtime.TemplateProxyFactory.resolveComponentHelper(TemplateProxyFactory.java:382)
	at coldfusion.runtime.TemplateProxyFactory.resolveName(TemplateProxyFactory.java:242)
	at coldfusion.runtime.TemplateProxyFactory.resolveComponentHelper(TemplateProxyFactory.java:382)
	at coldfusion.runtime.TemplateProxyFactory.resolveName(TemplateProxyFactory.java:242)
	at coldfusion.runtime.TemplateProxyFactory.resolveName(TemplateProxyFactory.java:162)
	at coldfusion.runtime.TemplateProxyFactory.resolveFile(TemplateProxyFactory.java:123)
	at coldfusion.cfc.CFCProxy.<init>(CFCProxy.java:140)
	at coldfusion.runtime.AppEventInvoker.<init>(AppEventInvoker.java:71)
	at coldfusion.runtime.AppEventInvoker.<init>(AppEventInvoker.java:66)
	at coldfusion.filter.PathFilter.resolveApplicationScope(PathFilter.java:213)
	at coldfusion.filter.PathFilter.invoke(PathFilter.java:143)
	at coldfusion.filter.IpFilter.invoke(IpFilter.java:45)
	at coldfusion.filter.LicenseFilter.invoke(LicenseFilter.java:30)
	at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:96)
	at coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28)
	at coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38)
	at coldfusion.filter.NoCacheFilter.invoke(NoCacheFilter.java:60)
	at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38)
	at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22)
	at coldfusion.filter.CachingFilter.invoke(CachingFilter.java:62)
	at coldfusion.CfmServlet.service(CfmServlet.java:226)
	at coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:311)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at coldfusion.monitor.event.MonitoringServletFilter.doFilter(MonitoringServletFilter.java:46)
	at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:47)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at coldfusion.inspect.weinre.MobileDeviceDomInspectionFilter.doFilter(MobileDeviceDomInspectionFilter.java:121)
	at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:47)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:491)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
	at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:422)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:764)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1388)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:834)

However, calling a simple CFM script which outputs the formatted current date/time works fine. Did I miss something?

A short update: The error is caused by a bug in the addJARPath() method in the webtop/Application.cfc file. The variable path was used for both the argument and the local scope (set with ā€œvarā€). CF 2018 on macOS apparently doesnā€™t like this. I rewrote the function as follows which does the trick:

<cffunction name="addJARPath" access="package" output="false"
	hint="This function takes the expanded FarCry path and extends it with the collected JAR paths.">
	<cfargument name="paths" required="true" hint="hopefully an array containing relative JAR paths">

	<!--- convert the provided (relative) path(s) to an array, if necessary --->
	<cfif NOT isArray(arguments.paths)>
		<cfset arguments.paths = [ arguments.paths ] />
	</cfif>

	<!--- append new paths to the javaSettings array --->
	<cfloop from="1" to="#arrayLen(arguments.paths)#" index="i">
		<cfset var path = application.farcryPathExpanded & "/" & arguments.paths[i] />
		<cfif NOT arrayFindNoCase(this.javaSettings.loadPaths, path)>
			<cfset arrayAppend(this.javaSettings.loadPaths, path) />
		</cfif>
	</cfloop>
</cffunction>

Now the application crashes because application.url.webtop is not defined when called in the OnError() methodā€¦

Thanks for digging into it further :slight_smile: Iā€™ve pushed a couple of fixes for both the path variable name issue and the application.url.webtop missing in the error handlers;

https://farcry.jira.com/browse/FC-3202
https://farcry.jira.com/browse/FC-3203

With any luck the error handler fix will help surface whatever the next issue is that youā€™re facing and/or it might be worth checking your exception.log to see if there is further details including a stack trace.

Thanks for the Jira links and the fixes. I will check them out later today or tomorrow to see how far they will get me. Unfortunately, the exception.log didnā€˜t provide more information than the stack trace. Both didnā€˜t contain any further information on where the error occurs.

With respect to the core/Application.cfc, Iā€˜m curious if we shouldnā€˜t move the initial settings into a init() constructor method and rearrange the methods (public first, package and private ones later). Also, I think that ā€” due to legacy code, I guess ā€” the different scopes use more properties than they actually require. What about adding the settings currently found in the /farcryconstructor.cfm file directly to the appropriate places in the application scope?

Iā€˜ll keep you posted.

Ok, here goes the update: After taking over the latest p470 branch, the application breaks down with the message ā€œThe directory or file /Users/Thomas/Sites/farcry/plugins/farcrycms/jars specified in LOADPATHS does not exist.ā€

Since everything is masked, not even the exception.log file provides more detailed information. But: The statement itself is correct. There is no jars directory in the plugins/farcrycms directory. And the same obviously will hold true for /farcry/projects/<projectname>/jars directory.
The question is: Where are these settings used? Where will the jar files be loaded? I searched for ā€œjavaSettings,ā€ ā€œJAR,ā€ and ā€œloadPaths,ā€ but to no avail.
I really wish one could configure the FarCry application to properly show where it failsā€¦

Also, the setting application.farcryUseJARPath only seems to be created in the OnApplicationStart() method but never to be used in the entire FarCry core. Can it be that the p740 branch is incomplete? I would have expected that all the jar classes would be loaded somewhere, using the application.farcryUseJARPath switch and the javaSettings.loadPaths array.

That error is where it fails, and the exception.log is the correct place to go to view the exception details.

It seems like another compatibility issue between engines; ACF throws an error in a place that Lucee doesnā€™t when adding folders to the Java settings loadpaths. Thatā€™s unfortunate because we donā€™t want to have to hit the file system to see if the folders exist all the time, but I can probably cache it. Iā€™ll have a fix for it tomorrow :slight_smile:

As for where itā€™s used, itā€™s a new feature to allow JAR files to be loaded by convention (and so that we can remove JavaLoader because it wonā€™t work in Java 11). We use the new JARs feature in the memcached plugin and some other projects that are on p740.

The setupJARPath() function doesnā€™t seem to be the issue. I can confirm that. But: Which code fragment uses the JavaSettings.loadPaths array? I could not find any place where it is used. Where is the lookup being done?

And with respect to the exception log: Kindly find below a snippet of the log.

I guess you are with me that this message does not contain much information.

Looks like my email reply got eaten up :astonished:

javaSettings.loadPaths is an application setting which instructs the CFML engine where to find Java classes so that calls to createObject("java", ...) work without requiring the user to manually set it up or copy JAR files into specific locations. So there isnā€™t really any code that uses javaSettings.loadPaths except for the code that adds to the array in addJARPath();

The problem you see is a compatibility issue between engines. Lucee allows you to add an array of folder paths whether or not they exist. It seems Adobe ColdFusion only allows you to add a folder path if it exists, which is why itā€™s throwing an error for you saying ā€œThe directory or file /Users/Thomas/Sites/farcry/plugins/farcrycms/jars specified in LOADPATHS does not existā€. To fix it Iā€™ll have to check if the directory exists, cache the results in a persistent scope (to avoid disk i/o on every request), and then add the JAR path only if it exists.

Iā€™ve pushed a fix to p740 today to add Adobe ColdFusion compatibility for this feature, hopefully that resolves your issues! Please let me know how you go :slight_smile:

1 Like

Thank you for the updates. Before I got them, I simply created jars sub-directories in /farcry/plugins/farcrycms and /projects/<projectname>/. This seems to do the trick (without adding your latest additions).

With respect to the application footprint and the server memory used, Iā€™d prefer to alter the install/update procedure and create the directories whenever FarCry is installed or updated to 7.4.0 (either manually or automatically) over checking the server scope multiple times. Old habits die hard, they say, but back in the olā€™ times I was always lectured to only use the absolute minimum of shared resourcesā€¦ :wink:

I canā€™t really use the installer to create the directories for all the existing projects that are out there, since the installer is only run on brand new projects. That said the performance of that function is 0ms most of the time, so it should be ok :slight_smile: itā€™s only storing a small struct that contains boolean values, it might use 1kb RAM at most too.

Understood.

There are two more things I would like to mention:

  1. I reintroduced my <cftry><cfcatch> sequence in the coapiadmin.getWebskinPath() method to make it work.
  2. I wonder if the furl health check, the /farcryconstructor.cfm call, the setup of the JAR paths, and the datasource name section really need to be processed for each request (and, if we fire the login screen or a frame set, even multiple times). The furl health check is called in the OnRequestStart()method anyway. Why calling it twice?
    Also, I would expect the aforementioned tasks to be run only once in the Application.cfc's OnApplicationStart() method. The jar path array could be stored in the application scope and utilized by the utils.cfc ā€“ and other ā€“ components either by reference in the init() constructors or directly in the arguments scope.
    Do you think this would be possible?
  3. Am I correct that the javaLoader is still used? I found it in line 24 of the utils.cfc constructor:
	<cfset variables.loader = createObject(
		"component", 
		"farcry.core.packages.farcry.javaloader.JavaLoader"
	).init(paths) />

I thought this will no longer be needed?

Iā€™ll need to understand how about how expandPath() is failing for you because it seems like a server configuration problem to meā€¦ Can you share your modified coapiadmin.getWebskinPath() so that I can take a look at it?

Yes the /pingFU and the /healthcheck/live URLs need to be processed in the pseudo constructor on each request, they need to happen before the normal friendly URL processing kicks in because they are special low-level responses. Checking if a struct key exists isnā€™t expensive.

JavaLoader has been removed in the p740 branch which is considered ā€œbetaā€;

Re utils.cfc: Ah, the joys of beta source code and different branches! It seems that I mixed up the version. I am sorry.

Re /healthcheck/live URls: The check needs to be done in the pseudo constructor. Understood. But this doesnā€™t answer the question why it is done in the OnRequestStart() method again. Can the second call be omitted? And if not, why not?

Re ExpandPath(): Iā€™ll get to that question later today in the other thread. I canā€™t see any reason why we should spread this topic over two threads.

No worries, itā€™s very easy to mix up branches depending on whether youā€™re looking at local code or GitHub, it happens to us all!

Technically we do already have a health check in onRequestStart(), but it serves a different purpose :slight_smile: Thereā€™s two different health checks which respond at different points in the request, and these correspond to what Kubernetes expects for itā€™s two health checks;

  1. /healthcheck/ready responds in onRequestStart() so that we know the application has finished starting up and request are able to be served. This usually takes a while to return 200 OK for the first time (applications take X seconds to start up).

  2. /healthcheck/live responds in the pseudo-constructor so that we know the JVM is responsive. This needs to be a fast and frictionless response as itā€™s called very frequently, so this is probably the fastest and best place to do that (this should respond in 0ms or 1ms).

Both of them donā€™t really add any overhead to a request, itā€™s a struct key check and a string comparison, which is about the only way to do it when you need to respond to a specific URL path.

1 Like