I was helping a co-worker debug some code earlier and after about 5 minutes of digging through different includes, we found that a ColdFusion query deep inside of a nested CFInclude was overriding a query made on a top level page by the time the top level query was getting used for output. The problem here isn't one that I come across very often, but I can see this being a potential problem an procedural applications.
This got me thinking - wouldn't it be interesting if you could wrap a bunch of code with a tag and stop the changes in that tag from persisting to the variables outside of the tag. To experiment with this concept, I created the Sandbox ColdFusion custom tag that simply wraps chunks of code to protect the REQUEST and VARIABLES scopes:
Launch code in new window » Download code as text file »
In the code, we are setting a string, array, and struct variable across the REQUEST and VARIABLES scope. Then, within the sandbox custom tag, we update all three of those variables. Once the Sandbox ColdFusion custom tag has finished executing, we CFDump out the REQUEST and VARIABLES scope to see if anything has changed:
| | | | ||
| | ![]() | | ||
| | | |
If you look at the output, you will see that the girl name, Maria Bello, persisted and was not overridden by Lori Petty. The array has no traces of Jodie Foster, who was appended within the sandbox. And, you will see that Maria is still HOT, and not ugly as defined within the sandbox.
The CF_Sandbox call was able to prevent the updates to the VARIABLES and REQUEST scopes from affecting the rest of the page. Now, why would you want to do something like this? The particular bug that we found was that a query was running to populate a select box. Now, like I said, there are better ways to deal with this, but using this tag could potentially protect something like this:
Launch code in new window » Download code as text file »
Whether or not you even find this interesting, the execution of such Sandbox protection was actually quite easy to perform. All the ColdFusion custom tag does is duplicate the REQUEST and VARIABLES scope into the custom tag and then copy them back out at the end:
Launch code in new window » Download code as text file »
When we copy the scopes, it is important to use Duplicate() so as to cut down on the number of ties to "by reference" variables. Then, when copying them back, we need to clear the target scopes before we append the stored scopes so as to remove any artifacts that have been added to the scopes within the sandbox.
Of course, if you have objects, such as ColdFusion components, that maintain an internal state, the Sandbox cannot "rollback" methods called on such objects. This can really only protect against built-in type updates (string, array, struct, query, etc.).
Download Code Snippet ZIP File
Comments (7) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Ben, I had to do a similar thing just a few days ago. I had the same situation where an include file overwrote one of the values in the calling template, so I needed to isolate the calling template from the includes. Unfortunately, most of the code re-use in this application is via includes, so my solution focuses on isolating includes.
Basically, I create a custom tag ( /Modules/cfincludeWrapper.cfm ) with the contents below. It simply executes the include within the context of a custom tag to prevent the calling template's variables scope from being polluted. Any attributes you pass into the custom tag get placed in the variables scope of the custom tag before the include is called so that the include can access the variables it needs still without having to look at different scopes. Then at the end, the variables scope of the custom tag is returned to the calling template so the caller can read any values that were created as a result of the include.
[begin: cfincludeWrapper.cfm]
<!--- Initialize default/required parameters --->
<cfparam name="attributes.includeToWrap" type="string" default="" >
<cfparam name="attributes.returnVariable" type="variablename" default="includeResults" >
<cfif NOT len(attributes.includeToWrap) >
<cfthrow message="Include to Wrap must be specified" >
<cfelseif NOT len(attributes.returnVariable) >
<cfthrow message="Include to Wrap must be specified" >
</cfif>
<!---
Copy all the attributes passed into the variables
scope so that they're available to the include
--->
<cfset structAppend(variables, attributes, "yes") >
<cfset structDelete(variables, "includeToWrap") >
<cfset structDelete(variables, "returnVariable") >
<!--- Run the include --->
<cfinclude template="../#attributes.includeToWrap#" >
<!--- Send all the variables that the include generated back to the caller --->
<cfset "caller.#attributes.returnVariable#" = variables >
[end: cfincludeWrapper.cfm]
Posted by Kurt Bonnet on Dec 15, 2007 at 8:59 AM
I am very happy that somebody this information can help
Posted by Opener on Dec 15, 2007 at 2:25 PM
@Kurt,
I like it a lot. The one thing I am cautious of it the CFInclude inside of the custom tag. Since the CFInclude paths are relative to the currently executing template, it must be relative to the custom tag. I guess, you just have to be sure that the path you pass in via the custom tag attribute is relative to the application root? and then it looks like the custom tag assumes itself to be level below the root?
But overall, I like your idea better than mine because it doesn't really mess with the top-level VARIABLES or REQUEST scopes like mine were doing.
Good stuff.
Posted by Ben Nadel on Dec 15, 2007 at 2:43 PM
Glad you like it Ben. I agree, the only downfall to this is the use of the include path. I didn't include all the documentation on the code to keep my comment to a reasonable size, but basically, the path to the include should always be a path relative to the web root (/somedir/somefile.cfm).
I always call the custom tag by doing a CFIMPORT and then using the JSP-like syntax for calling the custom tag. This way, the only place you have to worry about the path to the include file is in the custom tag itself. And since I tend to organize my custom tags like I do my CFCs (i.e. /com/company/project/tags/includeWrapper.cfm; I can count on the custom tag ALWAYS being X directories deep and can always figure out the path to the include as long as the path passed in is web-root relative.
Of course if mappings are being used that may cause an issue.
Love your blog, keep up the posts :)
Posted by Kurt Bonnet on Dec 16, 2007 at 11:13 PM
@Kurt,
That sounds pretty good to me.
Posted by Ben Nadel on Dec 17, 2007 at 7:11 AM
I'm late on this one but could not resist... how can you just treat girls as objects Ben. Shame on you...
Haha
Posted by Devon on Mar 6, 2008 at 3:11 AM
@Devon,
ColdFusion makes me do it.
Posted by Ben Nadel on Mar 6, 2008 at 8:39 AM