[Looking for Charlie's main web site?]

Did you know about the NULL attribute of CFQUERYPARAM? I didn't, until today.

Note: This blog post is from 2007. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
Will the riches of CFML ever cease to amaze me? :-) Did you know about the NULL attribute of CFQUERYPARAM? I didn't, until today. Consider that you use CFQUERYPARAM and point its VALUE to a variable. What would happen if the value was empty, but your database required a null? How would you solve this? If you didn't know better, you might use an IF test to say "if it's null, use a null, else use the variable", but there a much more elegant solution.

Did you know that CFQUERYPARAM has a NULL attribute that is just for this purpose? It takes a boolean to indicate whether and when to use a NULL rather than the VALUE.

It's not new, having been around since 4.5 according to the CFML language history file. I've just never noticed it before. I learned about it today from a couple of folks on the great CFAUSSIE list.

Now, to be honest, the docs (CFML Reference for the tag) just don't make it as clear as it could be, I don't think (otherwise I'd like to think I'd have noticed it before). I don't see any mention of it in the Developer's Guide, for instance.

Anyway, you can read more about it in a blog entry from Michael Sharman, who I see also just happened to blog about it last month, with a lot more detail:

http://www.chapter31.com/2007/02/04/cfqueryparam-and-conditional-handling-of-nulls/

You might also want to read the comments there as well as at the CFMX 7 docs for the tag, both of which have people sharing their experiences using the tag, over time.

Using a range of hex values in a CF regular expression

Note: This blog post is from 2007. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
If you were asked by someone to strip a string of all characters having ASCII codes from some value and higher, how would you do it? If you'd think to use a regular expression, you get 5 points. :-) But how would you get it to take a range? If you're ever stumped, here's how.

Assume you have a str1 variable with the string to be processed, and you want a resulting str2 holding the result. Let's assume as well that you've been asked to strip any codes with ASCII values of 160 or higher. This will do it:

<cfset str2 = rereplace(str1,"[\xa0-\xff]","","all")>

The CF docs do discuss using the \xnn option to search for a hex value, but they don't show how to specify a range. You might, as I did, try a few variations until you stumble upon it. That's the format.

Oh, and as for the xa0, that's the hex equivalent of decimal 160, and ff is the top of the range. I didn't find that it performed any more slowly with ff or some lower number.

Hope all that's helpful to someone. As for why you may do the above, I'd rather take that up in a separate entry which I hope to write today.

Did you know you can store CFDOCUMENT and CFREPORT output in a variable?

Note: This blog post is from 2007. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
This may not be news to some, but it was to me. I just noticed that the CF7 tag, CFDOCUMENT (as well as the new form of CFREPORT), has an available NAME attribute. This allows you to save the resulting PDF or FlashPaper (or Excel, in the case of CFREPORT) output (in binary form) to a variable.

Some may know the available FileName attribute, which allows you to save the output to a file. This NAME attribute is simply an interesting alternative.

Here's a quick example:

<cfdocument format="PDF" name="test">
test
</cfdocument>

This creates a variable named test. How would you output it? CFCONTENT, of course, with the appropriate content type. If your page has created any other output, don't forget the Reset attribute as well:

<cfcontent variable="#test#" type="application/pdf" reset="Yes">

To output a Flashpaper format report, use a CFCONTENT type of "application/x-shockwave-flash", and for a CFREPORT Excel spreadsheet, use a CFCONTENT type of "application/vnd.ms-excel".

Why might you do this?

Of course, if you just did that above, you might as well not have bothered, right? The point is that you may do something between when you create the variable and when you output it. For one thing, you may create it in one CFC, custom tag, or CFFUNCTION-based UDF and then output it later in the request.

More likely, you may choose this approach to facilitate caching and later reusing the generated output. Just as with creating a variable with any tag, you could have instead provided a prefix scope like session, application, or server. Then you could manage that cached result any of the many ways that have long existed for caching other tag results in shared scope variables.

Posting a form to itself without trickery, using an empty ACTION attribute

Note: This blog post is from 2007. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
Did you know that if a form has an empty ACTION attribute (or none at all), it will post back to itself (to the page that presented the form)? This can be very useful, and it's a lot easier than more complicated code that struggles to build the ACTION attribute to hold the current filename and any query string.

In the first iteration of this entry, I referred to the two approaches of either providing an empty ACTION or none at all, but as the comments below show, the former violates the HTML spec. So let's stick with the notion of an empty ACTION. Same result, though.

How often have we all seen code along the lines of:

<form action="<cfoutput>#cgi.script_name#?#cgi.query_string#</cfoutput>" method="post">
...

or more involved:

<cfset action=CGI.SCRIPT_NAME>
<cfif CGI.QUERY_STRING NEQ "">
   <cfset action=action & "?" & xmlformat(CGI.QUERY_STRING)>
</cfif>
<form action="<cfoutput>#action#</cfoutput>" method="post">
...

All this could be replaced very simply with:

<form method="post" action="">
...

The form will post back to itself. I'll offer another post that shows a unique way to take advantage of this. In any case, I hope that this observation may help some folks.

(Update: I never got around to that other entry in 2007, but see a my reply to a comment below where someone asked for more info on the idea I had in mind.

Is this reliable?

Now, there are some who will argue that this is a violation of the HTTP HTML spec, and so it may be, but I've never found a browser in which it didn't work.

Again, a clarification over what I wrote originallyhere. As was refined in the comments below, it's a violation to have *no* ACTION, but it's perfectly legit according to the URI spec (section 4.2 at http://www.ietf.org/rfc/rfc2396.txt) to have an empty ACTION, which is interpreted as a "same-document reference. (Thanks, Christopher Bradford, for that info.) Given that, even the following cautions seem needless, but I'll leave them for any still concerned.

If you have any hesitation, because you have to support multiple browsers and you can't test all possibilities, I'll understand if you choose to pass on this. But certainly if you only need to support browsers you can test, then if it works as expected, enjoy.

If anyone reading this can offer where this is the case, I'd appreciate hearing it. If you want some simple code to test, try this:

<form method="post" action="">
   <input type="Submit">
</form>
<cfif cgi.request_method is "post">
   Posted to itself
</cfif>

If it shows the text within the IF, then it worked as expected.

What about query string info?

You may wonder about the earlier more involved examples that showed passing the query string, in case any had been passed to the form. No problem. This technique passes any query string along just fine. Try it yourself (add ?test=test to the form and view in the debugging info that it's still in the URL scope after submission.)

CFFundamentals: Mimicking a form submission using CFHTTP

Note: This blog post is from 2006. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
While developers who've used CFML for a long time will regard some topics as old hat, we have to remember that there are folks who either have come along recently or simply never used some feature. This is one of those topics, and since I answered it on a list, I offer it here.

I'd like to start doing that more often (reformatting some answer I give on some list, so that others may learn from it.) As in this case, I'll refer to such as CFFundamentals and create a category for them so that interested readers may find them more readily.

The problem

Someone wrote wanting to mimic (in CFML code) the submission of some data to a form that they saw happening within an Airborne Trackship (http://track.dhl-usa.com/TrackEmail.asp) page.

They saw that the form submitting to that allowed one to enter an email address and some tracking numbers to have a status email sent back to that address. Using "view source" in the browser, they observed that the form actually called a Javascript form which submitted the form to TrackEmail.asp?nav=TrackByEmail. Since this was on a page at http://track.dhl-usa.com/, they realized they'd need to submit to http://track.dhl-usa.com/TrackEmail.asp?nav=TrackByEmail.

They also noticed that the form had two input fields, txtTracknbrs and txtEmailAddress, so they tried to put together a CFHTTP request that tried to submit to the form using this URL:

http://track.dhl-usa.com/TrackEmail.asp?nav=TrackByEmail&txtTracknbrs=nnnnnnnnn&txtEmailAddress=someemail@address.com

It didn't work. They asked if anyone knew why.

My Response: He was close, but needed to understand a couple of points

Here's what I wrote:

I think your problem is in presuming that the Javascript method builds a request that looks like the one you tried in CFHTTP. I just reviewed the page, and I see that it's a form, with a METHOD=POST, and so your CFHTTP needs to send its info in the way a form post would. And that's NOT by passing the variables on the URL as you have. (One may argue that they can write CFML apps that accept either FORM or URL variables, but the page being called is an ASP page, so we can't make that presumption.)

With that, I changed your CFHTTP to the following and it worked (you should be getting an email soon, as it told me the request was accepted). Try it yourself:

<cfhttp url="http://track.dhl-usa.com/TrackEmail.asp" method="POST">
   <cfhttpparam type="URL" name="nav" value="TrackByEmail">
   <cfhttpparam type="FORMFIELD" name="txtTrackNbrs"
value="nnnnnnn">

   <cfhttpparam type="FORMFIELD" name="txtEmailAddress"
value="someemail@address.com">

</cfhttp>

<cfoutput>
#cfhttp.filecontent#
</cfoutput>

Note that it uses a TYPE="URL" for the nav query string value (as that WAS passed on the URL), but uses TYPE="FormField" for the form fields, since those are passed as form fields. You were right in getting the exact names of the form fields as used in the form fields of the form. That's critical as well.

I'll point out as well that when trying to mimic such forms in CFHTTP, it's also sometimes critical to send along any hidden form fields. There were two on that form, but I left them off and it seems to work. IF you found you needed to add them, just add them as more TYPE="FormField" values.

I should note as well that sometimes when trying to simulate a form submission, you will also need to send along any cookie values that might be being sent by your browser to the server. Those aren't shown in the form. You could use browser/server proxy tools (like the free Fiddler or Firebug tools) to detect what's being sent along.

Hope that may help other readers.

I'll add in this blog entry that regarding that last point, about using HTTP proxies to study the browser/server communication, I wrote about those previously in:

Alternatives HTTP debugging proxies, for debugging

Programmatically accessing allowed IP Addresses in Developer Edition (and understanding the limits)

Note: This blog post is from 2006. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
Have you ever needed to know programmatically what IP addresses, besides localhost, ARE allowed to access your Developer Edition of ColdFusion? I'll show you how here.

The message above does show the allowed IP addresses, but what if you need that list programmatically for any reason? Since I couldn't find it discussed anywhere, and I discovered the feature today, I just figured I'd share it in case it helps anyone searching in the future.

Along the way, I also explain for any who need to know, both what the "allowed IPs" limits are and how they've changed in 5, 6, and 7, as well as how to reset the allowed IP addresses.

Background

Most folks know that the Developer Edition of ColdFusion is intended for just local (as opposed to production) development. But to be specific, it's limited not JUST to requests from your localhost but both localhost and 1 other IP address (in CFMX 6 or 6.1), or localhost and 2 other IP addresses in CFMX 7.

Once that limit is reached, if you or anyone else tries to access a CF page from an IP address other than localhost or that 1 (in 6/6.1, or 2 in 7) permitted to that point, the user will get a message:

A License Exception has been thrown.

You tried to access the developer edition from a disallowed IP (nnn.nnn.nnn.nnn). The developer edition can only be accessed from 127.0.0.1 and two additional IP addresses. The additional IP addresses are: yyy.yyy.yyy.yyy,zzz.zzz.zzz.zzz

Of course, in a real message the nnn, yyy, and zzz would be real numbers, and in CFMX 6/6.1 it would list only "one additional IP address".

But the key point for the purpose of this blog entry is that the end of the message lists the IP address(es) that have "gotten in", such that any requests from other IP addresses will fail to run and will get the error message.

So it's sometimes useful to know the list of allowed IP addresses, but what if you need to access it programmatically? And how do you reset it to allow other IP addresses to get in?

Accessing that List of Allowed IP Addresses Programmatically

If you have need to know the list of allowed IP address(es) programmatically for any reason, the following code using the undocumented and unsupported ServiceFactory will show it:

<cfobject action="CREATE" type="JAVA" class="coldfusion.server.ServiceFactory" name="ServiceFactory">

<cfoutput>#ServiceFactory.LicenseService.getAllowedIp()#</cfoutput>

Unfortunately, there is no equaivalent to this in CF Admin API (in CF 7 or 8).

Resetting the Allowed IP Address(es)

Finally, some have wondered how they can reset the list of permitted IP addresses. Just as in CF5 (where you could access the developer edition from any 1 IP address, either localhost or another), in CFMX 6.1 and 7 you just need to restart the server. That will clear the list of allowed addresses.

In CFMX 6.0, however, you needed to actually edit a license.properties file while the server was stopped (as I blogged about back in 2002. Thankfully, that was fixed in 6.1.

For more information on that, as well as for a documented reference of the localhost/localhost+1 licensing in CF 4.5/5/6.0/6.1, see http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=tn_17832.

For documented reference to the localhost+2 support in CF 7, see the bottom of the table at http://www.adobe.com/products/coldfusion/productinfo/product_editions/#s2.

Of course, the error message itself also clearly explains it in CFMX 6 and 7.

Reloading CF web services programmatically, using the CF7 Admin API

Note: This blog post is from 2006. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
I'm surprised to not see much out there about how to reload or refresh CF's cached WSDL proxy for calling a web service, at least programmatically using the new CF 7 Admin API. Perhaps it's because people have been tripped up, or simply haven't explored it. Either way, I'd like to offer here the code you need, and also point out some tips and traps.

Update for CF8: As an update to this entry from 2006, which is focused on CF7, I'll note that there was yet another approach that was added in CF8. Both still work and have their own value. More on the CF8 feature in my later blog entry. Still, the feature added in CF7 is important to understand, too, so please read on.

Introduction: Why You Would Want to

As background, someone reported having a problem calling a web service from CFML, and a solution suggested was that the person reload or refresh the web service using the CFMX Admin console (in the "Data & Services" > "Web Services" nav bar tab. There, you'll find any web services that have been called from CF (which is often a surprise to folks that they're tracked there). For each listed web service, there are 3 buttons and the middle one does a refresh (which means it goes and grabs the WSDL and builds a local java proxy stub, which is used when you then invoke the web service).

But one may then wonder how to do that programmatically, without having to open the Admin console. In CFMX 6.1, you could use the undocumented ServiceFactory, as I'll show below to those still using that. But since that's being deprecated, you really ought to learn the new CF7 approach.

The CF 7 Admin API Approach

As of CFMX 7, we are expected to use the new Admin API, a set of CFCs provided with CFMX 7, which offer a formal, secured API for accessing functionality otherwise offered in the Admin console.

So how would one do that to refresh a web service? Well, there is an extensions.cfc in the Admin API (a set of CFCs in the webroot's /CFIDE/adminapi/ directory), and it has a reloadWebService method that's just the trick. How did I know that? You can call the built-in CFC documentor by browsing the CFC directly:

http://<em>[servername]</em>/cfide/adminapi/extensions.cfc

Before you can call that, though, note that you do need to "login" to the Admin API by calling the login method of the administrator.cfc first. (Check out its docs to learn more.)

But to save you that effort, here's some code. (As always there are several ways to call a CFC and its methods (using CFINVOKE or CFOBJECT, or createObject either within CFSCRIPT or not), but here's at least one approach.):

<cfset createObject("component","cfide.adminapi.administrator").login("youradminpw")>
<cfset ws = createobject("component","CFIDE.adminapi.extensions")>
<cfset ws.reloadWebService(name="<em>webservicename</em>",path="<em>WSDLurl</em>")>

Note that you need to specify your own admin password in the first line, and in the last line you need to specify a web service name and its WSDL URL.

What's with this notion of passing in a web service "name"?

As you contemplate that code, you will certainly know what the WSDL URL is, since it's the same one you'd use in a CFINVOKE or CFOBJECT/createobject call of the web service itself. But what's the "name" requested here? Well, that can trip you up and it deserves further discussion, as it has several ramifications as I'll explain here.

The name is the name shown in the Admin console for the given web service. The trick/trap is that if you never open and change the Admin console entry for this web service, then the name will simply be the same value as the WSDL URL. But there's more to understand.

First, if you didn't know it, one can edit that "name" in the Admin console, and then one can even use that "name" as an alternative (or "alias") to the web service WSDL URL when invoking the web service from CFML. That's a whole separate subject which I've covered in user group talks in the past.

But assuming that no one has modified the web service name (or for reasons I'll explain in a moment, if you are not using such an alias name when you invoke the web service), then you can presume the name and WSDL URL to be the same. As an example, one could change the last line above to:

Getting Web Service Names Programmatically

Now may wonder, "can I get the web service name programmatically?" You can. But here's where it gets a little confusing. There is an available getwebservices method of the extensions.cfc. And according to the docs, you can either pass in the "name" of the webservice, or leave it off to get all web services. If we don't know the name, then we may think we'd want to use the latter approach. But I find that it doesn't quite work as straightforwardly as it seems.

First, I tried calling getwebservices() without a name:

<cfset createObject("component","cfide.adminapi.administrator").login("youradminpw")>
<cfset ws = createobject("component","CFIDE.adminapi.extensions")>
<cfdump var="#ws.getwebservices()#">

Sadly, it returned an empty dump as a result. Yet I had several web services listed in my admin console. Here's the thing: none had a name. I then renamed one of them (to "test"), and tried it again, and suddenly the call did return an array of structures (1, in my case) with the name and WSDL URL.

Hmm. So it seems instead that the getwebservices() ought perhaps instead be named getnamedwebservices(), since it only returns web services whose names have been changed (been given an alias).

Still, though, if I do pass in a name, as the docs suggest, then I do indeed get the same result:

<cfset createObject("component","cfide.adminapi.administrator").login("youradminpw")>
<cfset ws = createobject("component","CFIDE.adminapi.extensions")>
<cfdump var="#ws.getwebservices("test")#">

Now, you may wonder: "if I can't a listing unless I know the name, or can only get a list of 'all' of them if they are named, then how might I get the info for ones that have no name or whose name I don't know?"

Good question. And guess what I've found? A couple of important things.

First, I've found that you can also pass in the WSDL URL, even for a web service that's been renamed with an alias like "test", such that this works:

<cfset createObject("component","cfide.adminapi.administrator").login("youradminpw")>
<cfset ws = createobject("component","CFIDE.adminapi.extensions")>
<cfdump var="#ws.getwebservices("http://ws.invesbot.com/stockquotes.asmx?WSDL")#">

Again, that's not the web service name but the WSDL URL. And the resulting dump shows the name and that URL. So the API docs on this are a little misleading.

Be sure to refresh the "right" webservice: the one you'd really try calling

But perhaps a more important thing is that I found that you can have a web service entry for the SAME WSDL URL but with different names/aliases. Why is this important? Because you want to make sure you refresh whichever one you're using.

This goes back to my point above when I introduced the refreshWebService method: you need to give it the name as YOU call the web service, otherwise you'll be refreshing a different proxy stub and won't see the benefit you expect.

If you use the WSDL URL when you invoke the web service, then that will create a proxy stub with that "name", and therefore you want to use that as the "name" when you refresh it.

If you rename a web service in the Admin console, and then use that when you invoke the web service, then you want to use that as the "name" when you refresh it.

Refreshing Web Services Using the ServiceFactory

Since some folks reading this may not have moved to CF 7, let me show how you could do the same using CFMX 6/6.1 (or indeed 7, since it still works there). It's important to note, however, that using the ServiceFactory is not only not documented but it's also not supported. It has security problems (since there's no need to provide the admin password as you must in the Admin API). Also, it may eventually be obsoleted or otherwise restricted.

Still, since it's been documented by others in the past and is readily available on the web, I'll offer it here:

<cfobject action="CREATE" type="JAVA" class="coldfusion.server.ServiceFactory" name="ServiceFactory">
<cfset ServiceFactory.getXMLRPCService().refreshWebService("<em>webservicename</em>")>

Now, you'll notice that I've indicated that the value passed into the refreshWebService method is the webservicename. That's because it works just like the Admin API reloadWebService discussed at the top here. Be sure to specify the name as you would use it in the invocation of the web service (whether an alias or the full URL).

How to Confirm That Refreshing is Working

As you try these three approaches (Admin button, Admin API, and ServiceFactory), you will probably benefit greatly from being able to see for sure that the refresh/reload is updating the java proxy/stub class files. Where do you find them? They're stored in a "stubs" directory under your CFMX install directory, such as C:\CFusionMX\stubs or C:\CFusionMX7\stubs.

Then, under that, you will find directory names such as WS141836989, or a name that's the same as the alias/name you give to a web service in the Admin console. Inside those directories you will find other subdirectories, eventually finding some that hold the .class files representing the objects available in the web service. It's those .class files whose date/time stamp you want to see changing when you do a refresh/reload.

I'll note that there's no mapping or indication of that WSnnnn directory name, to know which one holds the web service you're interested in. I guess you just have to find the right one by looking for one whose class names map to the web service object you're calling. (If anyone knows a better connection, please do share it.)

Finally, I think it may be worth clarifying that when you do a refresh/reload of a web service using the approaches above, you need to have first made a call to that web service from CFML (or entered it manually in the admin console).

Hope all that's helpful.

Summary of Notes for Adobe Folks

Before I end, in case any Adobe folks are listening, here is a restatement (and expansion) of the couple of observations I made about wrong or undocumented functionality. This is for both the docs group and the engineers, since this is also about internal API documentation returned by the CFCs and their functionality:

  • getwebservices() only returns web services that have a name that was changed, whereas the API doc says it returns "all web services", so it either is working incorrectly or ought to be called getnamedwebservices instead
  • though not documented, getwebservices("wsdlurl") works also. The API docs say that it should only take a name (and I tested this against a web service where I had renamed it, so it was not getting it "by name")
  • if you do consider renaming the getwebservices() method to be called getnamedwebservices(), you might then also want to rename getwebservices(name) to getwebservice (singular), since it just gets one webservice
  • it would be nice to be able to refresh ALL webservices that use a given WSDL URL at once, perhaps by new method that accepts URL rather than name (and works for all occurrences of that URL in the cache, whether named with an alias or not)

Are you using CFCOMPONENT NAME? I'll argue that you should not be. Here's why

Note: This blog post is from 2006. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
Are you using CFCOMPONENT NAME? I'll argue that you should not. Here's why. Someone asked me what I knew about the CFCOMPONENT NAME attribute. They were finding it being used but couldn't find any explanation of its purpose. I'd never really noticed it before, but as I started researching it, I can see some sources of potential confusion. From my testing, I'm going to propose that it's not something you should be using at all. It serves no purpose and can instead lead to confusion. I realize that's going to surprise those who use it, and perhaps it will cause a stir, but let me make the case, and you can decide for yourself.

Supported versus Unsupported Attributes on CFCOMPONENT, and GetMetaData()

First, there's no NAME attribute listed for CFCOMPONENT in the CFMX docs. If one wants to give a friendly name or description to a CFC, that's the purpose of the DisplayName or Hint attributes instead.

More important, whenever you use any attribute on CFCOMPONENT that's not otherwise supported, the only programmatic motivation would seem to be to take advantage of the fact that a GetMetaData() call against that CFC will return that attribute as a key in its result.

But it turns out there is ALREADY a getmetadata key called "name" that's always returned from a getmetadata call against a CFC, and it always returns an internally generated value indicating the name and location of the CFC, and more to the point, this value returned is NEVER affected by your use of a NAME attribute on CFCOMPONENT.

Curiously, I do find that different versions of CF and BD return slightly different values for that (more on that in a moment). And for those who may wonder, neither the use of NAME nor any unsupported attribute affects the output of the CF Component browser.

That's why I'll argue you shouldn't be using NAME at all. It not only does nothing (is not returned by getmetadata), but it may lead someone to be confused that it should. I don't know if I will go so far as to propose that CF (and BD) should raise an error when it's used. Sadly, a google search for "cfcomponent name" shows hundreds of pages using it (and those aren't real code running on people's servers but instead code people are sharing on the web for demonstration purposes), so restricting that could have a lot of impact.

If one wonders how people started using it, I'll note that (sadly) the CF7 docs user guide section on "documenting CFCs" does shows using the NAME attribute. I will leave a comment there pointing to this blog for folk's consideration (and perhaps to get Adobe to remove that, if all this observation holds true).

Further, the person asking me about this did so because they noticed that the CFEclipse CFC wizard also implements the NAME attribute in CFCs it creates. Again, unless comments to this suggest otherwise, it seems that perhaps that should be changed.

I am open to hearing from others. It sure seems that all is as I see it, but if I've missed something, I welcome comments below. If there is any egregious problem or misunderstanding I've created, I will offer a follow-up entry to correct things.

What getmetadata's NAME key returns on different CFML engines

I mentioned above that if you use GetMetaData against a CFC instance, it does return a NAME value. What does that value hold? Well, the docs for CFMX 6.1 simply indicate that it's "the component name", with no further explanation. The CF7 docs reference on GetMetadata does elaborate further, now saying that it's the "Component name, including the period-delimited path from a component search root such as the web root or a directory specified in the administrator Custom Tag Paths page."

I mentioned before that I'd found differences between the two versions of CF as well as between editions of BlueDragon. Here's a demonstration. I setup a simple test hello.cfc file in a /demo directory off my webroot:

<CFCOMPONENT name="hellox">
<CFFUNCTION NAME="GetHello" ACCESS="REMOTE" RETURNTYPE="string">
<CFRETURN "Hello World">
</CFFUNCTION>
</CFCOMPONENT>

Note that in the CFC I have set the value of NAME to "hellox". I did also test it using simply "hello" or "hello.cfc", and it made no difference at all in the following results. I then called it with a template that used Getmetadata:

<cfobject component="hello" name="hello">
<cfdump var="#getmetadata(hello)#">

The dump of the getmetadata result always showed a NAME key and it was always the same whether I provided one or not, and regardless of what value I provided. But there was a slight difference among the engines, which showed the following values for the getmetadata name key:

versionvalue
CFMX 6.1hello
CFMX 7.02demo.hello
BDJX 6.2.302 (JX)demo.hello *
BDJX 6.2.302 (.NET)demo.hello

The big difference is that CFMX 6.1 does not show the location of the CFC as found, just its name. Both CFMX 7 and BD show the directory name in which the CFC was found, as explained in the CF7 help above. This value can be useful for times when you wonder which CFC you have found on an invocation (since both CFMX and BD have a variety of search locations that they use), although note there is also a "path" key returned by getmetadata which returns the absolute rather than relative path.

You'll note I've put an asterisk next to the BDJX test. To do this testing among the 4 engines, I'm running on Windows XP with IIS, so I can't have multiple web sites. (Yes, I know about tools to get around that and that Apache would let me have multiple sites.) Instead, I setup multiple virtual directories, each pointing to the different engines (as I document in a CFDJ article), so that I could call the page invoking the CFC using each of the different versions of CF and BD. On my laptop, I use virtual directories named _cfmx, _cf7, _bdjx, and _bdnet (so calling my demo page above might be http://localhost/_cf7/demo/invoke_hello.cfm.) I was surprised to find that in BDJX (only), the resulting name key returned did also show the name of the virtual directory as a prefix to the value returned, so the getmetadata name value returned by BDJX in my example was _bdjx.demo.hello. Of course, you won't see that difference if you don't use virtual directories.

Those who delight in the discover of the new value returned in the pre-defined getmetadata name key may find the above details useful. The more important point, again, is simply that the use of a NAME attribute on CFCOMPONENT has no programmatic purpose at all. It's not a supported attribute, and unlike any other unsupported attribute, it's not returned by GetMetaData, so it seems it should not be used at all. Let me know if you think otherwise.

Simplifying the captcha graphic in Lyla Captcha (and BlogCFC)

Note: This blog post is from 2006. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
Wish you could simplify your captcha's? If you use Peter Farrell's Lyla Captcha, as I do because it's embedded in Ray's BlogCFC, I'll show a few quick changes you can make that will make them much easier for your users to read.

Sound counter-intuitive? Aren't captcha's supposed to be difficult to read, to hamper spammers? In my last entry, I made a call for simplifying captchas and why they aren't all bad. As a blog owner who uses them to weed out the random spambots who would otherwise clog my comments and feedback mechanisms, I like captchas, and I'm grateful for the work Peter's done.

That said, I have to admit that as I've encountered them in the blogs of others, I've grown a tad weary of their complexity. They require the user to type several characters and have several swirly ovals, random lines, and a wavy background. Frankly they're quite hard to read, and it would be a shame to lose commenters for that reason.

hard captcha

Again, the intent is to make it hard for some spammer to scan the captcha request somehow and figure out what's being requested so as to automate around it. Fair enough, but as I said in my last entry I'm really not that concerned about protecting my site from determined break-ins. I'm not a bank. I just want to keep out the automated pests.

With just a couple of changes to Lyla's captcha.xml file, you'll have a much simplified captcha, if you want one.

hard captcha

Lyla is highly customizable

On a lark, I decided to try to find out if Lyla might just be modifiable to dial down the intensity. Turns out it is, by simple changes in the lyla captcha.xml file, as documented in this PDF. Thanks again, Peter! :-)

After a few simple tweaks, I reduced my captcha to just asking for 3 characters, all lowercase, without all the swirly ovals, lines, and wavy background.

Changing Lyla's captcha.xml

In BlogCFC, the captcha.xml file is located in blog\client\includes (or just \includes if you've installed the blog client directory as your webroot.)

To effect the change I wanted, I ended up with the following values for the following entries. Again, see the docs for more info:

<config name="randStrType" value="alphaLcase"/>
<config name="randStrLen" value="3"/>
<config name="fontColor" value="dark"/>
<config name="backgroundColor" value="light"/>
<config name="useGradientBackground" value="false"/>
<config name="backgroundColorUseCyclic" value="false"/>
<config name="useOvals" value="false"/>
<config name="useBackgroundLines" value="false"/>
<config name="useForegroundLines" value="false"/>

You can change them to suit your taste. Note that if you do change the randStrLen, the value selected represents the "average" length of the string that users will be asked to enter, and may vary by +/- 1 from that.

Make the changes, and check 'em out for yourself. Note that with Ray's BlogCFC, you need to reinitialize the blog (add ?reinit=1 to your blog URL) to see the changes. What I did was had one browser page open to do that, and another sitting on a blog comment form. After running the reinit, I could then just reload the comment page to see the impact. (If there's a still-simpler way to test changes to the captcha.xml, let me know.)

If you don't use BlogCFC, then you have to re-instantiate the captcha object after making changes to the XML file. If you've stored it in a shared scope (like application), you need to run some code that reloads it. Of course, restarting ColdFusion will also reload the CFC in whatever scope you stored it in.

Conclusion

Making these changes won't solve the accessibility problems some have with captchas, and it certainly could increase the risk of a determined spammer more easily breaking your captcha. As I said in the last entry, I doubt that's a real concern for most of us. If it proves to be so, then you can dial the intensity back up.

I just want to keep from annoying my readers, and I hope others will consider these changes to keep from annoying us all. :-)

PS: I do realize that one could skip the captcha graphic entirely and just go to prompting the user for a random string. That may just a bit "too" easy for a spambot to get around. To each his own.

Thanking Peter

One last note: while Peter certainly appreciates your kind comments (and do share them, as I'm sure many don't bother), those who REALLY appreciate his work should note that he gratefully accepts contributions by way of his Amazon Wishlist or you may may make a donation with PayPal, using his address, pjf@maestropublishing.com.

Captchas: making them simpler, and dialing down the angst against them

Note: This blog post is from 2006. Some content, links and indeed comments from others may be outdated--though not necessarily. Corrections are welcome, in the comments. I may revise the content if necessary.
Most by now understand what captchas are. Some love 'em, some hate 'em. I want to dial down the rhetoric some with this perspective: as a blog owner fighting frequent spam in comments and trackbacks, captchas (in some form, not necessarily a graphic) have their place to keep out spambots, and they can indeed be simplified (even the graphics ones) and at no loss of benefit. My bottom line: I don't use them as a double-key deadbolt lock to keep out intruders, I just use them as a screendoor to keep out random pests.

If you use Peter Farrell's Lyla Captcha, which I use because it's embedded in Ray's BlogCFC, in the next entry I'll show a few quick changes you could make in the Lyla captcha.xml file to make them much easier to read, going from this
hard captcha
to this
simple captcha.

Before that, I just want to expand on those thoughts above on the general angst against captcha's, and why I think it's ok to make them easier to read.

The Haters

I realize that some have gone to great lengths to decry captchas primarily because they are not "accessible" (to those using screenreaders), though audio ones help solve that.

Others simply hate them because they're too darned difficult to read. I've surely seen that, even in the ones created by default in Lyla (thus my next entry on addressing that).

Now, while most use a graphic that a user must read, it's not the only approach. As the previous link discusses, other approaches include simpler approaches like asking the reader to add some numbers or answer a question (that only a human could reasonably do).

But the other complaint is that they give those who use them a false sense of security, because they can be easily broken, even the graphic ones.

But my Blog is Not a Bank

Here's the thing: my blog is not a bank. While the difficulty in breaking a captcha may be important to a bank or commercial site trying to use them for authentication, I just want to make it hard for an automated spambot to post crap in my blog comments and trackback forms. If you have any similar king of input form on a publicly accessible site, you may suffer similar problems.

I really can't believe anyone would go to the lengths of scanning and breaking the captcha on my site (random as it is) to get a crap spam comment into my lil' ol' blog. And some of the comments are just nonsense; it's not like they're trying to drive traffic to another site or something--so the popularity of my (or your) site isn't the issue. It's just the annoyance factor (both to me as I get notified of comments and to readers who would have to sift through them if I didn't delete them as I do now).

Having made the case for why a simpler captcha may suffice for some purposes, in the next entry I'll show how to control the degree of difficulty in reading them for captchas built using Lyla Captcha.

More Entries

Copyright ©2019 Charlie Arehart
Carehart Logo
BlogCFC was created by Raymond Camden. This blog is running version 5.005.
(Want to validate the html in this page?)

Managed Hosting Services provided by
Managed Dedicated Hosting