Posting File Data Using A Base64 Encoding In ColdFusion

Posted January 4, 2008 at 10:35 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

This was sort of based on an Ask Ben inquiry, so I will include it in this section, but it's not entirely accurate. Someone had asked me about passing a file to a web service and I had suggested that one of the ways to do this was to pass it using a Base64 encoding of the binary data. This would allow the data to be easily defined using XML, which is how many web services function (think SOAP, think XML-RPC). While I am not following SOAP or XML-RPC standards in this quick demo, the ideas stay the same.

There are two players in this game. One entity is the file that reads the file data, encodes it, and submits it to the web service. The other entity is the actual web service file that accepts the data, converts the encoded data back to binary, and writes it to a file.

To demo this, we are going to first read in a JPG file and then post it as XML data using the ColdFusion's CFHttp and CFHttpParam tags:

  • <!---
  • Create the path to the binary file that we want to
  • post via a web service.
  • --->
  • <cfset strFilePath = ExpandPath( "./girls_in_bikinis.jpg" ) />
  •  
  •  
  • <!--- Read in the binary file. --->
  • <cffile
  • action="readbinary"
  • file="#strFilePath#"
  • variable="objBinaryData"
  • />
  •  
  •  
  • <!---
  • Create the XML that we are going to post. When posting this
  • file, we are going to encode it as base64 text data.
  • --->
  • <cfsavecontent variable="strXmlData">
  • <cfoutput>
  •  
  • <file>
  • <name>#XmlFormat(
  • ListLast( strFilePath, "\/" )
  • )#</name>
  •  
  • <ext>#XmlFormat(
  • ListLast( strFilePath, "." )
  • )#</ext>
  •  
  • <!---
  • When storing the binary data, we are going to
  • pass the file as a Base64 encoding. I am like
  • 95% sure that this will result in only valid
  • XML characters, but I am not completely sure.
  • --->
  • <base64>#ToBase64(
  • objBinaryData
  • )#</base64>
  • </file>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Build the URL for the "web service" to which we are going
  • to post this file data.
  • --->
  • <cfset strUrl = (
  • GetDirectoryFromPath(
  • GetPageContext().GetRequest().GetRequestUrl().ToString()
  • ) &
  • "webservice.cfm"
  • ) />
  •  
  •  
  • <!--- Post the XML data to the "web service" file. --->
  • <cfhttp
  • url="#strUrl#"
  • method="post">
  •  
  • <!--- Post XML data. --->
  • <cfhttpparam
  • type="xml"
  • value="#strXmlData#"
  • />
  •  
  • </cfhttp>

As you can see, when I store the data in the XML string, I am being very careful not to include anything that is NOT XML safe. I am, however, not encoding the Base64 data. That makes me nervous, but I feel like 95% sure that Base64 is inherently XML safe since the Base64 data type is part of the XML-RPC standard as well as the XStandard web services SOAP definition. Once we have our XML string, we are simply posting it to the target URL using the CFHttpParam tag of type XML. Using this tag, the CFHttp request automatically sets the content to be of type text/xml and defines our XML string as the request data content.

Now, on the other side of the submission, we have our web service, which takes the XML post, extracts the Base64 data, converts it back to Binary, and writes it to the file system:

  • <!--- Grab the content from the request data post. --->
  • <cfset strContent = Trim( GetHttpRequestData().Content ) />
  •  
  • <!--- Check to see if the content is a valid XML document. --->
  • <cfif IsXml( strContent )>
  •  
  • <!---
  • Parse the XML data into a ColdFusion Xml
  • Document Object.
  • --->
  • <cfset xmlPost = XmlParse( strContent ) />
  •  
  • <!---
  • Check to see if we have all the XML nodes that we
  • need in order to save the file. For our naming
  • purposes, all we need is the file extension and
  • the base64 data.
  • --->
  • <cfif (
  • StructKeyExists( xmlPost.file, "ext" ) AND
  • StructKeyExists( xmlPost.file, "base64" )
  • )>
  •  
  • <!---
  • ASSERT: We have the nodes that we need, and for
  • this demo, we are just going to assume that the
  • data values are valid.
  • --->
  •  
  • <!---
  • Create the file name for the target file. We are
  • going to use the passed in extension and a UUID.
  • Make sure that file name points to the executing
  • script directory (ExpandPath()).
  • --->
  • <cfset strFileName = ExpandPath(
  • CreateUUID() & "." &
  • xmlPost.file.ext.XmlText
  • ) />
  •  
  • <!---
  • Grab the base64 file data and convert it to
  • binary data.
  • --->
  • <cfset objBinaryData = ToBinary(
  • xmlPost.file.base64.XmlText
  • ) />
  •  
  •  
  • <!--- Write the binary file data to disk. --->
  • <cffile
  • action="write"
  • file="#strFileName#"
  • output="#objBinaryData#"
  • />
  •  
  • </cfif>
  •  
  • </cfif>

I have not included any real error handling or fail handlers as this is just a demo in a controlled environment. If this were a real web service, I am sure you'd want to have CFTry / CFCatch blocks as well as a return value to indicate success or error messages. A lot of that depends on the type of standards you are using (ex. XML-RPC has a very defined way to return errors).

There's not a whole lot going on here. Really, it's just about knowing how to use and leverage ColdFusion's ToBase64() and ToBinary() methods. If you want to see any more of this, my ColdFusion XStandard Web Services project provides file uploading using a SOAP version of this idea.




Reader Comments

Roe
Jan 4, 2008 at 11:20 AM // reply »
10 Comments

As always Ben you don't disappoint. Thanks for the info on these functions.


Jan 4, 2008 at 11:23 AM // reply »
11,238 Comments

@Paul,

My pleasure! Let me know if you have any follow-up questions.


Jan 4, 2008 at 11:25 AM // reply »
5 Comments

As a suggestion, you may want to wrap the base64 encoded data in a CDATA block since it's being transmitted in an XML format. Base64 is a great encoding for passing data around over HTTP, but to avoid the stress of the XML parser getting its hands on your data, a CDATA block is always useful.

BTW - Thanks for a great site. I refer a lot of people using CF to your site for advice and tutorials. Keep up the great work!


Jan 4, 2008 at 11:53 AM // reply »
11,238 Comments

@Brian,

Good suggestion, thanks. And also, thanks for the kind words about the blog :)


Jan 4, 2008 at 1:45 PM // reply »
132 Comments

Base64 uses A-Z a-z 0-9 + / and =. There's nothing in there that could invalidate the XML. So yes, you could use CDATA blocks, but it's not needed.

Hopefully that gives you the other 5% Ben. :)

http://en.wikipedia.org/wiki/Base64


Jan 4, 2008 at 1:46 PM // reply »
11,238 Comments

@Elliott,

Perfect! Thanks. I kept trying to find the definition, but all I ever found was that it uses "printable characters", which did not instill 100% confidence.


Jul 21, 2009 at 4:36 PM // reply »
1 Comments

I think the ToBase64() function is deprecated.
You can use:
BinaryEncode(objBinaryData,"Base64");


Jul 21, 2009 at 4:49 PM // reply »
11,238 Comments

@Bilal,

From the documentation it looks like you are absolutely correct. Thanks for pointing that out.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
May 22, 2013 at 11:07 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
Could your problem be that "users.id" is actually an ARRAY, not a single value? Perhaps try it again with "users.id[1]" (I only have CF8 here at work). ... read »
May 22, 2013 at 7:52 AM
Nested Views, Routing, And Deep Linking With AngularJS
Hi, Just a quick thank you. As it happens, for my own purposes, the pending ui-router work being done in native angular is likely the one I'll adopt, but your exploration, code and documentation of ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools