application.cfc - conditionally turn on session and/or client management? - coldfusion

I want to reduce the overhead of spider/crawler traffic. I'm not expecting to catch all of it, but if I can catch 90% of it then it's a win.
What's the best way to conditionally turn on/off session or client management in application.cfc? I'm thinking something along the lines of this, but I'm not sure if the CGI scope is always defined and initialized when application.cfc is instantiated.
this.sessionManagement = !isSpiderRequest();
and:
private boolean function isSpiderRequest() {
if (REFindNoCase("googlebot|msnbot|crawler|crawling|spider|wget|curl|baidu|robot|slurp|Gigabot|ia_archiver|libwww-perl|lwp-trivial|Mediapartners-Google", CGI.HTTP_USER_AGENT))
return(true);
return(false);
}

We set the session timeout to 10 seconds for bots. They don't get errors, but don't consume any (much) memory.
<!--- SET UP THE APP --->
<cfscript>
THIS.Name = "ASDF";
THIS.ApplicationTimeout = createTimeSpan( 0, 0, 60, 0 );
THIS.SetClientCookies = true;
THIS.Datasource = "ASDF";
THIS.SessionManagement = true;
// TEST WHETHER USER IS A BOT
THIS.IsBot = THIS.checkUserAgent();
// VISITOR IS A BOT ~ SET FAST TIMEOUT
if (THIS.IsBot == true) {
//abort;
THIS.SessionTimeout = createTimeSpan( 0, 0, 0, 10 );
// VISITOR IS A NOT A BOT ~ SET SLOW TIMEOUT
} else {
THIS.SessionTimeout = createTimeSpan( 0, 0, 60, 10 );
}
</cfscript>
<cffunction name="checkUserAgent">
<cfscript>
// QUERY THE CURRENT LIST OF BOT WORDS
LOCAL.BotWordList = THIS.getBotWords();
// GET THE VISITORS CURRENT USER AGENT IN LOWER CASE
LOCAL.ThisAgent = trim(lCase(CGI.HTTP_USER_AGENT));
// look at the user agent to see if the browser
// browser's user agent contains a banned word
// return true or false
</cfscript>
</cffunction>
Alternative answer... I haven't tried this, but I don't see why it won't work.
<!--- SET UP THE APP --->
<cfscript>
THIS.Name = "ASDF";
THIS.ApplicationTimeout = createTimeSpan( 0, 0, 60, 0 );
THIS.Datasource = "ASDF";
THIS.SetClientCookies = true;
// TEST WHETHER USER IS A BOT
THIS.IsBot = THIS.checkUserAgent();
// VISITOR IS A BOT ~ SET FAST TIMEOUT
if (THIS.IsBot == true) {
THIS.SessionManagement = false;
// VISITOR IS NOT A BOT
} else {
THIS.SessionManagement = true;
THIS.SessionTimeout = createTimeSpan( 0, 0, 60, 10 );
}
</cfscript>

Related

Function that includes JSP file is returning an empty result in Lucee but not ACF

I have a function utilizing an include, of a JSP file, to retrieve thread information which is then converted into a query object. The function returns an empty query Lucee, but it executes properly in ColdFusion.
CFML:
<cffunction name="mainThreads" output="false" returntype="query" access="public">
<cfargument name="filterPages" type="boolean" required="true">
<cfscript>
var threadStackDump = "";
var thread = 0;
var stackTrace = "";
request.threads = arraynew(1);
GetPageContext().include("putParentThreadInRequestScope.jsp");
ThreadQuery = QueryNew("id, name, group, stacktrace, alive", "Integer, VarChar, VarChar, VarChar, Bit");
QueryAddRow(ThreadQuery, arrayLen(request.threads));
for ( thread = 1; thread lte arrayLen(request.threads); thread = thread + 1 )
{
QuerySetCell(ThreadQuery, "id", request.threads[thread].getId(), thread);
QuerySetCell(ThreadQuery, "name", request.threads[thread].getName(), thread);
QuerySetCell(ThreadQuery, "group", request.threads[thread].getThreadGroup().getName(), thread);
QuerySetCell(ThreadQuery, "alive", request.threads[thread].isAlive(), thread);
threadStackDump = "";
stackTrace = request.threads[thread].getStackTrace();
for ( element = 1; element lte arrayLen(stackTrace); element = element + 1 )
if ( arguments.filterPages )
{
if ( findNoCase('runPage',stackTrace[element]) neq 0 or findNoCase('runFunction',stackTrace[element]) neq 0 )
threadStackDump = threadStackDump & stackTrace[element] & "#chr(13)#";
}
else
threadStackDump = threadStackDump & stackTrace[element] & "#chr(13)#";
QuerySetCell(ThreadQuery, "stacktrace", threadStackDump, thread);
}
return ThreadQuery;
</cfscript>
</cffunction>
JSP
<%
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Thread threadList[]=new Thread[Thread.activeCount()];
threadGroup.enumerate(threadList);
request.setAttribute("threads", threadList);
%>
The code is not working in Lucee, but I'm not sure why. Does it have something to do with the java versions?
I'd guess you forgot to enable handling of .jsp in your web.xml, but ... you're not aware of it because the problem code executes inside a cffunction that suppresses all output!
Take a leaf out of Troubleshooting 101 and test the problem code in small chunks. Start by executing the JSP include separately. If it displays the JSP code on screen, instead of executing it, then you know JSP handling isn't enabled, and that's your problem.
<cfscript>
GetPageContext().include("putParentThreadInRequestScope.jsp");
writeDump( request );
</cfscript>

How to compare column values in cfscript?

I would like to loop over query and compare column values. Here is example of CFML code:
<cfquery name="qryUserPerm" datasource="#Application.dsn#">
SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(session.AccountID)#">
</cfquery>
<cfset local.permissionType = "">
<cfset local.permissionLevel = "">
<cfset local.permissionList = "">
<cfif qryUserPerm.AccessLevel EQ "S">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = qryUserPerm.State>
<cfelseif qryUserPerm.AccessLevel EQ "C">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.City))>
<cfelseif qryUserPerm.AccessLevel EQ "B">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.Building))>
</cfif>
Code above should be translated to cfscript, I got this far but can't figure it out how to access column values.
<cfscript>
public string function permissionList(required string AccountID) {
local.fnResults = "";
local.permissionList = "";
try{
local.qryPermissions = new Query();
local.qryPermissions.setDatasource("#Application.dsn#");
local.qryPermissions.setSQL("SELECT AccessType, AccessLevel, State, City, Building FROM Permissions WHERE AccountID = :AccountID");
local.qryPermissions.addParam(name="AccountID",value="#trim(arguments.AccountID)#",cfsqltype="cf_sql_idstamp");
local.qryRes = qryPermissions.execute();
for ( i = 1 ; i <= qryRes.getResult().recordCount ; i++ ) {
if(qryRes["AccessLevel"][i] EQ "S"){
local.permissionList = "";
}else if(qryRes["AccessLevel"][i] EQ "S"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.Agency,","));
}else if(qryRes["AccessLevel"][i] EQ "C"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.District,","));
}else if(qryRes["AccessLevel"][i] EQ "B"){
local.permissionList = ListRemoveDuplicates(ValueList(qryRes.Building,","));
}
}
local.fnResults = permissionList;
}catch(any e){
local.fnResults = e.message;
//writeOutput(e.message);
}
return fnResults;
}
writeOutput(permissionList(AccountID));
</cfscript>
If anyone can help please let me know.
(From comments ...)
The issue is local.qryRes doesn't actually contain a query object. Confusingly, calling execute() doesn't return a query, but calling execute().getResult() does. Try changing the assignment from:
local.qryRes = qryPermissions.execute();
To:
local.qryRes = qryPermissions.execute().getResult();
A few other observations:
It is important to local scope ALL function variables, including your loop index i. Otherwise, you may get some bizarre and unpredictable results if the component is stored in a shared scope.
Although I don't think a loop is necessary, if you do loop, consider the simpler for..in syntax, instead of an indexed loop:
for (local.row in local.qryPermissions ) {
if (local.row.AccessType eq "S") {
//... code here
}
....
}
Since the access fields are so closely related, I'd probably have the function return a structure containing all three keys (AccessType, AccessLevel, PermissionList) rather than having three separate functions.
Rather than using a loop, consider going with one of the suggestions on your other thread,
Best way to store permissions for the user account?
You can also use :
local.qryPermissions = queryExecute(
"SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = :AccountID" ,
{AccountID={value="#trim(arguments.AccountID)#", cfsqltype="cf_sql_idstamp"}} // Or "?" and "[value=xxx,cfsqltype=xxx]"
) ;
And then just build out your permissions pieces without the loop:
local.permissionType = qryPermissions.AccessType ;
local.permissionLevel = qryPermissions.AccessLevel ;
switch( qryPermissions.AccessLevel ) {
case "S" : local.permissionList = qryPermissions.State ;
break ;
case "C" : local.permissionList = ListRemoveDuplicates(ValueList(qryPermissions.City)) ;
break ;
case "B" : local.permissionList = ListRemoveDuplicates(ValueList(qryPermissions.Building)) ;
break ;
}
Also see my notes on the other question about potential for unintentional, semi-related data.

Why is CFTHREAD not running query and creating a file?

I have a function that should check the date of a file. If the file is greater than sixty seconds, a query should run and create a new file. The query takes sixty seconds to run.
This process works perfectly when it's not wrapped in a CFTHREAD. When CFTHREAD is used, nothing seems to happen. I get no errors. What I expect to see, is a new file being made. I never see that new file.
Where should I look for an error? What am I missing? Why is CFTHREAD not working?
<!--- GET CATEGORIES --->
<cffunction name="getCategories" access="remote">
<cfscript>
LOCAL.MaxFileAge = 60;
LOCAL.MaxFileUnits = 's';
// THE FILE
LOCAL.TheFileDaily = "#VARIABLES.JSDir#\#VARIABLES.DayMonth#-categories.json";
// THE FILE DOES NOT EXIST
if (fileExists(LOCAL.TheFileDaily) == false) {
LOCAL.MakeNewFile = true;
// THE FILE EXISTS
} else {
// GET THE DATE OF THE FILE
LOCAL.LastModified = getFileInfo(LOCAL.TheFileDaily).LastModified;
// GET FILE AGE
LOCAL.FileAge = dateDiff(LOCAL.MaxFileUnits, LOCAL.LastModified, now());
// FILE IS OLD
if (LOCAL.FileAge > LOCAL.MaxFileAge) {
LOCAL.MakeNewFile = true;
} else {
LOCAL.MakeNewFile = false;
}
}
</cfscript>
<cfif LOCAL.MakeNewFile eq true>
<cfthread action="run" priority="HIGH">
<cfquery name="Q">
SELECT Stuff
FROM Tables
</cfquery>
<!--- MAKE THE DAILY FILE --->
<cffile action="write" file="#LOCAL.TheFileDaily#" output="#serializeJSON(Q)#">
</cfthread>
</cfif>
</cffunction>
You can't write to and share the local scope to a seperate thread, you need to share them via the request scope (request is the ideal scope for this as developers have very tight control over what data is contained within). You might try something like this:
Create a struct within the request scope and write to that.
In fairness, only variables you need to transfer need be in the request scope's struct. This is just a generic update because I don't know what the contents of your CFTHREAD really looks like. In this case, it actually looks like TheFileDaily is the only variable you're sharing, so that would be the only thing that needed to be in the request scope.
<!--- GET CATEGORIES --->
<cffunction name="getCategories" access="remote">
<cfscript>
request.lData = StructNew();
request.lData.MaxFileAge = 60;
request.lData.MaxFileUnits = 's';
// THE FILE
request.lData.TheFileDaily = "#VARIABLES.JSDir#\#VARIABLES.DayMonth#-categories.json";
// THE FILE DOES NOT EXIST
if (fileExists(request.lData.TheFileDaily) == false) {
request.lData.MakeNewFile = true;
// THE FILE EXISTS
} else {
// GET THE DATE OF THE FILE
request.lData.LastModified = getFileInfo(request.lData.TheFileDaily).LastModified;
// GET FILE AGE
request.lData.FileAge = dateDiff(request.lData.MaxFileUnits, request.lData.LastModified, now());
// FILE IS OLD
if (request.lData.FileAge > request.lData.MaxFileAge) {
request.lData.MakeNewFile = true;
} else {
request.lData.MakeNewFile = false;
}
}
</cfscript>
<cfif request.lData.MakeNewFile eq true>
<cfthread action="run" priority="HIGH">
<cfquery name="Q">
SELECT Stuff
FROM Tables
</cfquery>
<!--- MAKE THE DAILY FILE --->
<cffile action="write" file="#request.lData.TheFileDaily#" output="#serializeJSON(Q)#">
</cfthread>
</cfif>
</cffunction>
Useful sources:
Working with Threads
Adam Cameron's post on the topic

ColdFusion client variables

I have an issue with client variables in ColdFusion 10. When I create client variables in one page it is effecting that page only and I am not able to access the variables in another page in my application. Here is code from Application.cfc:
this.Name = "test";
this.ApplicationTimeout = CreateTimeSpan(0,0,0,0);
this.ClientManagement= "yes";
this.ClientStorage = "clientstorage";
this.SessionManagement = true;
this.SessionTimeout = CreateTimeSpan( 0, 0, 20, 0 );
this.SetClientCookies = "yes";
this.SetDomainCookies = "no";
this.ScriptProtect = "all";
I was having the same problem on a site and fixed it by using setdomaincookies="yes"

How do I retrieve the current value of enablecfoutputonly?

We are using Coldfusion 9.
Is there a simple way to know if enablecfoutputonly has been set to true during a particular request?
I cannot test with CF9 right now, but in CF10 it is accessible from getPageContext() by checking the output object:
<cfscript>
out = getPageContext().getOut();
// Is the cfsetting enablecfoutputonly value currently true?
isSettingEnabled = out.getDisableCount() > 0;
WriteOutput("isSettingEnabled="& isSettingEnabled &"<br>");
// Is output currently allowed?
isOuputtingEnabled = out.getDisableCount() == 0 || out.getOutputCount() > 0;
WriteOutput("isOuputtingEnabled="& isOuputtingEnabled &"<br>");
</cfscript>
.. or using reflection:
<cfscript>
out = getPageContext().getOut();
internalMethod = out.getClass().getDeclaredMethod("isOutputEnabled", []);
internalMethod.setAccessible( true );
isOuputtingEnabled = internalMethod.invoke( out, [] );
// is output currently allowed?
WriteOutput("isOuputtingEnabled="& isOuputtingEnabled);
</cfscript>