Archive for the ‘ColdFusion’ Category

Search Multiple Files with CF and Java

Wednesday, January 23rd, 2008

I recently had the need to search a web directory’s application files for a particular string. Not as a customer-facing function, just as a one-off development need.

Well, CFEclipse (as truly awesome a tool as it is) doesn’t have a way to search multiple files for the same string simultaneously, and while Home Site does have this feature (”Extended Find / Extended Replace”), if you use it on anything but the smallest directories, it consistently times out and needs to be restarted again. So below is a little utility I wrote to recursively search a given directory for a given string. Its basically just two Java objects wrapped in a filtered CFDIRECTORY loop.

“But Tyler, why did you use Java when you could have just used CFFILE?” you say. Well, for me there’s now been several projects wherein CFFILE has considerably underperformed compared to its Java counterparts, and with the buffered reader object specifically, I can pass in fairly decent size files without ColdFusion choking on them. And its fast. Blazing fast. My browser almost caught fire (but I guess that’s the price you pay for efficiency). So nowadays, if I need a function that CFFILE would normally handle (at least in regards to reading and writing), I first consider the Java alternatives.

By the way, there is significant mixing of logic and presentation here, so be warned if you need to avert your eyes. Anyway, HTH.

<!--- the string to search for --->
<cfset request.searchstring = "thestring" />
<!--- the path to look in --->
<cfset request.startingpath = "/var/www" />
<!--- should the search be recursive. careful on this one. --->
<cfset request.recurse = false>
<cfset request.filecount = 0 />
<cfset request.linecount = 0 />
<cfset request.fileshown = 0 />    

<cfdirectory name="files"
    action="list"
    directory="#request.startingpath#"
    recurse="#request.recurse#"
    filter="*.cfm|*.cfc|*.cfml|*.js|*.css|*.htm|*.html"
    sort="directory asc, name asc" />    

<html>
<head>
    <style>
        * {
            font-size: 12px;
            font-family: 'courier new';
        }
        table {
            border: 0;
            padding: 0;
            border-collapse: collapse;
        }
        th { text-align: left; }
        th, td { padding: 0 15px 0 5px; }
        .even { background: #dadada; }
        .filename { font-weight: bold; }
    </style>
</head>    

<body>    

    <table>
        <tr>
            <th colspan="3">directory/filename</th>
        </tr>
        <tr>
            <th> </th>
            <th>num</th>
            <th>line of code</th>
        </tr>    

        <cfloop query="files">
            <!--- exclude this file from the search --->
            <cfif files.name NEQ listlast(cgi.SCRIPT_NAME,"/")>    

                <!--- set the source file to be read --->
                <cfset variables.sourcefile = files.directory & "\" & files.name />
                <cfset variables.linenumber = 1 />    

                <!--- instantiate the necessary java objects --->
                <cfset variables.filereader = createObject("java","java.io.FileReader").init(variables.sourcefile) />
                <cfset variables.bufferedreader = createObject("java","java.io.BufferedReader").init(variables.filereader) />    

                <!--- set the current line to the first available line in the file --->
                <cfset variables.currentline = variables.bufferedreader.readLine() />    

                <!--- beginning looping over the file line by line --->
                <cfloop condition="isDefined(' variables.currentline')">    

                    <!--- check the current line for the given search string --->
                    <cfif variables.currentline CONTAINS request.searchstring >    

                        <cfif request.fileshown EQ 0>
                            <!--- stack multiple results under a single file name --->
                            <tr valign="top" <cfif request.filecount MOD 2 EQ 0> class="even" </cfif> >
                                    <td colspan="3" class="filename"><cfoutput>#variables.sourcefile#</cfoutput></td>
                            </tr>   
                            <cfset request.fileshown = 1 />
                        </cfif>    

                        <!--- list the line number and line code containing the search string --->
                        <tr valign="top" <cfif request.filecount MOD 2 EQ 0> class="even" </cfif> >
                                <td> </td>
                                <cfoutput>
                                    <td align="right">#variables.linenumber#</td>
                                    <td>#htmleditformat(variables.currentline)#</td>
                                </cfoutput>
                        </tr>    

                        <cfset request.linecount = request.linecount + 1 />
                    </cfif>    

                    <!--- try to get the next line in the file. --->
                    <cfset variables.currentline = variables.bufferedreader.readLine() />
                    <cfset variables.linenumber = variables.linenumber + 1 />
                </cfloop>    

                <!--- close the buffered reader object --->
                <cfset variables.bufferedreader.close() />
                <cfif request.fileshown EQ 1>
                    <cfset request.fileshown = 0 />
                    <cfset request.filecount = request.filecount + 1>
                </cfif>
            </cfif>
        </cfloop>    

        <!--- list the line number and line code containing the search string --->
        <tr valign="top" <cfif request.filecount MOD 2 EQ 0> class="even" </cfif> >
            <th colspan="3">
                <cfoutput>
                    #request.linecount# matching lines in #request.filecount# files. #files.recordcount# files searched.
                </cfoutput>
            </th>
        </tr>
    </table>    

</body>
</html>

THIS.mappings in Application.cfc

Saturday, January 5th, 2008

I’ve always been a bit perturbed at the CFIMPORT tag, both because you have to use the tag on every page in which you’ll call the prefixed custom tag, including pages that are called with CFINCLUDE (will save that rant for another time), and also because the taglib attribute cannot take a variable. To illustrate, you cannot do this:

<!--- this does not work --->
<cfset request.tagpath = "/var/www/tagsdirectory/" />
<cfimport prefix="t" taglib="#request.tagpath#" />

As you probably know, you must declare the custom tag path in the cfadmin, then call the CFIMPORT tag using the name you assigned to the path in the admin. Like so:

<cfimport prefix="t" taglib="/tagpath" />

That is until now. In ColdFusion 8 aka Scorpio aka SomeOtherCoolName, you can now create application-specific paths for your custom tags. You do so by creating a structure called THIS.mappings in your Application.cfc, then adding keys and values to this structure referencing the tag path name and the tag path directory, respectively. Like so:

<cfcomponent displayname="Application">
<!--- application-specific custom tag paths --->
<cfset this.mappings = structnew() />
<cfset this.mappings["/tagpath"] = "/var/www/tagsdirectory/" />

Note the required forward slash preceeding the tag name you assign. Now just call the CFIMPORT tag like normal at the top of your page:

<cfimport prefix="t" taglib="/tagpath" />

Bada bing bada boom, dynamic tag paths. HTH.

Using CFCONTENT to Serve Images

Friday, November 30th, 2007

This post started from a curiosity about how to store image data in SQL. Not for any particular reason, just how. I’m now firmly convinced that the correct method is: don’t. And for those wondering, here’s just one article i came across that outlines arguments for (not many) and against (more than a few) storing image data on SQL. Its on an ASP-related site, but the same principles apply to Coldfusion (and PHP and whatever else for that matter). There are a couple points the site doesn’t mention, but the general idea is that its much more advisable to just store location pointers for the images in SQL, and then store the images themselves on the file system.

So this idea in conjunction with the CFCONTENT tag gives me really all the control I might want, without having to awkwardly shove my images into a database (even though the binary aspect to that is pretty cool). So here’s an example of how you could use these two things in coordination.

First, the basic html page.

<html>
<head></head>
    <body><img src="fakeimage.cfm?id=1234" />
</body>
</html>

Note the source of the IMG tag is a .cfm page with an associated url parameter. This parameter is what we will use to fetch and serve the correct image to the requester, allowing us to use this single page to serve all the images on our site. Also interesting, even though the page being called is a .cfm page, the html example shown above could have a regular .htm or .html extension.

Next up is the imageserver.cfm page that, you guessed it, serves the images.

<cfif IsDefined("URL.id") AND IsNumeric(URL.id) AND URL.id GT 0>
<cfquery name="fetch" datasource="yourdatabase">
    SELECT imagelocation
    FROM imagetable
    WHERE id = <cfqueryparam cfsqltype="cf_sql_integer" value="#URL.id#">
    LIMIT 1    <!--- MSSQL users discard this line --->
</cfquery> 

<cfif fetch.recordcount AND trim(fetch.imagelocation) NEQ ""> 

    <cfcontent
        type="image/#listlast(fetch.imagelocation,'.')#"
        file="#fetch.imagelocation#"
        reset="true" /> 

</cfif> 

</cfif>

When you store the image’s full path in the database instead of a relative path, you can even store your images outside of your web root (I’ve tested this on Linux and Windows). This could be a good way to keep spiders or other bots from finding your well-hidden images directory. Also, I know some people are purists with their language/s of choice (and I can understand that), but if you really wanted to get fancy here and fetch the correct mime type instead of hoping based on the extension like I did above, you could do this very simply with some nested Java functions:

<cfcontent
    type="#getPageContext().getServletContext().getMimeType(fetch.imagelocation)#"
    file="#fetch.imagelocation#"
    reset="true" />

And remember, since Coldfusion is translated directly to Java bytecode (since version 6), one could maybe argue that the additional functions are really the same as Coldfusion anyway. OK maybe not. Yeah, not really at all. That one sounded better in my head. Anyway, that’s how you could use Coldfusion to serve your site’s images. Some other cool additions to this functionality:

1. You could keep people from leeching your images for their own use, even when they call your imageserver.cfm with the right url parameters. Do this by checking the referer before serving the images, and if its not you, cfabort. Like so:

<!--- strip the referer down to the domain name --->
<cfset request.refererurl = listfirst(replace(CGI.HTTP_REFERER,"http://","","one"),"/") /> 

<!--- if the referer is any site but this one --->
<cfif comparenocase(request.refererurl,cgi.SERVER_NAME) NEQ 0>
    <cfabort/>
</cfif>

I should caution you that technically, CGI variables can be spoofed, but in practice I would count this as few and far between. And if that kind of thing is happening on your site (which you likely won’t know about without some thorough digging anyway), then you likely have larger issues at hand. But this will definitely put the brakes on your average hotlinker. You could even drive the point home by serving up a less-than-pleasant image that they didn’t ask for (if you get my drift lol), instead of aborting.

2. If, for some reason, you wanted to track various statistics on your images and their number of views or whatever, you could do that by databasing any information you want prior to serving the image. Useful information could be anything found in the URL, REQUEST, SESSION, APPLICATION, SERVER, or CGI scopes.

This was fun. I hope this helps someone!