6

I initially posted this as an answer to this question earlier regarding Empty CGI.REDIRECT_URL on ColdFusion 2016. After thinking about it, I thought better of it since technically didn't answer the OP's question. Instead I decided to make it into a separate question, even though it's more of a commentary than a question. While this technically might not meet the full requirements of a Minimal, Complete, and Verifiable example and people might ding me with downvotes, I decided it was worth it anyway in the hope that it will become easier to find for future CFers who might encounter this. Thus preventing them from banging their head against the wall regarding this peculiar behavior of the CGI struct/scope.

With that said, the CGI struct/scope has some undocumented inconsistent behavior from other structs/scopes. Note that I personally take no credit for this discovery since I happened across this some time ago upon reading Ben Nadel's blog post on this. So all the information I'm posting here is already detailed there, but I wanted to write a nice summary here on SO.

Undocumented behavior 1 - Unlike other structures, if a CGI struct key doesn't exist, then it won't throw an error when referencing it.

In the OP's original question, he was wondering why cgi.REDIRECT_URL existed but was empty. As he eventually found out, it never actually existed. As a separate example you can execute this line of code without throwing an error. Not what you'd expect, huh?

<cfoutout>#cgi.THIS_IS_A_FAKE_KEY#</cfoutout>

So what's a CFer to do? Test for the key existence.

<cfif structKeyExists( CGI, 'THIS_IS_A_FAKE_KEY' )>
    THIS_IS_A_FAKE_KEY exists
<cfelse>
    THIS_IS_A_FAKE_KEY doesn't exist
</cfif>

Undocumented behavior 2 - Unlike other structures, if you dump the CGI struct, it won't display all the key/value pairs, it will only display a defined set of keys.

In the OP's case, he had a custom Apache CGI variable cgi.REDIRECT_URL that was used in his code prior to upgrading to CF2016 and was able to refer to it directly. However, I'm presuming if he dumped out the cgi struct, it wouldn't appear in the dump. In Ben Nadel's case, he also had a custom cgi variable called cgi.document_root that was passed through from a load balancer and was able to refer to it directly, but he also wasn't able to see the key when dumping the cgi contents.

So what's a CFer to do? Understand this and store it in the back of your mind so you won't get bitten when you dump the cgi contents and the key/value pair isn't there. Other than that, not much else.

user9263373
  • 1,054
  • 8
  • 15
  • This is a great question. It has been troubling me for a long time now. My apache server passes `HTTP_X_FORWARDED` to the CF server. But when I dump this I am able to find the string in the `CGI` scope. But `CGI.HTTP_X_FORWARDED` gives me the value. This is a really terrible. – rrk Feb 15 '18 at 06:04
  • I tried to take a look under the hood. But most of this Java things flew right over my head. – rrk Feb 15 '18 at 06:07
  • Just found out that the way `CGI` scope works is that first it scans in the scope itself, then if not found, then it scans in the *request headers* (`getHttpRequestData()`). But in the request headers the keys will be different that means underscores(`_`) in the request scope will be hyphens(`-`). That means `cgi.THIS_IS_A_FAKE_KEY` actually pulls the data from `getHttpRequestData().headers['THIS-IS-A-FAKE-KEY']`. Now for the question, *Why?*; I have no idea. – rrk Feb 15 '18 at 06:40

2 Answers2

8

I went in to the cfusion.jar file of ColdFusion. What I found there was a bit confusing.

CGI scope is not of a structure form as one would hope for.

This is how a call for CGI variable is handled. eg <cfoutout>#cgi.THIS_IS_A_FAKE_KEY#</cfoutout>

  1. The normal valid CGI scope variables are the ones in this list and these will be initialized to "" by default.

     private static final String[] names ="AUTH_PASSWORD","AUTH_TYPE","AUTH_USER","CERT_COOKIE","CERT_FLAGS","CERT_ISSUER","CERT_KEYSIZE","CERT_SECRETKEYSIZE","CERT_SERIALNUMBER","CERT_SERVER_ISSUER","CERT_SERVER_SUBJECT","CERT_SUBJECT","CF_TEMPLATE_PATH","CONTENT_LENGTH","CONTENT_TYPE","CONTEXT_PATH","GATEWAY_INTERFACE","HTTP_ACCEPT","HTTP_ACCEPT_ENCODING","HTTP_ACCEPT_LANGUAGE","HTTP_CONNECTION","HTTP_COOKIE","HTTP_HOST","HTTP_USER_AGENT","HTTP_REFERER","HTTP_URL","HTTPS","HTTPS_KEYSIZE","HTTPS_SECRETKEYSIZE","HTTPS_SERVER_ISSUER","HTTPS_SERVER_SUBJECT","LOCAL_ADDR","PATH_INFO","PATH_TRANSLATED","QUERY_STRING","REMOTE_ADDR","REMOTE_HOST","REMOTE_USER","REQUEST_METHOD","SCRIPT_NAME","SERVER_NAME","SERVER_PORT","SERVER_PORT_SECURE","SERVER_PROTOCOL","SERVER_SOFTWARE","WEB_SERVER_API" };`
    

    Also all of these values also come from various java libraries javax.servlet, HttpServletRequest etc.

  2. If the requested variable is not any of these after a bit of checks, ColdFusion goes to the request headers. You can see these using getHttpRequestData().headers. Then looks for a key there with hyphens(-) instead of the underscores(_) in the cgi key request. (If the key starts with http_, then the key in the request headers will be with out it like this http_x_forward in request header will be x-forward)

     value = request.getHeader(name.replace('_', '-'));
    

From what I understand as far as ColdFusion is concerned the keys, mentioned in the first point are the recognized as part of the CGI scope. But when there is additional information passed from a Apache load balancer server to ColdFusion, those end up in the request headers. Since the java getHeader just returns a blank string (or something with data type undefined) instead of a undefined error, ColdFusion does not identify any of the keys is defined or not.

So if the key THIS_IS_A_FAKE_KEY is sent to ColdFusion from an intermediary such as an Apache server. You will find that in the getHttpRequestData().headers['THIS-IS-A-FAKE-KEY'] but not on the CGI scope dump.

That being said my personal opinion is that it is better to check directly in the getHttpRequestData().headers for custom CGI variables other than in the scope itself.

Community
  • 1
  • 1
rrk
  • 15,677
  • 4
  • 29
  • 45
7

EDIT Thanks to Ageax for pointing out one of my test cases was invalid on my earlier revision of this post.

Great bit of detective work RRK! So I decided to perform an experiment to verify your finding by creating two loops. The first loop will display the key/value pairs from getHttpRequestData().headers and the second loop does the same using the corresponding key/value pairs from the cgi scope by replacing the - with _. Voila! as reported by RRK, we can see how you can obtain the values by either method. I made an updated gist and posted here for anyone interested.

<cfset httpHeaders = getHttpRequestData().headers>

<h3>getHttpRequestData().headers</h3>

<cfloop collection="#httpHeaders#" item="key" >
    <cfoutput><strong>#Key#</strong> : #httpHeaders[key]#<br></cfoutput>
</cfloop>

<h3>cgi keys dash to underscore</h3>

<cfloop collection="#httpHeaders#" item="key" >
    <cfset keyUnderscore = replace(key, "-", "_", "all")>
    <cfoutput><strong>#keyUnderscore#</strong> : #cgi[keyUnderscore]#<br></cfoutput>
</cfloop>
user9263373
  • 1,054
  • 8
  • 15
  • 1
    Why do you say dot notation is inconsistent? I wouldn't expect a dynamic key like `#cgi.keyUnderscore#` to be evaluated properly, without hard coding ("CGI.HTTP_ACCEPT") or using structure notation like in the second loop. – SOS Feb 15 '18 at 16:37
  • @Ageax Thanks for pointing that out to me! Sometimes I get caught up in my own head with a conclusion without realizing that my test was invalid. Good catch and thanks. I'll update the post later and correct that. – user9263373 Feb 15 '18 at 17:21
  • Don't we all! Great thread though. Always fun to dig into the internal workings of CF :) – SOS Feb 15 '18 at 17:23