Strange behaviour of coldfusion arguments surviving outside function? - scope

I have programmed ColdFusion for over 15 years, but never came across this.
Here's a code to replicate the behaviour:
<cfset _run()>
<cffunction name="_run">
<cfset variables.dataArray=ArrayNew(1)>
<cfset local.data={
area="profile"
}>
<cfset _append(data=local.data,field="name")>
<cfset _append(data=local.data,field="phone")>
<cfdump var="#variables.dataArray#" label="dataArray">
</cffunction>
<cffunction name="_append">
<cfargument name="data" type="struct" required="yes">
<cfargument name="field" type="string" required="yes">
<cfdump var="#arguments#" label="arguments">
<cfset arguments.data.field=arguments.field>
<cfset ArrayAppend(variables.dataArray,arguments.data)>
</cffunction>
As you can see this is what I do:
Initing an array in the variables scope to make it globally accessable
Initing a struct (local.data) in the local scope
Appending first field item (name) by invoking the data to the _append function
Appending second field item (phone) in the same manner
This code will result in the following output:
As you can see, the code results in an array with duplicate entries, when you might expected that the first index should have field="name".
As you also can see, the value of data that is invoked to _append the second time, contains the property "field" with the value "name". It seems to linger in the arguments scope from the first time we called the function? How is this possible. I thought the arguments scope was isolated to inside the cffunction tag?
But if I replace the _append function with this:
<cffunction name="_append">
<cfargument name="data" type="struct" required="yes">
<cfargument name="field" type="string" required="yes">
<cfdump var="#arguments#" label="arguments">
<cfset local.data=Duplicate(arguments.data)>
<cfset local.data.field=arguments.field>
<cfset ArrayAppend(variables.dataArray,local.data)>
</cffunction>
it will give the following output:
As you can see, making a duplicate of the arguments.data before appending "field" to it, solves the problem. Note that just doing:
<cfset local.data=arguments.data>
was not enough.
Can someone explain this behaviour of the arguments scope?

So after some research, I found this on Adobe Coldfusion Documentation Page (Bolding of text done by me):
About the Arguments scope
All function arguments exist in their own scope, the Arguments scope.
The Arguments scope exists for the life of a function call. When the function returns, the scope and its variables are destroyed.
However, destroying the Argument scope does not destroy variables, such as structures or query objects, that ColdFusion passes to the function by reference. The variables on the calling page that you use as function arguments continue to exist; if the function changes the argument value, the variable in the calling page reflects the changed value.
This was an eye opener for me, and it will keep me out of trouble in the future :)

Related

Caching Instantiated Object

I am trying to cache a block of code that instantiates two objects (the primary object extends the generic abstract one.
Without the cache-specific code everything works fine. But when I run the below code I only get a blank page. I'm unsure if this is expected behavior but I doubt it.
I'm calling this like so:
test.cfm
<cfset foobar = CreateObject("foo") />
<cfset foobar.pushLeads(
a = 1,
b = 2
) />
foo.cfc
<cffunction name="pushLeads" access="public" returntype="void">
<cfargument name="a" required="true" />
<cfargument name="b" required="true" />
<cfset local.cachedVendorData = cacheGet("vendorExport") />
<cfif IsNull(local.cachedVendorData)>
<cfsavecontent variable="local.vendorCFC">
<cfset local.leadsObj = createobject("baz").init() />
<!--- Take leads and pass into cfc for pushing to remote server --->
<cfset test = local.leadsObj.pushLeadData(
a = arguments.a,
b = arguments.b
) />
<cfdump var="#test#">
</cfsavecontent>
<cfoutput>#local.vendorCFC#</cfoutput>
<cfset cachePut("vendorExport", local.vendorCFC, CreateTimeSpan(0,0,1,0))>
</cfif>
</cffunction>
Edit - I forgot to add before that, before caching I had a CFDUMP that would show all results returned. Now that I added caching the dump results are not appearing.

Alternatives to Evaluate

An audit has shown up a vulnerability in our use of evaluate as it allows arbitrary code execution. Has anyone got an alternative? Example below. We're running CF9.
<cfquery name="getvalue" datasource="#application.ds#">
SELECT #url.column#
FROM dbo.tbl#url.table#
WHERE (int#url.table#Id = <CFQUERYPARAM Value="#url.Id#">)
</cfquery>
<cfif url.rowtype eq "text">
<cfoutput>
<input type="text" id="focus#url.table##url.column##url.Id#"
name="#url.table##url.column##url.Id#"
value="#evaluate('getvalue.#url.column#')#"
class="inputtext"
onblur="updateeditvalue('#url.rowtype#','#url.table#','#url.column#',#url.Id#
,escape(this.form.#url.table##url.column##url.Id#.value))"
style="height:20px;width:500px;">
</cfoutput>
<cfelseif url.rowtype eq "textarea">
<cfoutput>
<textarea id="focus#url.table##url.column##url.Id#"
name="#url.table##url.column##url.Id#"
class="inputtext" style="height:20px;width:500px;"
onblur="updateeditvalue('#url.rowtype#','#url.table#','#url.column#',#url.Id#
, escape(this.form.#url.table##url.column##url.Id#.value))">
#evaluate('getvalue.#url.column#')#
</textarea>
</cfoutput>
</cfif>
you will need to use getvalue[url.column][1] so you are pulling in row 1 of the getvalue query, you'll get an error otherwise. Peter is correct about SQL injection issues as well. At a minimum you need to use <cfqueryparam> for your queries
You shouldn't be including the column/table/id column names directly within the URL, this is opening up the SQL for injection. I see you have used the cfqueryparam for the value, however the rest of the query makes this redundant.
You should pass aliases via the URL and then set the correct column names. A very quick example is below:
<cfset idColumn = ""/>
<cfset columnName = ""/>
<cfset tableName = ""/>
<cfif structKeyExists(url, "aliasForColumnA")>
<cfset columnName = "the_real_column_name_a"/>
<cfset tableName = "the_real_table_name_a"/>
<cfset idColumn = "int" & #tableName# & "Id"/>
<cfelseif structKeyExists(url, "aliasForColumnB")>
<cfset columnName = "the_real_column_name_b"/>
<cfset tableName = "the_real_table_name_b"/>
<cfset idColumn = "int" & #tableName# & "Id"/>
</cfif>
<cfquery name="getvalue" datasource="#application.ds#">
SELECT
#columnName#
FROM
#tableName#
WHERE
#idColumn# = <CFQUERYPARAM cfsqltype="CF_SQL_INTEGER" Value="#url.Id#"/>
</cfquery>
If you really have to run SQL that is that dynamic, here's what I'd do. onApplicationStart you query the database metadata using cfdbinfo for a list of tables in your database and store them in application scope.
You can write a function which takes URL.table and checks that it exists in your list. It can throw an error if it's not listed.
The same approach would work for columns. That'll check that your tables/columns exist, but if you want to only allow access to certain tables/columns then you're no choice to store some kind of list or set of aliases as AlexP and Peter suggest.

Redirect all web traffic except a few dirs/paths?

I have a website that I need to redirect almost everything to another domain, except for a few directories/paths. The server is running ColdFusion and IIS in a hosting environment.
Functionality:
a) http://example1.com redirects to http://example2.com
b) http://example1.com/special stays put
c) http://example1.com/anydir redirects to http://example2.com
Any suggestions for how I can accomplish this?
I considered doing it in ColdFusion, but this won't handle case c). URL Rewrite in IIS isn't possible, because this is a limitation in the hosting provider.
edit:
I just realized that the functionality above does not explicitly state this case:
d) http://example1.com/anydir/anydir redirects to http://example2.com
I created this a while back to redirect an existing application from an old path to its new path. I believe it relies on subfolders to be in existence, for example "anydir/anydir/" must actually be real folders. I essentially just paste it into an existing application folder so config, application and index files are overwritten and then redirects take place based on definitions in config.
The redirect definitions are regex so can actually get quite complicated if necessary. It is an ordered array so you can put more specific redirects first and the more general ones at the end. You can either include a "last resort" redirect at the end or allow an error to occur if no definitions match--just depends on how precise you want to be.
config/config.cfm
<cfset config = {
debug=true
, redirects = [
{find="^/path/temp/dir2/(.+)$", replace="http://temp.domain.com/dir2\1"}
, {find="^/path/temp/(.+)$", replace="http://temp.domain.com/\1"}
]
} />
index.cfm
[blank file]
Application.cfc
<cfcomponent>
<cfset this.name="Redirect#hash(getCurrentTemplatePath())#"/>
<cfinclude template="config/config.cfm" />
<cffunction name="onRequestStart">
<cfset redirect(cgi.path_info) />
</cffunction>
<cffunction name="onMissingTemplate">
<cfargument name="targetPage" required="true" />
<cfset redirect(arguments.targetPage) />
</cffunction>
<cffunction name="redirect">
<cfargument name="targetPage" required="true" />
<cfset var i = 0 />
<cfset var newpath = "" />
<cfloop from="1" to="#arraylen(variables.config.redirects)#" index="i">
<cfif refindnocase(variables.config.redirects[i].find, arguments.targetPage)>
<cfset newpath = rereplacenocase(arguments.targetPage, variables.config.redirects[i].find, variables.config.redirects[i].replace) />
<cfif len(cgi.query_string)>
<cfset newpath &= "?" & cgi.query_string />
</cfif>
<cfif variables.config.debug>
<cfoutput>#newpath#</cfoutput>
<cfabort>
</cfif>
<cflocation url="#newpath#" addtoken="false" />
</cfif>
</cfloop>
<cfthrow type="custom.redirect.notfound" />
<cfabort>
</cffunction>
</cfcomponent>
You're better handling redirects with the server if you can, but if you can you could do it with CF with something like this.. how you structure really depends on what the actual URLs you need to handle are..
You could likely handle case (c) with Regular Expressions..
<!---get the current URL--->
<cfset currentURL = "#cgi.server_name##cgi.path_info#" >
<!---based on the URL, redirect accordingly--->
<cfif FindNoCase("example1.com/special", currentURL)>
<!---do nothing--->
<cfelseif FindNoCase("example1.com", currentURL)>
<cflocation url="http://www.example2.com" >
</cfif>

Coldfusion string::split() issue

I have the following code
<cffunction name="getObjTag" returnType="string" output="false">
<cfargument name="obj" Type="string" required="true">
<cfargument name="tagname" Type="string" required="true">
<cfreturn obj.split("<" & tagname.toUpperCase() & ">")[2]>
</cffunction>
Which results in the following error
Invalid CFML construct found on line 96 at column 63.
ColdFusion was looking at the following text:
[
The CFML compiler was processing:
A cfreturn tag beginning on line 96, column 10.
A cfreturn tag beginning on line 96, column 10.
Why is this? This happens on compile, not run.
CF 9 added the ability to access the results of the split as an array directly from the function call. The following works as expected on my local install of 9.0.1:
<cfset foo = "this is a string" />
<cfdump var="#foo.split(" ")[1]#" />
The dump shows 'this' in this example.
CF can't access the results of the split as an array directly from the function call. You need an intermediate variable.
<cfset var tmpArray = arrayNew(1)/>
<cfset tmpArray = arguments.obj.split("<" & arguments.tagname.toUpperCase() & ">")/>
<cfif arrayLen(tmpArray) gt 1>
<cfreturn tmpArray[2]/>
<cfelse>
<cfreturn ""/>
</cfif>
You also need to watch your indexes. Although the java array underneath is 0 index'ed, using coldfusion to get at it makes it indexed by 1.

How can I use Verity to index and search database content in ColdFusion 9?

I have tried to use ColdFusion 9 to build search engine in my site. The key is Verity which I read it is the best tool to do the indexing and searching in my database content.
But I search around with no luck about any tutorial to tell me how to done this, even a tutorial is missing, or I think I don't found it.
I am using ColdFusion 9 with MySQL server. Could you advice me how to do this? or any tutorial, article, or e-book is also welcome.
Actually, you have two great engines for CF9: Verity (classic) and Solr (modern).
Both of them implement the idea of collections. Creating and maintanence of the collection is pretty obvious and can be found in manual (see previous links).
The main hint for you can be found on cfindex tag manual page: you can populate (update) the collection with query data. Set type custom, enter the query name and all columns you need (combinations may vary).
All you need after that is to use cfsearch.
Also I can recommend to set up the script executed by scheduler to refresh your collection periodically.
EDIT
Sample code (note: code not tested, just the simplified cut from my old component). These are two methods of the CFC.
<cffunction name="index" access="public" returntype="any" output="true" hint="Rebuild search index">
<cfargument name="collection" type="string" required="true" hint="Target collection name">
<cfset var local = {} />
<cftry>
<!--- pull the content --->
<cfquery datasource="#variables.dsn#" name="local.getContent">
SELECT C.id, C.title, C.content, P.name AS page
FROM #variables.tableContent# C
INNER JOIN #variables.tablePages# P
ON C.id_page = P.id
</cfquery>
<!--- update collection --->
<cflock name="cfindex_lock" type="exclusive" timeout="30">
<cfindex collection="#arguments.collection#"
action="refresh"
type="custom"
query="local.getContent"
key="id"
custom1="page"
title="title"
body="title,content"
>
</cflock>
<cfreturn true />
<cfcatch type="any">
<!--- custom error handler here --->
<cfreturn false />
</cfcatch>
</cftry>
</cffunction>
<cffunction name="search" access="public" returntype="any" output="true" hint="Perform search through the collection">
<cfargument name="collection" type="string" required="true" hint="Target collection name">
<cfargument name="type" type="string" required="true" hint="Search type">
<cfargument name="criteria" type="string" required="true" hint="Search criteria">
<cfargument name="startrow" type="numeric" required="false" default="1" hint="Select offset">
<cfargument name="maxrows" type="numeric" required="false" default="50" hint="Select items count">
<cfset var local = {} />
<cftry>
<!--- pull the data from collection --->
<cfsearch collection="#arguments.collection#"
name="local.searchResults"
type="#arguments.type#"
criteria="#LCase(arguments.criteria)#"
startrow="#arguments.startrow#"
maxrows="#arguments.maxrows#"
>
<cfset local.resultsArray = [] />
<!--- convert data into the array --->
<cfloop query="local.searchResults">
<cfscript>
local.res = StructNew();
local.res["id"] = local.searchResults.key;
local.res["summary"] = Left(local.searchResults.summary, 500) & "...";
// highlight the search phrase
local.res["summary"] = ReplaceNoCase(local.res["summary"], arguments.criteria, "<strong>" & arguments.criteria & "</strong>", "ALL");
local.res["page"] = local.searchResults.custom1;
local.res["title"] = local.searchResults.title;
ArrayAppend(local.resultsArray, local.res);
</cfscript>
</cfloop>
<cfreturn local.resultsArray />
<cfcatch type="any">
<!--- custom error handler here --->
<cfreturn false />
</cfcatch>
</cftry>
</cffunction>

Resources