tag:blogger.com,1999:blog-58246603139670210402024-03-05T10:35:24.112-08:00cfSearching.. in the middle of the nightUnknownnoreply@blogger.comBlogger250125tag:blogger.com,1999:blog-5824660313967021040.post-39079381662180120502011-01-24T15:15:00.000-08:002011-01-24T15:15:50.292-08:00Small Bug with SpreadSheetCreateSheetI was running some comparisons with CF9's spreadsheet functions and noticed a small bug with <span style="font-family: "Courier New",Courier,monospace;">SpreadSheetCreateSheet()</span>. When you omit the sheet name argument, CF automatically generates the sheet's name for you, using the standard naming convention <span style="font-family: "Courier New",Courier,monospace;">Sheet1</span>, <span style="font-family: "Courier New",Courier,monospace;">Sheet2</span>, etcetera). But apparently CF, or more accurately POI, does not verify the new name is not already in use. <br />
<br />
<span id="fullpost"> If you run the code below, it creates two sheets <i>both </i>named <span style="font-family: "Courier New",Courier,monospace;">Sheet1</span>. Obviously if you saved the file to disk and tried to open it, Excel would complain about the duplicate sheet names. It is possible it may also cause problems with other spreadsheet functions that rely on sheet name, rather than index. <br />
<br />
<pre class="xhtml" name="code"><cfset sheet = SpreadSheetNew() />
<cfset SpreadSheetCreateSheet(sheet) />
<cfset info = SpreadSheetInfo(sheet)/>
<cfdump var="#info#" label="Duplicate sheet names" />
</pre><br />
Now I suspect most people prefer to provide their own (more descriptive) sheet names, in which case the issue does not apply. But in case you do choose to use this feature, just be aware of this "gotcha". <br />
</span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-51618903287666461772011-01-09T21:04:00.000-08:002011-01-09T21:04:06.604-08:00Converting Relative to Absolute URL'sThe very first time I used the ExpandPath() function I remember wondering why there was not a similar function for expanding url's. At some point later, I did sit down and write a very rudimentary ExpandURL function. I have long since forgotten the code. But I do have a vague recollection of an ugly series of strings functions. Well the other day I came across <a href="http://stackoverflow.com/questions/1389184/building-an-absolute-url-from-a-relative-url-in-java-servlet/1389468#1389468">a tip on stackoverflow.com</a> that would have made it much easier: <a href="http://download.oracle.com/javase/6/docs/api/java/net/URL.html">java.net.URL</a>. <br />
<br />
<span id="fullpost"><br />
You simply pass in an object representing the base url (ie <span style="font-family: "Courier New",Courier,monospace;">http://www.mysite.com/blog/archive/</span>) and a string representing the relative path (ie <span style="font-family: "Courier New",Courier,monospace;">"../../somePage.cfm"</span>). The java.net.URL class normalizes the paths and returns an absolute url. In this example it would be: <span style="font-family: "Courier New",Courier,monospace;">http://www.mysite.com/somePage.cfm</span>. With a little effort it could be used to create a custom ExpandURL() function. <br />
<br />
<pre class="xhtml" name="code"><!---
... or using the base url of current request
<cfset theBaseURL = getPageContext().getRequest().getRequestURL()>
--->
<cfset theBaseURL = "http://www.mysite.com/blog/archive/" />
<cfset theRelativeURL = "../../somePage.cfm" />
<cfset baseURL = createObject("java", "java.net.URL").init( theBaseURL ) />
<cfset absURL = createObject("java", "java.net.URL").init( baseURL, theRelativeURL ) />
<cfoutput>
<p>theBaseURL = #theBaseURL#</p>
<p>theRelativeURL = #theRelativeURL#</p>
<p>absURL = #absURL.toString()#</p>
</cfoutput>
</pre><br />
Now obviously I am glossing over a <i>lot</i>. There are definitely nuances to be aware of, but you can find a complete description of how the paths are resolved in the <a href="http://download.oracle.com/javase/6/docs/api/java/net/URL.html">java api</a> (and referenced specifications for url's and uri's). This method is not perfect, but overall it is a lot more robust and elegant than using string functions.<br />
<br />
</span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-39629603349316321932011-01-08T21:36:00.000-08:002011-01-08T21:45:05.682-08:00CF9.0.1 - Small Quirk: New XLS Spreadsheets are missing CreationDate propertyTesting the new <a href="http://www.andyjarrett.co.uk/blog/index.cfm/2011/1/2/cfspreadsheet-for-Railo">cfspreadsheet extension for Railo</a> reminded me of an ACF spreadsheet quirk I saw in one of the forums a few weeks back. For some reason, the creation date property is not populated for new binary spreadsheets (ie *.xls). Nor is that property accessible via the <span style="font-family: "Courier New",Courier,monospace;">SpreadsheetAddInfo </span>function. Granted it is just a metadata value. So it is hardly a major issue. But the omission does seem a bit odd. Especially since ACF does set the creation date for new .xlsx files. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEickYxe5sH7R5qTC3mH7kueDzRTyUJFm6MQ78F2wIRGX5_a06lfN78XxJyjVIECf7en028wSEaQdzI5s3dTZ2Ul_NpI2Cy0Avkrf-q5yLQeupeWDOv7js-YlEZ8v9AbueRppDEJ4QdERIY/s1600/MissingCreateDate.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="137" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEickYxe5sH7R5qTC3mH7kueDzRTyUJFm6MQ78F2wIRGX5_a06lfN78XxJyjVIECf7en028wSEaQdzI5s3dTZ2Ul_NpI2Cy0Avkrf-q5yLQeupeWDOv7js-YlEZ8v9AbueRppDEJ4QdERIY/s320/MissingCreateDate.png" /></a></div><br />
<br />
<span id="fullpost"><br />
It is easy enough to that property on an <i>existing </i>spreadsheets. Just read in the file. Grab the SummaryInformation and set the property by invoking <span style="font-family: "Courier New",Courier,monospace;">setCreateDateTime()</span>. SummaryInformation is basically the same core document properties returned by the <span style="font-family: "Courier New",Courier,monospace;">SpreadsheetInfo </span>function. Unfortunately, CF does not add SummaryInformation for <i>new </i>spreadsheets until they are saved to disk. So if you tried the code below on a new spreadsheet object it would fail. <br />
<br />
<pre class="xhtml" name="code"><cfset sampleFile = ExpandPath("newSpreadsheet.xls") />
<cfset sheet = SpreadsheetRead( sampleFile ) />
<cfset wb = sheet.getWorkBook() />
<cfset wb.getSummaryInformation().setCreateDateTime( now() ) />
<cfset spreadsheetWrite( sheet, sampleFile, true ) />
</pre><br />
As it turns out there is an easy way to add the creation date property under CF9.0.1, albeit undocumented. CF9.0.1 includes a slightly newer version of POI (3.6-beta1). The newer version includes a new convenience method called <span style="font-family: "Courier New",Courier,monospace;">createInformationProperties()</span>. When used on a new workbook it creates and initializes the missing summary properties. Even before the new file is written to disk. Once the properties are intialized, you can easily set the creation date.<br />
<br />
<pre class="xhtml" name="code"><cfset sheet = SpreadsheetNew()>
<cfset spreadsheetSetCellValue( sheet, "foo", 1, 1 )>
<!--- get underlying workbook object --->
<cfset wb = sheet.getWorkBook()>
<!--- initialize the summary information and set the creation date --->
<cfset wb.createInformationProperties()>
<cfset wb.getSummaryInformation().setCreateDateTime( now() ) />
<!--- save the spreadsheet to disk --->
<cfset spreadsheetWrite( sheet, ExpandPath("newSpreadsheet.xls"), true )>
</pre></span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-30904187983556911962011-01-08T20:25:00.000-08:002011-01-08T21:37:48.539-08:00Running ACF Database Examples on RailoI was excited to read there is an alpha <a href="http://www.andyjarrett.co.uk/blog/index.cfm/2011/1/4/cfspreadsheet-for-Railo-update">cfspreadsheet extension available for Railo</a> (Written by <a href="http://www.andyjarrett.co.uk/">Andy Jarrett</a> as a fork of the CFPOI project by <a href="http://www.mattwoodward.com/">Matt Woodward</a>.) As a user of both ACF and Railo I was curious how the two versions of the tag compared. Being lazy, I decided to use the <a href="http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec17cba-7f87.html">cfspreadsheet examples</a> from the ACF documentation for some basic tests. While they are convenient, they naturally make use of sample databases included only with ACF. But it occurred to me, why not just copy the sample databases into Railo and set up a datasource? Then I could easily use the examples with both engines. <br />
<br />
<span id="fullpost"> So first I located the <span style="font-family: "Courier New",Courier,monospace;">cfcodexamples</span> database and copied the entire directory beneath my Railo root. <br />
<br />
<blockquote><i>My Local Settings:</i><br />
<div style="font-family: "Courier New",Courier,monospace;"><b>ACF</b>: c:/dev/ColdFusion9/db/cfcodexamples/<br />
<b>Railo</b>: c:/dev/railo321/db/cfcodeexamples/</div></blockquote><br />
Next I created the datasource. A quick search turned up these handy instructions for configuring an <a href="http://www.blogger.com/">Apache Derby datasource on Railo</a>, posted by <a href="http://web-rat.com/blog/">Todd Rafferty</a>. <br />
<br />
1) First copy the derby.jar into {railo_server}\lib and restart Railo <br />
<blockquote><i>My Local Settings:</i> <br />
<span style="font-family: "Courier New",Courier,monospace;">c:/dev/railo321/lib/derby.jar</span></blockquote><br />
2) Create a new "Other" datasource in Railo. (As this was just for testing, I accepted all the default settings)<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-XRIc3JqZKkFG7cUw7BdrI4UujdxK1kFMElL64cvksnKy6SntyOGJHNyfwyQHEo_DavXN_0ySLxk45E7bKUnIC7757qp2AmC9jUNkoHgkjNVbaFzomgfKHG4JYrH9l6afLA-GfcYCyRk/s1600/railo_derby_dsn.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-XRIc3JqZKkFG7cUw7BdrI4UujdxK1kFMElL64cvksnKy6SntyOGJHNyfwyQHEo_DavXN_0ySLxk45E7bKUnIC7757qp2AmC9jUNkoHgkjNVbaFzomgfKHG4JYrH9l6afLA-GfcYCyRk/s320/railo_derby_dsn.png" width="320" /></a></div><br />
<blockquote><i>My Local Settings:</i><br />
<div style="font-family: "Courier New",Courier,monospace;"><b>Name</b>: cfdocexamples</div><div style="font-family: "Courier New",Courier,monospace;"><b>Class</b>: org.apache.derby.jdbc.EmbeddedDriver </div><div style="font-family: "Courier New",Courier,monospace;"><b>Dsn</b>: jdbc:derby:<span style="color: #0b5394;">db/cfdocexamples</span>;create=true</div><br />
Note: The database path in the dsn is relative to the application root. I.e.<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"><b>Root</b>: c:/dev/railo321/</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> <b>Database</b>: c:/dev/railo321/db/cfcodexamples</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> <b>Relative Path</b>: <span style="color: #0b5394;">db/cfdocexamples</span></span><br />
</blockquote><br />
Once I verified the datasource. I was able to run the same cfspreadsheet examples under my Railo install too. Now obviously there <i>are</i> differences between the two engines. So it would be silly to expect every tag example to work exactly the same. But I thought this was an easy way to run some quick comparisons between the two cfspreadsheet tags. </span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-73597056191572426642010-10-07T22:18:00.000-07:002010-10-07T22:38:26.896-07:00ColdFusion 9.0.1: Expand / Collapse All Groups in a CFGridBeing late to the game, I just read about CF9's grouping feature with CFGrids. While it is a useful feature, I immediately noticed there was no option to expand or collapse all groups. After perusing the Ext forums, I discovered methods for expanding or <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.grid.GroupingView&member=collapseAllGroups">collapsing all groups</a> in the underlying GridView class. With the help of ajaxOnLoad(), I was able to create a grouped CFGrid, but this time with all groups collapsed by default.<br />
<br />
<span id="fullpost"><br />
<pre name="code" class="xhtml"><script type="text/javascript">
function collapseGrid() {
var grid = ColdFusion.Grid.getGridObject('MyGrid');
grid.getView().collapseAllGroups();
}
</script>
... create the grouped cfgrid ...
<cfset ajaxOnLoad("collapseGrid")>
</pre><br />
Giddy with my small success, I decided to try creating a button that would Expand/Collapse all groups. Fortunately <a href="http://www.danvega.org/blog/index.cfm/2010/7/29/Creating-a-grid-toolbar-in-ColdFusion-901">a great entry on Dan Vega's blog</a> saved me from hurting myself with my rudimentary Ext skills. In no time at all, I had a new toolbar with a button. <br />
<br />
Now since I wanted to pass the grid id into the handler function, I used createCallback() to bind the grid id as a function argument. Voila! A fully functional (albeit plain) toolbar button that toggles the expansion state of all groups. <br />
<br />
If you are interested in styling the button, check out <a href="http://www.danvega.org/blog/index.cfm/2008/3/5/ColdFusion-8-Grid-Custom-Toolbars">ColdFusion 8 Grid Custom Toolbars</a> for an example.<br />
<br />
<b>Full Code</b> (Requires CF 9.0.1)<br />
<pre name="code" class="xhtml"><!--- sample data for grid --->
<cfset qGridData = queryNew("ID,Title,Area", "integer,varchar,varchar") />
<cfloop from="1" to="50" index="r">
<cfset row = queryAddRow(qGridData, 1)>
<cfset qGridData.ID[row] = r>
<cfset qGridData.Title[row] = "Title "& numberFormat(r, "000")>
<cfset qGridData.Area[row] = ceiling(r /10)>
</cfloop>
<html>
<head>
<script type="text/javascript">
function init(){
// create a toolbar
var tbar = ColdFusion.Grid.getTopToolbar('MyGrid');
tbar.addButton({xtype: 'tbfill'});
// add a button that expands/collapses all groups
tbar.addButton({ text: "Expand/Collapse All",
tooltip: "Toggles the expansion state of all groups",
handler: toggleGridGroups.createCallback('MyGrid')
});
//show the toolbar
ColdFusion.Grid.showTopToolbar('MyGrid');
//collapse all groups by default
var grid = ColdFusion.Grid.getGridObject('MyGrid');
grid.getView().collapseAllGroups();
}
function toggleGridGroups(gridId){
var grid = ColdFusion.Grid.getGridObject(gridId);
grid.getView().toggleAllGroups();
}
</script>
</head>
<body>
<cfform format="html">
<cfgrid name="MyGrid" format="html" query="qGridData" groupField="Area" width="500">
<cfgridcolumn name="Title" header="Title" />
<cfgridcolumn name="Area" header="Title Area" />
</cfgrid>
</cfform>
<cfset ajaxOnLoad("init")>
</body>
</html>
</pre></span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-29524277065119697632010-09-19T01:26:00.000-07:002010-09-22T19:21:32.156-07:00CFHTTP + FILE + POST (Bug Byte ?)A few years ago, I saw a post on the adobe forums about <a href="http://forums.adobe.com/message/860303">a strange problem with cfhttp</a>. Posting a simple .zip file with <cfhttp> somehow ended up corrupting the file. So the resulting archive file could not be read by some tools, in particular <cfzip>. <br />
<br />
<span id="fullpost"> <br />
<pre class="xhtml" name="code"><!---
With CF8 this code fails with the error:
Unsupported File Format
Ensure that the file is a valid zip file and it is accessible.
--->
<cfif structKeyExists(FORM, "myFile")>
<cffile action="upload" destination="#ExpandPath('.')#" nameconflict="overwrite" />
<cfzip action="list" file="#cffile.serverDirectory#/#cffile.serverFile#" name="result" />
<cfdump var="#result#" label="Zip File Contents" />
<cfelse>
<cfhttp url="http://127.0.0.1/cfhttpTest.cfm" method="post">
<cfhttpparam name="myFile" type="file" file="c:/test/testFile.zip" mimetype="application/zip" />
</cfhttp>
<cfoutput>#cfhttp.fileContent#</cfoutput>
</cfif>
</pre><br />
<b>The Byte</b><br />
So I decided to have a look and noticed something strange about the received file. It always seemed to be two (2) bytes bigger than the original. After a bit more experimentation, I discovered the problem disappears if you add a <span style="font-family: "Courier New",Courier,monospace;">formfield</span> (any <span style="font-family: inherit;">formfield</span>) directly <i>after</i> the file. Placing it before the file does <u>not</u> work.<br />
<br />
<pre class="xhtml" name="code"><!---
This DOES work
--->
<cfif structKeyExists(FORM, "myFile")>
<cffile action="upload" destination="#ExpandPath('.')#" nameconflict="overwrite" />
<cfzip action="list" file="#cffile.serverDirectory#/#cffile.serverFile#" name="result" />
<cfdump var="#result#" label="Zip File Contents" />
<cfelse>
<cfhttp url="http://127.0.0.1/cfhttpTest.cfm" method="post">
<cfhttpparam name="myFile" type="file" file="c:/test/testFile.zip" mimetype="application/zip" />
<cfhttpparam name="BecauseTheFileIsCorruptedWithoutThisField" type="formfield" value="" />
</cfhttp>
<cfoutput>#cfhttp.fileContent#</cfoutput>
</cfif>
</pre><br />
Now I just assumed it was an issue with cfhttp. As there was a work-around, I did not really explore it further. But a recent question on stackoverflow.com prompted me to revisit the issue and take a closer look. A poster named <a href="http://stackoverflow.com/users/18511/kip%20">Kip</a> mentioned something important I had missed the first time around. That extra two (2) bytes was a newline! So armed with that key piece of information, I decided to use <a href="http://www.fiddler2.com/fiddler2/">Fiddler</a> to see what was happening within the cfhttp request. <br />
<br />
<b>The Pest</b><br />
If you are unfamiliar with multipart submissions, the w3c describes them as a "<a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2">.. message contain(ing) a series of parts, each representing a successful control</a>." In loose terms, a successful control is just a form field. (There is a little more to it than that. But the details are not relevant to this particular case.) When a multipart form is submitted, the names and values of the various fields are submitted as parts, delineated by <a href="http://en.wikipedia.org/wiki/MIME#Multipart_messages">boundary markers</a>. <br />
<br />
Now in this case, there is only one (1) form field. But as you can see from the image below, the information is nested within boundary markers. Obviously the request contents are separated by a newline, at various points. But notice there is an <i>extra </i>newline just before the closing boundary marker? That is the extra two (2) bytes. Given that <cfhttp> is responsible for generating the request content, and the separating newlines, it certainly seemed like a problem <cfhttp> <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcYbeiDXWNzoDLr6KIeupTNGbOKmEWAUYiGcsIhg_KGMj0alx7-kt2MtfPz8q0cuC3rjk28sn2Y-UmHzCPFJmJ14oAhYlQj7OtxbzHs_nB8iLCBZ0x9P3_eVVvEDsxyucmDOqJqLkVMZw/s1600/cfhttp_rawContent.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="137" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcYbeiDXWNzoDLr6KIeupTNGbOKmEWAUYiGcsIhg_KGMj0alx7-kt2MtfPz8q0cuC3rjk28sn2Y-UmHzCPFJmJ14oAhYlQj7OtxbzHs_nB8iLCBZ0x9P3_eVVvEDsxyucmDOqJqLkVMZw/s400/cfhttp_rawContent.png" width="400" /></a></div><br />
<br />
Next I submitted the same file manually with a regular <form> post. The results from Fiddler showed there was no extra newline in the content.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bmh0otlfl6LMXDORXt1qxMSvCJPgd6az3njvUM9adVgutbZFfTM23YUfZU1nCNXJ-Qib7T1a7ySt1B6fxcvXXDYsyiuYcWRBkLug5K68kl2a2v7vPexcaR89toZO8Y_qQn4S2Xvab08/s1600/cfhttp_regularFormPost.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bmh0otlfl6LMXDORXt1qxMSvCJPgd6az3njvUM9adVgutbZFfTM23YUfZU1nCNXJ-Qib7T1a7ySt1B6fxcvXXDYsyiuYcWRBkLug5K68kl2a2v7vPexcaR89toZO8Y_qQn4S2Xvab08/s400/cfhttp_regularFormPost.png" width="400" /></a></div><br />
So I went back and tested the extra <span style="font-family: "Courier New",Courier,monospace;">formfield </span>hack and surprise, surprise.. that pesky newline was gone.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikcujANpMGr-1B1QFxgAWD7ntd8dFaOsxDu5vJ8QQatLD_3qjhVv1hD20kg_Ij-MVauH-stbTFqdh-ELVaqK_-Lfy-urXeuhgdwD9CoiClUh8Pn8XNER48yoJLxc6Ps1BXsEbV9f6_wT8/s1600/cfhttp_nomoreNewLine.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikcujANpMGr-1B1QFxgAWD7ntd8dFaOsxDu5vJ8QQatLD_3qjhVv1hD20kg_Ij-MVauH-stbTFqdh-ELVaqK_-Lfy-urXeuhgdwD9CoiClUh8Pn8XNER48yoJLxc6Ps1BXsEbV9f6_wT8/s400/cfhttp_nomoreNewLine.png" width="400" /></a></div><br />
Another poster, <a href="http://stackoverflow.com/users/56604/sergii">Sergii</a>, mentioned this issue does not occur with Railo 3.1.2. So I tested the same code under Railo. Sure enough the resulting archive was fine, no corruption. Fiddler confirmed there were no pesky newline problems when using Railo's <cfhttp>. That clinched it for me. I would say this is an ACF bug with <cfhttp> in CF8 and CF9. So if you ever experience something similar, now you know why.<br />
<br />
<i>(Note: I do not know if this issue applies to 9.0.1) </i><br />
</span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-15672132355104516972010-09-07T22:52:00.000-07:002010-09-08T09:11:43.556-07:00My First Look at Diffie-Hellman (Merkle) Key Exchange - Part 2<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP-4hWEnNemuiHWQH8pterfiSzks0SMoa6Iuwkvt9SJLH-ddEZhFqroj0NZ84VJYeVHFWnr-Kq6jM6Hxe93DcuPsgWMqBY4xYYiBFztg7_xT3l96Cr8bxavx2bh1iVXknzKATM5p8z6lo/s1600/AlicesKeyValues.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"></a></div>After working through <a href="http://cfsearching.blogspot.com/2010/09/my-first-look-at-diffie-hellman-merkle.html">the basic formulas in Part 1</a>, I felt I had a decent grasp of the overall process. So I decided to dive into a java example. From what I have read, there are different implementations. So I looked over a few java examples before choosing what I felt was the simplest: the one from the <a href="http://download.oracle.com/javase/1.4.2/docs/guide/security/jce/JCERefGuide.html#AppF">(Sun) Java Cryptography Extension (JCE) Reference Guide</a>. Make no mistake, there is definitely a <b>lot</b> more involved in establishing secure exchanges than is covered in the example. But as with most things, understanding the concepts and what the code is doing is a good start.<br />
<br />
<span id="fullpost"><br />
So when I first looked at the java code, I was a bit .. flummoxed. I honestly did not recognize much of anything, other than "DH". I suppose I naively expected I might see objects initialized with secret values, calculations of prime numbers and primitive roots. Ye-ah. Needless to say, I did not find anything like that. At least not in the basic "intro" example. <br />
<br />
The more I thought about it, I realized how silly that would be. Remember I mentioned typical exchanges involve really, really big numbers? Well imagine having to calculate really large prime numbers or primitive roots (on your own) in order to use the library? Not fun. After reviewing the code more closely, I realized the SunJCE does indeed provide a class that does the grunt work for you.<br />
<br />
First an <a href="http://download.oracle.com/javase/6/docs/api/java/security/AlgorithmParameterGenerator.html">AlgorithmParameterGenerator</a> is used to generate the initial parameters. In other words the <span style="font-family: "Courier New",Courier,monospace;">prime number</span> and <span style="font-family: "Courier New",Courier,monospace;">primitive root </span>agreed upon by Alice and Bob. In this example it is initialized with a key size 512 bits. A real exchange would probably use a larger key like 1024 bits. <br />
<br />
As you can imagine, generating the values is an expensive operation and may take a few seconds. But when finished, the generator will return a <a href="http://download.oracle.com/javase/6/docs/api/javax/crypto/spec/DHParameterSpec.html">DHParameterSpec</a> object. It is simply an object containing the settings used for the exchange: prime number (p), primitive root (g) and key size (l). To view the values, use the methods <span style="font-family: "Courier New",Courier,monospace;">getG()</span>, <span style="font-family: "Courier New",Courier,monospace;">getP() </span>and <span style="font-family: "Courier New",Courier,monospace;">getL()</span>.<br />
<br />
<pre class="xhtml" name="code"><cfset generator = createObject("java", "java.security.AlgorithmParameterGenerator").getInstance("DH") />
<!--- Intialize the generator to create a 512 bit key pair (Testing only) --->
<!--- This is an expensive operation and may take a while --->
<cfset generator.init( javacast("int", 512) ) />
<cfset params = generator.generateParameters() />
<!--- Convert the parameters to the right type of object --->
<cfset DHParameterSpec = createObject("java", "javax.crypto.spec.DHParameterSpec")/>
<cfset parameterSpec = params.getParameterSpec(DHParameterSpec.getClass()) />
</pre><br />
Next the code passes those parameters into a <a href="http://download.oracle.com/javase/6/docs/api/java/security/KeyPairGenerator.html">KeyPairGenerator </a>. The generator returns a <a href="http://download.oracle.com/javase/6/docs/api/java/security/KeyPair.html">KeyPair</a>object which contains Alice's private and public keys. While so far things may not seem very familiar, the generator is actually doing the same thing we did in <a href="http://cfsearching.blogspot.com/2010/09/my-first-look-at-diffie-hellman-merkle.html">Part 1</a>. Except it automatically selects Alice's secret number, and calculates her public number internally using the given parameters (ie prime number and primitive root).<br />
<br />
<pre class="xhtml" name="code"><cfset KeyPairGenerator = createObject("java", "java.security.KeyPairGenerator") />
<cfset alicesKeyPairGenerator = KeyPairGenerator.getInstance("DH") />
<cfset alicesKeyPairGenerator.initialize( parameterSpec ) />
<cfset alicesKeyPair = alicesKeyPairGenerator.generateKeyPair() />
</pre><br />
How do you know this? Well if you display Alice's keys, you will see several parameter names followed by a long string of hexadecimal. Those values are just very large numbers, encoded as hex. The "x" represents Alice's private number and the "y" her public number. The "p" and "g" are our prime number and primitive root values. Again, encoded as hex. <br />
<br />
Starting to seem familiar now? <br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2E3JThVG1CmWX-btOIWAnOcvbEu0F05P5AifzflcW1x0MIjz2PUf8tigPpKss6UhPgSP7fR_pQ2IO_NtfwDB4kVjR-bFkvS4m1zB2jBPq5qRzWWskdwf4GMmrWGTBFVSa5PesoVMBL8s/s1600/AlicesKeyValues.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="255" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2E3JThVG1CmWX-btOIWAnOcvbEu0F05P5AifzflcW1x0MIjz2PUf8tigPpKss6UhPgSP7fR_pQ2IO_NtfwDB4kVjR-bFkvS4m1zB2jBPq5qRzWWskdwf4GMmrWGTBFVSa5PesoVMBL8s/s400/AlicesKeyValues.png" width="400" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"></div><br />
<pre class="xhtml" name="code"><cfoutput>
<strong>Alice's Values</strong>
<pre>
Format = #alicesKeyPair.getPublic().getFormat()#
Base (g) = #alicesKeyPair.getPublic().getParams().getG()#
Prime (p) = #alicesKeyPair.getPublic().getParams().getP()#
</pre>
<pre>
#alicesKeyPair.getPrivate()#
</pre>
<pre>
#alicesKeyPair.getPublic()#
</pre>
</cfoutput>
</pre><br />
Before Alice sends Bob her public number, she creates a KeyAgreement. Basically this object will be used to complete the final step: calculating the shared key. So it is first initialized with Alice's private key. Then Alice sends her public key off to Bob, and waits to receive his in return.<br />
<br />
<pre class="xhtml" name="code"><!--- Alice creates and initializes her DH KeyAgreement object with her Private Key --->
<cfset KeyAgreement = createObject("java", "javax.crypto.KeyAgreement").getInstance("DH") />
<cfset alicesKeyAgreement = KeyAgreement.getInstance("DH") />
<cfset alicesKeyAgreement.init( alicesKeyPair.getPrivate() ) />
<!--- Alice encodes her public key, and sends it over to Bob --->
<cfset alicesPublicKeyBytes = alicesKeyPair.getPublic().getEncoded() />
</pre><br />
When Bob receives Alice's public key, it is encoded in x509 format. So it has to be decoded it first. Bob then uses that object to create his own keys. Finally, he encodes his public key and sends it back to Alice. <br />
<br />
<pre class="xhtml" name="code"><!---
Bob gets the DH parameters associated with Alice's public key.
He must use the same parameters when he generates his own key pair.
--->
<cfset dhParamSpec = publicKeyFromAlice.getParams() />
<!--- Bob creates his own DH key pair --->
<cfset KeyPairGenerator = createObject("java", "java.security.KeyPairGenerator") />
<cfset bobsKeyPairGenerator = KeyPairGenerator.getInstance("DH") />
<cfset bobsKeyPairGenerator.initialize( dhParamSpec ) />
<cfset bobsKeyPair = bobsKeyPairGenerator.generateKeyPair() />
<!--- Bob encodes his public key, and sends it over to Alice --->
<cfset bobsPublicKeyBytes = bobsKeyPair.getPublic().getEncoded() />
</pre><br />
Just like Alice's values, Bob's keys will be encoded as hex.<br />
<br />
<pre class="xhtml" name="code"><cfoutput>
<strong>Bob's Values</strong>
<pre>
Base (g) = #dhParamSpec.getG()#
Prime (p) = #dhParamSpec.getP()#
</pre>
<pre>
#bobsKeyPair.getPrivate()#
</pre>
<pre>
#bobsKeyPair.getPublic()#
</pre>
</cfoutput>
</pre><br />
Bob is now ready to calculate the shared key. So he too creates a KeyAgreement, and initializes it with his private key. Finally he plugs in Alice's public key and calculates the shared value. Now internally this class is using the same formulas as we did in <a href="http://cfsearching.blogspot.com/2010/09/my-first-look-at-diffie-hellman-merkle.html">Part 1</a>. But it is all done for you auto-magically.<br />
<br />
<pre class="xhtml" name="code"><!--- Bob creates his DH KeyAgreement and initializes it with his private key --->
<cfset bobsKeyAgreement = createObject("java", "javax.crypto.KeyAgreement").getInstance("DH") />
<cfset bobsKeyAgreement.init( bobsKeyPair.getPrivate() ) />
<cfset bobsKeyAgreement.doPhase(publicKeyFromAlice, true) />
<cfset bobsSharedSecret = bobsKeyAgreement.generateSecret() />
<cfset bobsSharedSecretAsHex = binaryEncode(bobsSharedSecret, "hex") />
</pre><br />
Once Alice receives Bob's public key, she decodes it and plugs the value into her KeyAgreement object. Then calculates the shared value.<br />
<br />
<pre class="xhtml" name="code"><cfset alicesKeyFactory = createObject("java", "java.security.KeyFactory").getInstance("DH") />
<cfset X509EncodedKeySpec = createObject("java", "java.security.spec.X509EncodedKeySpec") />
<cfset x509KeySpec = X509EncodedKeySpec.init( bobsPublicKeyBytes ) />
<cfset publicKeyFromBob = alicesKeyFactory.generatePublic(x509KeySpec) />
<cfset alicesKeyAgreement.doPhase( publicKeyFromBob, true ) />
<cfset alicesSharedSecret = alicesKeyAgreement.generateSecret() />
<cfset alicesSharedSecretAsHex = binaryEncode(alicesSharedSecret, "hex") />
</pre><br />
Thanks to the wonders of mathematics, Alice and Bob both arrive at the same shared value.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA1KY6h47kLUs36O8VVri7spqQdFtch-vShKYFh0kXeBpqfaH1OZHIg-23tPGPPAFYcrq33m0E8bKI6twmQu7TlBWvBZMsikkT0BgBPLVLJhWJhYA7dJmSbEoCYdFFOmGduEPBg0qqNtw/s1600/SharedValues.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA1KY6h47kLUs36O8VVri7spqQdFtch-vShKYFh0kXeBpqfaH1OZHIg-23tPGPPAFYcrq33m0E8bKI6twmQu7TlBWvBZMsikkT0BgBPLVLJhWJhYA7dJmSbEoCYdFFOmGduEPBg0qqNtw/s400/SharedValues.png" width="400" /></a></div><br />
<pre class="xhtml" name="code"><cfset areSecretsTheSame = alicesSharedSecretAsHex eq bobsSharedSecretAsHex />
<cfoutput>
<strong>Are Alice and Bob's secrets the same?</strong> #areSecretsTheSame#
<p class="result">alicesSharedSecretAsHex</p>
<p>#alicesSharedSecretAsHex#</p>
<p class="result">bobsSharedSecretAsHex</p>
<p>#bobsSharedSecretAsHex#</p>
</cfoutput>
</pre><br />
By itself, the shared value cannot be used for encryption. However Alice and Bob can use the shared value to generate a secret key which <b>can</b> be used for encryption. <br />
<br />
The java documentation includes a simple example using DES. Obviously outdated, but it does demonstrate that Alice and Bob can successfully can encrypt/decrypt each other's values, starting only with the shared key.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZr_ZHHWlsyo1diGdYGY8pRiY0DK62vJrMaIQHyA2zTvySRac8a5VfmXeAqeWC1FQAl6v5t-P7xlvOyw4JePvxgArxOby3CLrcpW4nUsj1AtScUsjWqM5idI_a0Kj7KufCMtrjmrdBDM4/s1600/DESResults.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZr_ZHHWlsyo1diGdYGY8pRiY0DK62vJrMaIQHyA2zTvySRac8a5VfmXeAqeWC1FQAl6v5t-P7xlvOyw4JePvxgArxOby3CLrcpW4nUsj1AtScUsjWqM5idI_a0Kj7KufCMtrjmrdBDM4/s320/DESResults.png" /></a></div><br />
<b>Bob Encrypts</b><br />
<pre class="xhtml" name="code"><!---
The previous call to bobsKeyAgreement.generateSecret resets the key
agreement object, so we call doPhase again prior to another generateSecret call
--->
<cfset bobsKeyAgreement.doPhase(publicKeyFromAlice, true) />
<cfset bobsDESKey = bobsKeyAgreement.generateSecret("DES") />
<!--- Encrypt the text with a simple encryption --->
<cfset Cipher = createObject("java", "javax.crypto.Cipher") />
<cfset bobsCipher = Cipher.getInstance("DES/ECB/NoPadding") />
<cfset bobsCipher.init( Cipher.ENCRYPT_MODE, bobsDESKey ) />
<cfset textToEncrypt = "StandAndDeliver!" />
<cfset bytesToEncrypt = CharsetDecode(textToEncrypt, "utf8") />
<cfset encryptedBytes = bobsCipher.doFinal(bytesToEncrypt) />
<cfset encryptedText = BinaryEncode(encryptedBytes, "hex") />
<!--- results --->
<strong>Bob's Values:</strong>
<cfoutput>
<p><strong>DES Key</strong> : #BinaryEncode(bobsDESKey.getEncoded(), "hex")# </p>
<p><strong>Encrypted Text</strong> : #encryptedText# </p>
</cfoutput>
</pre><br />
<b>Alice Decrypts</b><br />
<pre class="xhtml" name="code"><!---
The previous call to bobsKeyAgreement.generateSecret resets the key
agreement object, so we call doPhase again prior to another generateSecret call
--->
<cfset alicesKeyAgreement.doPhase(publicKeyFromBob, true) />
<cfset alicesDESKey = alicesKeyAgreement.generateSecret("DES") />
<cfset Cipher = createObject("java", "javax.crypto.Cipher") />
<cfset alicesCipher = Cipher.getInstance("DES/ECB/NoPadding") />
<cfset alicesCipher.init( Cipher.DECRYPT_MODE, alicesDESKey ) />
<cfset bytesToDecrypt = BinaryDecode(encryptedText, "hex") />
<cfset unencryptedBytes = alicesCipher.doFinal( bytesToDecrypt ) />
<!--- results --->
<strong>Alice's Values:</strong>
<cfoutput>
<p><strong>DES Key</strong> : #BinaryEncode(alicesDESKey.getEncoded(), "hex")# </p>
<p><strong>Unencrypted text</strong> : #CharsetEncode(unencryptedBytes, "utf8")# </p>
</cfoutput>
</pre><br />
Obviously there is a lot more to consider before entering into this type of exchange. Guarding against <a href="http://en.wikipedia.org/wiki/Man-in-the-middle_attack%20">man-in-the-middle attacks</a> is definitely one issue. So you will want to do a lot more reading first. But hopefully this entry helped de-mystify Diffie-Hellman key exchanges ... a little.<br />
<br />
As always, any comments or corrections are welcome!<br />
</span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-92152525183129648852010-09-07T15:09:00.000-07:002010-09-07T22:53:07.373-07:00My First Look at Diffie-Hellman (Merkle) Key Exchange - Part 1While I have seen references to Diffie-Hellman before, I honestly knew very little about it until this week. After seeing it mentioned on <a href="http://stackoverflow.com/questions/tagged/coldfusion">stackoverflow.com</a>, I decided to do some research. Now I am pretty sure this protocol is not available in the standard edition of Adobe ColdFusion, and contrary to popular opinion, I have my doubts about its availability in Enterprise version as well. Though admittedly, I could not find any solid references one way or the other. So I could be wrong. Anyway, since there is a plethora of implementations in the java world, I decided to explore that route. <br />
<br />
<span id="fullpost"> <br />
<b>Disclaimers</b><br />
First let me say this entry is not intended to be a "how to guide". In the world of encryption, I am most definitely a novice. But I am a curious novice. So in an effort to better understand the Diffie-Hellman (Merkle) exchange, I decided to take one of the simpler java implementations, deconstruct it, and put the results in a CF context. If you are already familiar with it, this beginner level entry will probably be one big yawn. But any corrections or clarifications are definitely welcome. Just go easy lest my brain implode.<br />
<br />
<b>What is it?</b><br />
<a href="http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange">Wikipedia</a> describes Diffie-Hellman as a key exchange protocol<br />
<br />
<blockquote>"...that allows two parties that have no prior knowledge of each other to jointly establish a shared secret key over an insecure communications channel. This key can then be used to encrypt subsequent communications using a symmetric key cipher."</blockquote><br />
My novice interpretation would be that instead of exchanging a key, two parties exchange "other", transitory, pieces of information instead. Then use those "other" pieces of information to derive the key (independently) without actually sending the key itself over an open channel.<br />
<br />
What are those "other" pieces of information? In short, they are really, really large numbers. The protocol uses several numbers in a series of simple mathematical formulas to eventually calculate the secret key. I will not go into details about <i>how</i> and <i>why </i>those numbers are selected. The wikipedia entry describes it far better than I ever could. But suffice it to say they are not arbitrary. Each has specific characteristics that directly affect both the results and the security of the exchange. So I would highly recommend you read the article. But to paraphrase the salient points (without formulas):<br />
<ol><li>Two parties (Alice and Bob) agree upon two (2) numbers to be used in their calculations (a prime number and a primitive root) <i>Note: These are public values, known by both Alice and Bob</i></li>
<li>Then Alice and Bob each pick a private number. <i>Note: Alice should not know Bob's value, and Bob should not know Alice's value.</i></li>
<li>Using their private numbers (plus the prime and primitive root) Alice and Bob each calculate a public number. They then exchange public numbers with each other. <i>Note: These are public values, known to both Alice and Bob. </i></li>
<li>After exchanging public values, Alice and Bob now have enough information to calculate the shared secret key (using another formula) <i>Note: They both arrive at the same secret key value, independently, without ever sending that value over an open channel.</i></li>
</ol><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq4SwvLLJRSTchyphenhyphenTCFppvyDUap6K6r0WiA6ZFwU31a571g5CpoS1YR_pmYte7ydZPUu8MYbgLp0l4QRBf4YlBN1i5Rm8LkFzAVrpo9yMNmJ0Q2TN8H4CrHOOzn9cl8juyqazjsYzrZy8I/s1600/DiffieHellman_Overview.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq4SwvLLJRSTchyphenhyphenTCFppvyDUap6K6r0WiA6ZFwU31a571g5CpoS1YR_pmYte7ydZPUu8MYbgLp0l4QRBf4YlBN1i5Rm8LkFzAVrpo9yMNmJ0Q2TN8H4CrHOOzn9cl8juyqazjsYzrZy8I/s320/DiffieHellman_Overview.png" /></a></div><br />
<br />
<b>Math 101</b><br />
Now jumping straight into a java example from here felt a bit like sending a student driver onto a five lane highway after receiving only five minutes of instruction. So I decided to test the basic formulas from the <a href="http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange#Description">wikipedia example</a> first. It gave me a better understanding of the overall process, and also provided a good basis of comparison for the java results.<br />
<br />
Now before someone corrects me, the overview below is conceptual only. When I finally did run the simple java example, the actual steps were slightly different. But overall the process was the same.<br />
<br />
(On a side note, this whole thing felt a bit like something you would read about in a spy novel. But I suppose that cannot be helped...)<br />
<br />
<b>Step 1) Preparing for the Meeting</b><br />
First, a prime number and <a href="http://homepage.smc.edu/morgan_david/vpn/assignments/assgt-primitive-roots.htm">primitive root</a> are selected. These are considered <b>public</b> values, known to both Alice and Bob. Now you may notice I am using java objects. That was necessary because the calculations involved produce very large numbers. Even using small test values like 23 and 5 the results exceeded the capacity of a basic CF integer.<br />
<br />
<pre class="xhtml" name="code"><cfset prime = createObject("java", "java.math.BigInteger").init( 23 ) />
<cfset base = createObject("java", "java.math.BigInteger").init( 5 ) />
<strong>Values known to both Alice and Bob</strong>
<cfoutput>
<p> ie prime = #prime# AND base = #base# </p>
</cfoutput>
</pre><br />
<b>Step 2) Secret Code Words </b><br />
Next, Alice and Bob each select a private number, which they do <b>not </b>share with each other. <br />
<pre class="xhtml" name="code"><cfset alicesPrivateValue = createObject("java", "java.math.BigInteger").init( 6 ) />
<cfset bobsPrivateValue = createObject("java", "java.math.BigInteger").init( 15 ) />
</pre><br />
<b>Step 3) The Public Exchange</b><br />
Alice and Bob then use their private numbers to calculate a <b>public</b> number using the formula: <span style="font-family: "Courier New",Courier,monospace;">base ^ private MOD prime</span>. They then exchange public numbers. Again, their <i><span style="color: red;"></span>private</i> numbers are never exchanged.<br />
<br />
<br />
<pre class="xhtml" name="code"><cfset alicesPublicValue = base.modPow( alicesPrivateValue, prime) />
<strong>Alice's values: </strong>
<span class="code">alicesPublicValue = base ^ alicesPrivateValue MOD prime </span>
<cfoutput>
<p>ie #alicesPublicValue# = #base# ^ #alicesPrivateValue# MOD #prime# </p>
</cfoutput>
<cfset bobsPublicValue = base.modPow( bobsPrivateValue, prime) />
<strong>Bob's values: </strong>
<div class="code">bobsPublicValue = base ^ bobsPrivateValue MOD prime </div>
<cfoutput>
<p>ie #bobsPublicValue# = #base# ^ #bobsPrivateValue# MOD #prime#</p>
</cfoutput>
</pre><br />
<b>Step 4) Finding the Key</b><br />
Alice an Bob now have enough information to derive the shared secret value, independently. Alice uses the formula: <span style="font-family: "Courier New",Courier,monospace;">alicesSharedKey = bobsPublicValue ^ alicesPrivateValue MOD prime</span> .<br />
<br />
<pre class="xhtml" name="code"><strong>Alice uses Bob's value to compute the shared key (B <sup>a</sup> MOD p)</strong>
<div class="code">alicesSharedKey = bobsPublicValue ^ alicesPrivateValue MOD prime </div>
<cfset alicesSharedKey = bobsPublicValue.modPow(alicesPrivateValue, prime ) />
<cfoutput>
<p class="result">Alice's shared key is <strong>#alicesSharedKey#</strong></p>
<p>ie #alicesSharedKey# = #bobsPublicValue# ^ #alicesPrivateValue# MOD #prime#</p>
</cfoutput>
</pre><br />
Whereas Bob uses the formula: <span style="font-family: "Courier New",Courier,monospace;">bobsSharedKey = alicesPublicValue ^ bobsPrivateValue MOD prime</span><br />
<br />
<pre class="xhtml" name="code"><strong>Bob uses Alice's value to compute the shared key (A<sup>b</sup> MOD p)</strong><br/>
<div class="code">bobsSharedKey = alicesPublicValue ^ bobsPrivateValue MOD prime </div>
<cfset bobsSharedKey = alicesPublicValue.modPow( bobsPrivateValue, prime ) />
<cfoutput>
<p class="result">Bob's shared key: #bobsSharedKey#</p>
<p>ie #bobsSharedKey# = #alicesPublicValue# ^ #bobsPrivateValue# MOD #prime#</p>
</cfoutput>
</pre><br />
If everything was done correctly, they will both calculate the <b>same</b> value (ie 2). This value can then be used to create a secret key (DES, etcetera) with which to encrypt and decrypt data. <br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-ZZCk3gqDpHVVp7VS13ThrOA9u2EC6osXPTrcuj_W7tYa-DNlg9NWAcPx7QVqDw0h2Lno2eIy_yAJ3bHWjYdxPZQ7a-zTNiTGgAHHFpBVmw5zaLKZB54IQhc6xJgKwikDUUK_aVJgYk/s1600/DiffieHellman_CFResults.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7-ZZCk3gqDpHVVp7VS13ThrOA9u2EC6osXPTrcuj_W7tYa-DNlg9NWAcPx7QVqDw0h2Lno2eIy_yAJ3bHWjYdxPZQ7a-zTNiTGgAHHFpBVmw5zaLKZB54IQhc6xJgKwikDUUK_aVJgYk/s320/DiffieHellman_CFResults.png" /></a></div><br />
<br />
<br />
<a name='more'></a><br />
<div style="text-align: center;">Next up in Part 2: <a href="http://cfsearching.blogspot.com/2010/09/my-first-look-at-diffie-hellman-merkle_07.html">Working through a java example</a>.</div></span>Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-5824660313967021040.post-11172904008485689062010-08-18T20:26:00.000-07:002010-08-20T07:36:27.149-07:00OT: Random Image from FlickrI came across this image on flickr. It made me laugh for <i>some</i> reason ...<br />
<br />
<a href="http://www.flickr.com/photos/kiuz/4486531501/" title="Typo Poster / Batman 02 by Domenico / Kiuz, on Flickr"><img src="http://farm3.static.flickr.com/2751/4486531501_aaa8bb709f_z.jpg" width="496" height="640" alt="Typo Poster / Batman 02" /></a><br />
<br />
;)Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-90044233821468251892010-05-27T22:47:00.000-07:002010-05-27T23:01:55.445-07:00CF9 Question: Do Spreadsheet Functions Support "Blank Cells"?I saw a question about spreadsheets this week that had me scratching my head. When you create a new worksheet in Excel, all of the cells are blank. So if you enter a long string in any cell, the text will overflow into the adjacent cell. (If the adjacent cell is blank.)<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7A1draZs0_gGEblPZpzqNzFYaJ2jE5GccLLrmsqjTWIq6CTXkA2CcZ8BnQ9ML-GtT4uzfyV7RsqpNF5pvJjSl33-ZZhH7A8iX64_-TQDcVvbPbxHSHPS4mD87blcJGkmOJu3ItZI2GZE/s1600/BlankCell_Excel.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7A1draZs0_gGEblPZpzqNzFYaJ2jE5GccLLrmsqjTWIq6CTXkA2CcZ8BnQ9ML-GtT4uzfyV7RsqpNF5pvJjSl33-ZZhH7A8iX64_-TQDcVvbPbxHSHPS4mD87blcJGkmOJu3ItZI2GZE/s320/BlankCell_Excel.png" /></a></div><br />
<span id="fullpost"> <br />
<div class="separator" style="clear: both; text-align: center;"></div><br />
However, if you do something similar in ColdFusion 9, the text does not overflow into the next cell. Notice how the extra text in cell <span style="font-family: "Courier New",Courier,monospace;">A1</span> is hidden, even though the adjacent cell is technically empty.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiez8PEpwbpzfPuYyUWLZhPdsruvO2BdsXYtEEIqH7WA4Iadwx_qsTqxhJtKhAY6IETypLlKq3tOkkme8hI8Gz-3gric39fTbcsafrO1xuxZxyT7fbOJnaejMGK8rrOM4eo-NFv1BUVd7Q/s1600/BlankCell_CF9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiez8PEpwbpzfPuYyUWLZhPdsruvO2BdsXYtEEIqH7WA4Iadwx_qsTqxhJtKhAY6IETypLlKq3tOkkme8hI8Gz-3gric39fTbcsafrO1xuxZxyT7fbOJnaejMGK8rrOM4eo-NFv1BUVd7Q/s320/BlankCell_CF9.png" /></a></div><br />
<br />
<pre class="xhtml" name="code"><cfscript>
cfSheet = SpreadsheetNew("foo");
SpreadsheetAddRow(cfSheet, "this text should overflow,,short text", 1);
SpreadsheetSetColumnWidth(cfSheet, 1, 15);
SpreadsheetSetColumnWidth(cfSheet, 2, 15);
SpreadsheetSetColumnWidth(cfSheet, 3, 15);
saveToFile = ExpandPath("test.xls");
SpreadsheetWrite(cfsheet, saveToFile, true);
// how about a WriteLine() function folks ... ;) ?
WriteOutput("Saved file: "& saveToFile &"<hr>");
</cfscript>
<cfheader name="Content-Disposition" value="inline; filename=#saveToFile#">
<cfcontent type="application/vnd.ms-excel" file="#saveToFile#" deleteFile="true" />
</pre><br />
<br />
In the underlying POI library there is a special cell type for blank cells: <a href="http://poi.apache.org/apidocs/org/apache/poi/ss/usermodel/Cell.html#CELL_TYPE_BLANK"><span style="font-family: "Courier New",Courier,monospace;">CELL_TYPE_BLANK</span></a>. In loose terms, it represents a raw cell that has never had a value. This is different from a cell whose value is set to an empty string. The value may be an empty string, but the cell still has a value. So it has a type of <a href="http://poi.apache.org/apidocs/org/apache/poi/ss/usermodel/Cell.html#CELL_TYPE_STRING"><span style="font-family: "Courier New",Courier,monospace;">CELL_TYPE_STRING</span></a>.<br />
<br />
If you loop over the test file created earlier, you will see all of the cells are <span style="font-family: "Courier New",Courier,monospace;">CELL_TYPE_STRING</span>. Which at least explains why the first cell's text does not overflow into the next cell.<br />
<br />
<pre class="xhtml" name="code"><cfscript>
// open the workbook
source = ExpandPath("test.xls");
input = createObject("java", "java.io.FileInputStream").init( source );
wb = createObject("java", "org.apache.poi.ss.usermodel.WorkbookFactory").create( input );
// get the first sheet
sheet = wb.getSheetAt(0);
// loop over each row in the sheet
rows = sheet.rowIterator();
while(rows.hasNext()) {
// get the current row
r = rows.next();
// loop over each cell in the sheet
cells = r.cellIterator();
while (cells.hasNext()) {
// get the current cell
c = cells.next();
// check the cell type (ingore error handling for brevity)
if (c.getCellType() == c.CELL_TYPE_BLANK) {
type = "CELL_TYPE_BLANK";
}
else if (c.getCellType() == c.CELL_TYPE_STRING) {
type = "CELL_TYPE_STRING";
}
else {
type = "other";
}
// display the position, cell type / value
// note: Adding +1 because POI indexes are 0-based
WriteOutput("["& r.getRowNum()+1 &"]["& c.getColumnIndex()+1 &"]=");
WriteOutput(type &" / "& c.toString() &"<hr>");
}
}
// clean up
input.close();
</cfscript>
</pre><br />
But this raises the question, does ColdFusion 9 support "blank" cells, and if so how do you create one? I am honestly not sure. You could work around it by tapping into the <i>undocumented </i><span style="font-family: "Courier New",Courier,monospace;">getWorkBook()</span> method of ColdFusion spreadsheet objects. It returns a reference to the underlying POI workbook. Using the workbook, you can then grab the desired rows and cells and change the cell type to <span style="font-family: "Courier New",Courier,monospace;">CELL_TYPE_BLANK</span>. <br />
<br />
<pre class="xhtml" name="code"><cfscript>
cfSheet = SpreadsheetNew("foo");
SpreadsheetAddRow(cfSheet, "this text should overflow,,short text", 1);
SpreadsheetSetColumnWidth(cfSheet, 1, 15);
SpreadsheetSetColumnWidth(cfSheet, 2, 15);
SpreadsheetSetColumnWidth(cfSheet, 3, 15);
// work around to make cell B1 a "blank" cell
// POI row/cell indexes are 0-based !!
poiSheet = cfSheet.getWorkBook().getSheet("foo");
poiCell = poiSheet.getRow( 0 ).getCell( 1 );
poiCell.setCellType( poiCell.CELL_TYPE_BLANK );
saveToFile = ExpandPath("newTest.xls");
SpreadsheetWrite(cfsheet, saveToFile, true);
WriteOutput("Saved file: "& saveToFile &"<br>");
</cfscript>
<cfheader name="Content-Disposition" value="inline; filename=#saveToFile#">
<cfcontent type="application/vnd.ms-excel" file="#saveToFile#" deleteFile="true" />
</pre><br />
However I have not found a <i>documented </i>way to do this. So does anyone know the answer to the million dollar question: does ColdFusion 9 provide a documented method for creating blank cells? My guess would be no. But I would be very happy to be proven wrong. Any spreadsheet function gurus out there?<br />
<br />
</span>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-5824660313967021040.post-32758713363893590452010-05-26T23:08:00.000-07:002010-05-26T23:31:20.787-07:00Experiment Extracting Windows Thumbnails (XP and Windows 2003 only)Windows thumbnails have existed for eons. But in all that time I never really used the windows explorer thumbnail view. Yes, pictures are nice. But invariably I found myself needing the detail view with its listing of file types, sizes and dates. <br />
<br />
Well, this week I came across an <a href="http://stackoverflow.com/questions/228304/is-there-any-c-lib-to-read-thumbnails-from-thumb-db-in-windows-folder">old stackoverflow thread</a> which mentioned <a href="http://www.petedavis.net/drupal//index.php?q=node/2">a C# tool</a> for reading and extracting images from the windows thumbnail cache (ie thumbs.db). At least on older XP and Windows 2003 systems. (Vista and later use a slightly different format.) While there are tools galore in this category, the idea of a small DLL that could be called from CF was appealing. So I decided to give it a quick whirl with CF8, under XP.<br />
<span id="fullpost"><br />
After compiling the source with Visual C# Express, I changed my explorer settings so I actually had a thumbnails file to test. Next, I created an instance of the <span style="font-family: "Courier New",Courier,monospace;">ThumbDB</span> class and initialized it by passing in the path my thumbnails database. Once initialized, I used the <span style="font-family: "Courier New",Courier,monospace;">GetThumbFiles()</span> method to grab an array of all file names within that database.<br />
<br />
<pre class="xhtml" name="code"><cfset util = createObject(".net", "ThumbDBLib.ThumbDB", "c:/test/ThumbDBLib.dll")>
<cfset util.init( "C:/docs/thumbs.db" )>
<cfset fileNames = util.GetThumbFiles()>
<cfdump var="#fileNames#" label="Top 25 Files in Thumbs.db" top="25">
</pre><br />
Next, I selected one of the file names and used the GetThumbData() method to retrieve the image bytes. I was hoping to create a CF image object from the bytes and display it in my browser. But every time I called ImageNew(), ColdFusion kept complaining about my argument type.<br />
<br />
<pre class="xhtml" name="code"><!--- this does NOT work --->
<!--- grabbing an arbitrary file from the listing for testing ...--->
<cfset thumbnailName = fileNames[1] />
<cfset bytes = util.GetThumbData( thumbnailName )>
<cfset img = ImageNew(bytes) />
</pre><br />
That is when it hit me. A <span style="font-family: "Courier New",Courier,monospace;">byte </span>in C# is not the same as <span style="font-family: "Courier New",Courier,monospace;">byte</span> in Java. Unlike Java, C# has signed and unsigned types. So where C# has two data types, <span style="font-family: "Courier New",Courier,monospace;">sbyte </span>(-128 to 127) and <span style="font-family: "Courier New",Courier,monospace;">byte </span>(0 to 255), java has only one, <span style="font-family: "Courier New",Courier,monospace;">byte </span>(range -128 to 127). So according to the <a href="http://livedocs.adobe.com/coldfusion/8/dotNet_04.html">data type conversion matrix</a>, the C# byte array was being converted to a <span style="font-family: "Courier New",Courier,monospace;">short </span>array. <br />
<br />
Thanks to a tip from <a href="http://www.blackwasp.co.uk/ConvertArray.aspx">BlackWasp.com</a>, I added a new method called <span style="font-family: "Courier New",Courier,monospace;">GetJavaThumbData()</span>, which converts the C# byte array into one that is compatible with Java. Using the new method, I was then able to display the thumbnail perfectly.<br />
<br />
<b>C# Code:</b><br />
<pre class="c-sharp" name="code">public sbyte[] GetJavaThumbData(string fileName)
{
byte[] data = GetThumbData(fileName);
if (data != null)
{
sbyte[] jvData = Array.ConvertAll<byte, sbyte>(data, delegate(byte b) { return unchecked((sbyte)b); });
return jvData;
}
return null;
}
</pre><br />
<b>ColdFusion Code:</b><br />
<pre class="xhtml" name="code"><!--- grabbing an arbitrary file from the listing for testing ...--->
<cfset thumbnailName = fileNames[1] />
<cfset bytes = util.GetJavaThumbData( thumbnailName )>
<cfset img = ImageNew( bytes )>
<cfimage action="writeToBrowser" source="#img#">
</pre><br />
It is worth noting the <span style="font-family: "Courier New",Courier,monospace;">ThumbDB</span> class also has a method named <span style="font-family: "Courier New",Courier,monospace;">GetThumbnailImage()</span>, which returns a C# image object. You can use its properties to display things like height, width, resolution. The class also has several methods for saving the image to a file. Out of curiosity, I tried several of the overloaded save() methods. But I only had success with the simplest form: Image.save(filepath). I am not sure why. Though with CF's own image functions, they are not really needed anyway. <br />
<br />
<pre class="xhtml" name="code"><cfset img = util.GetThumbnailImage( thumbnailName ) />
<cfoutput>
<strong>Thumbnail: #thumbnailName#</strong> <hr />
Height = #img.Get_Height()#
Width = #img.Get_Width()#
PixelFormat = #img.Get_PixelFormat()#
Resolution = #img.Get_HorizontalResolution()# /
#img.Get_VerticalResolution()#
</cfoutput>
</pre><br />
Supposedly the <a href="http://code.msdn.microsoft.com/WindowsAPICodePack">Windows API Code pack</a> can extract thumbnails on later systems like Windows 7. If anyone has used it, or something similar, on Windows 7, let me know. I would be interested in hearing your experiences with it.<br />
</span>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-5824660313967021040.post-46459409590091321792010-05-20T18:19:00.000-07:002010-05-24T12:49:44.227-07:00MS SQL 2005 Tip: Display Estimated Sizes of All Tables in a DatabaseToday I needed to get a quick <a href="http://msdn.microsoft.com/en-us/library/aa933068%28SQL.80%29.aspx">estimate of tables sizes</a> in an MS SQL 2005 database. So I ended up using the <a href="http://msdn.microsoft.com/en-us/library/ms188776.aspx">sp_spaceused</a> stored procedure, which displays the space used for either a database or a single object (table, etcetera).<br />
<span id="fullpost"><br />
As I was interested in a more detailed listing, I threw together a quick cursor that pulls all table names from the information_schema views, and inserts the results for each item into a table variable.<br />
<br />
<b>Updated</b>: Updated to incorporate improvements from comments <br />
<pre name="code" class="sql">USE MyDatabaseName
GO
--- suppress extraneous row counts
SET NOCOUNT ON
GO
--- Temporary table used to store results
DECLARE @spaceUsed TABLE
(
RecordID int IDENTITY(1,1),
TableName nvarchar(128),
Rows varchar(20),
Reserved varchar(20),
Data varchar(20),
Index_size varchar(20),
Unused varchar(20),
SchemaName nvarchar(128)
)
--- get the names of all tables in the current database
DEClARE @id int
DECLARE @tableName VARCHAR(260)
DECLARE @tableSchema VARCHAR(128)
DECLARE @tables CURSOR
SET @tables = CURSOR FOR SELECT TABLE_SCHEMA, TABLE_SCHEMA +'.'+ TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'Base Table'
OPEN @tables
FETCH NEXT FROM @tables INTO @TableSchema, @TableName
--- For each table ...
WHILE @@FETCH_STATUS = 0 -- lazy check
BEGIN
BEGIN TRY
--- insert the estimated size into the work table
INSERT INTO @spaceUsed (TableName, Rows, Reserved, Data, Index_Size, Unused)
EXEC sp_spaceused @TableName
--- update the schema information
UPDATE @spaceUsed
SET SchemaName = @TableSchema
WHERE RecordID = SCOPE_IDENTITY()
END TRY
BEGIN CATCH
--- ignore any errors
PRINT 'Unable to calculate space for table ['+ @TableName +'] '+ char(10) + ERROR_MESSAGE()
END CATCH
FETCH NEXT FROM @tables INTO @TableSchema, @TableName
END
CLOSE @tables
DEALLOCATE @tables
</pre><br />
Since the resulting sizes are formatted as strings (ie xxx KB), I had to run a quick update to remove the non-numeric values. Then I was able to calculate the total size for each table, and sort the results numerically. So I could get an idea of which were the largest tables in my database.<br />
<br />
It is not at all pretty. But it did get the job done. <br />
<br />
<pre name="code" class="sql">--- remove KB prefix so values can be cast to numeric type ...
UPDATE @spaceUsed
SET Reserved = REPLACE(Reserved, ' KB', '')
, Data = REPLACE(Data, ' KB', '')
, Index_size = REPLACE(Index_size, ' KB', '')
, Unused = REPLACE(Unused, ' KB', '')
, Rows = REPLACE(Rows, ' KB', '')
--- display estimates for each table (order by largest amount used)
SELECT SchemaName
, TableName
, CAST(Rows AS decimal) AS Rows
, CAST(Reserved AS decimal) AS ReservedKB
, CAST(Data AS decimal) AS DataKB
, CAST(Index_size AS decimal) AS IndexKB
, CAST(Data AS decimal) + CAST(Index_size AS decimal) AS TotalUsedKB
, CAST(Unused AS decimal) AS UnusedKB
FROM @spaceUsed
ORDER BY TotalUsedKB DESC
--- display overall estimates for database
EXEC sp_spaceused
GO
SET NOCOUNT OFF
GO
</pre><br />
If you are interested in listing the overall sizes for a series of MS SQL datasources, there is good example in an <a href="http://www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/getting-ms-sql-database-size-using-coldfusion-37">older entry</a> on John Whish's blog.<br />
<br />
<br />
</span>Unknownnoreply@blogger.com7tag:blogger.com,1999:blog-5824660313967021040.post-32764317655897950542010-05-18T20:07:00.000-07:002010-05-20T13:43:00.217-07:00Please do not use the database examples for CFGRID!Looking at the documentation for CFGRID today, I was reminded of something that really bugs me. If you look over some of the <a href="http://livedocs.adobe.com/coldfusion/8/htmldocs/ajaxui_5.html#1126812">CFGRID examples</a> they use dynamic sql, which is totally unsafe and vulnerable to sql injection. If forum posts are any indication, that same code is being used in live applications (unfortunately). So in case you were tempted, do not be lazy and copy straight out of the examples. If you want safe sql, you have to put in some work. Validate, scrub, lather, rinse, repeat.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-33875478636831600592010-05-05T03:41:00.000-07:002010-05-06T20:19:05.541-07:00PING with ColdFusion and .NETI was curious if were possible to ping a server using something other than the usual cfexecute and ping.exe. method. My first thought was surely java can do it. Feeling quite confident, I found a neat method in the <span style="font-family: "Courier New",Courier,monospace;">InetAddress</span> class called <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/InetAddress.html#isReachable%28int%29"><span style="font-family: "Courier New",Courier,monospace;">isReachable()</span></a> that seemed to fit the bill. I happily wrote up a small test, ran the code, and quickly realized something was wrong. <br />
<br />
<span id="fullpost"> <br />
<pre class="xhtml" name="code"><cfscript>
InetAddress = createObject("java", "java.net.InetAddress");
address = InetAddress.getByName("www.google.com");
isReachable = address.isReachable( 500 ); //timeout
WriteOutput( address.getHostName() &" ["& address.getHostAddress() &"] "&
"IsReachable = "& isReachable
);
</cfscript>
</pre><br />
<br />
Apparently, google was unreachable. At least to java. Of course it is technically possible, yes. Probable ... no. Especially when a command line ping reported google was alive and kicking just moments prior to that. <br />
<br />
After re-checking the API, I realized <span style="font-family: "Courier New",Courier,monospace;">isReachable()</span> might not be as good as it sounded. Aside from the usual disclaimers about firewalls and blocking, the API states:<br />
<br />
<blockquote style="font-family: "Courier New",Courier,monospace;">A typical implementation will use ICMP ECHO REQUESTs if the privilege can be obtained, otherwise it will try to establish a TCP connection on port 7 (Echo) of the destination host. </blockquote><br />
Since I had no idea what a <i>typical</i> implementation was, I fired up <a href="http://www.wireshark.org/">Wireshark</a> to see what was really happening. Sure enough, Wireshark showed isReachable() was using a TCP connection, not ICMP. Since TCP Echo is often disabled, that explained why isReachable() returned false.<br />
<br />
<div style="text-align: center;"><span style="font-family: "Courier New",Courier,monospace;"> Command Line Ping</span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4t4vDruyf3fd__mtcWW3KoMi9mHGQamS8Cd37V_r5yzxdJP0F2N_kWO8h9MTq0QIT2UN5E9o3OKVdfXripDTIJO2eY6r3BiviGcRWoLZyYyhgxmTYql1UpH9MbDU5mjxiPxinWmgr1YM/s1600/commandLinePing.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4t4vDruyf3fd__mtcWW3KoMi9mHGQamS8Cd37V_r5yzxdJP0F2N_kWO8h9MTq0QIT2UN5E9o3OKVdfXripDTIJO2eY6r3BiviGcRWoLZyYyhgxmTYql1UpH9MbDU5mjxiPxinWmgr1YM/s320/commandLinePing.png" /></a></div><br />
<div style="text-align: center;"><span style="font-family: "Courier New",Courier,monospace;">InetAddress.isReachable(..)</span> </div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUynLG3NpJTc7v7K4cMOFBu8_LIUJzQvwvcMkRgXKwCQWEYbN9U_bVOPsEtvkxi5_W8YNJR-pFuZnwZgVVMyunn2hJ4WbIo245o1fE0x7GqMXTfzsilLdM0GJyZz3P1XH6oyvMIRkqZOU/s1600/javaIsReachable.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUynLG3NpJTc7v7K4cMOFBu8_LIUJzQvwvcMkRgXKwCQWEYbN9U_bVOPsEtvkxi5_W8YNJR-pFuZnwZgVVMyunn2hJ4WbIo245o1fE0x7GqMXTfzsilLdM0GJyZz3P1XH6oyvMIRkqZOU/s320/javaIsReachable.png" /></a></div><br />
<br />
So I thought to myself, either I do not have a <i>typical</i> implementation, or the API is wrong. (My money is on the latter). Originally I thought the jvm might be able to use ICMP through JNI perhaps. But apparently not. So back to the drawing board I went.<br />
<br />
That is when I found .NET's simple <a href="http://msdn.microsoft.com/en-us/library/system.net.networkinformation.pingreply%28v=VS.100%29.aspx">Ping</a> class. Its <span style="font-family: "Courier New",Courier,monospace;">Send(..)</span> method has four parameters: target host, timeout, sample data packet and options (time to live and discard fragmented packets). <span style="font-family: "Courier New",Courier,monospace;">Host </span>and <span style="font-family: "Courier New",Courier,monospace;">options</span> are complex objects. So you have to create an instance of those classes first. Once you have all of the necessary objects, simply call Send(..) to execute the ping and get the response. <br />
<br />
<pre class="xhtml" name="code"><cfscript>
ping = createObject(".net", "System.Net.NetworkInformation.Ping");
dns = createObject(".net", "System.Net.Dns");
ips = dns.GetHostAddresses( "www.google.com" );
// arbitrary data to be transmitted
data = listToArray(repeatString("1,", 32));
// set the ttl (time to live) = 64 and disable fragmenting
options = createObject(".net", "System.Net.NetworkInformation.PingOptions").init(64, true);
reply = ping.Send ( ips[1], 100, javacast("short[]", data), options);
status = createObject(".net", "System.Net.NetworkInformation.IPStatus");
</cfscript>
</pre><br />
Finally, check the response status to determine if the ping was successful. The example below displays all of the options used for debugging purposes. But all you really need to do is check the response status.<br />
<br />
One oddity you may notice. For whatever reason, I do not believe you can access the class properties by name alone, as you can in .NET (or with java objects). So you must use the method <span style="font-family: "Courier New",Courier,monospace;">reply.Get_Address()</span> instead of the shorter form <span style="font-family: "Courier New",Courier,monospace;">reply.Address</span>. <br />
<br />
<pre class="xhtml" name="code"><!--- using HR tags because of a blogger bug --->
<cfif reply.Get_Status() eq status.Success>
<cfoutput>
Get_Address = #reply.Get_Address().ToString()# <hr />
Get_RoundTripTime = #reply.Get_RoundtripTime().ToString()# <hr />
Get_Options.Ttl (Time to Live) = #reply.Get_Options().Get_Ttl()# <hr />
Get_Options.DontFragment = #reply.Get_Options().Get_DontFragment()# <hr />
Get_Buffer (Length) = #ArrayLen(reply.Get_Buffer())# <hr />
</cfoutput>
<cfelse>
Drat. Something went wrong ...
</cfif>
</pre><br />
Anyway, I thought this was an interesting alternative to ping.exe. Plus, it was a good opportunity to brush up on my CF / .NET skills ;)<br />
<br />
<div class="update"><b>Update</b>: {Sigh} Darn, I do not know why this one did not came up <br />
in my searches. Usually anything from cflib.org is pretty highly ranked. <br />
Well, in any event, it was still a good learning experience about why not to use java for ping ;). <a href="http://www.cflib.org/udf/Ping">http://www.cflib.org/udf/Ping</a><br />
</div></span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-68182046661741926322010-04-27T06:26:00.000-07:002010-04-27T08:23:20.415-07:00ColdFusion: Verify SMTP, POP3 and IMAP Mail Server ConnectionI saw an intriguing question about mail servers this week, on <a href="http://stackoverflow.com/">stackoverflow.com</a>. The question was is it possible to verify an SMTP mail server connection programatically? Essentially duplicate the "Verify mail server connection" functionality that exists in the CF Administrator. Though I am certain this is old news to some of you, the answer is yes. <br />
<br />
<span id="fullpost">Interestingly, both suggestions on stackoverflow.com utilized java libraries already built into all three engines: Adobe ColdFusion, Railo and OpenBlueDragon. Member <a href="http://stackoverflow.com/users/163423/folone">folone</a> suggested using the SMTPClient class in <a href="http://commons.apache.org/net/">Apache Commons Net</a> , while member <a href="http://stackoverflow.com/users/178526/sfussenegger">sfussenegger</a> suggested using the core <a href="http://java.sun.com/products/javamail/index.jsp">JavaMail</a> library with an <a href="http://java.sun.com/javase/6/docs/api/java/net/Authenticator.html">Authenticator</a>. (Unfortunately, I do not know their full names).<br />
<br />
Having used JavaMail and Authenticators before, I was somewhat familiar with that option. Though a bit rusty. Being less familiar with Commons Net, I had to do some research. Apparently, Commons Net is the predecessor of JavaMail. So the two libraries serve similar purposes, but Commons Net is a lower level API. However, both can be used to communicate with SMTP and POP3 servers. Though I do not think the Commons library supports IMAP.<br />
<br />
Anyway, I was curious about the Commons classes, so I played around with them a bit. Eventually I managed to force an SSL connection with POP3, courtesy of <a href="http://www.antsight.com/perry/archives/000697.html">this tip</a> and the convenience MailSSLSocketFactory class. However, the code only works in Adobe CF. Unfortunately, Railo and OpenBlueDragon do not seem to include the MailSSLSocketFactory class, and I did not want to create my own.<br />
<br />
<pre class="xhtml" name="code"><cfscript>
isVerified = false;
try {
SSLFactory = createObject("java", "com.sun.mail.util.MailSSLSocketFactory").init("SSL");
POP3Client = createObject("java", "org.apache.commons.net.pop3.POP3Client").init();
POP3Client.setSocketFactory( SSLFactory );
POP3Client.connect("pop.gmail.com", 995);
isVerified = POP3Client.login("user@gmail.com", "user password");
WriteOutput("isVerified="& isVerified &"<br />");
POP3Client.logout();
}
// error occurred during login or logout
catch(java.io.IOException e) {
WriteDump(e);
}
</cfscript>
</pre><br />
Since JavaMail works on all three engines, with no extra classes, it seemed the best choice. Plus I really liked the idea of an all-in-one validator, not just smtp. So I put together a rough function that validates SMTP, POP3 and IMAP connections. It should be compatible with CF8, CF9, Railo and OpenBlueDragon. Tested against gmail and hMailServer. <br />
<br />
For those not familiar with JavaMail, the function is very simple. It first stores the properties you want to use for the connection (like TLS). Then creates a new mail Session using those properties. Next it attempts to open a connection to the given mail server and requests authentication. Finally the connection is closed. If an error occurs, the function checks the exception type to determine if the credentials were invalid or it was some other type failure. The function returns a structure with three keys: WasValidated, ErrorType and ErrorDetail.<br />
<br />
One last note about the function. When TLS is selected, it is made mandatory. So if the mail server on the other end does not support TLS, the verification will fail, for security reasons. But you can change that behavior if you wish.<br />
<br />
If you are interested in reading more about JavaMail, these two articles are very comprehensive: <a href="http://java.sun.com/developer/onlineTraining/JavaMail/contents.html">jGuru: Fundamentals of the JavaMail API</a> and <a href="http://java.sun.com/products/javamail/FAQ.html">JavaMail FAQ's</a>.<br />
<br />
<b>Example</b><br />
<pre class="xhtml" name="code"><cfset response = verifyMailServer( host = "smtp.gmail.com",
protocol = "smtp",
port = 587,
user = "username@gmail.com",
password = "user password",
useTLS = true,
debug = true,
overwrite = false,
logPath = "c:\verifyMailServer_Test.log"
) />
<cfdump var="#response#" label="Verfication Results">
</pre><br />
<b>Function</b><br />
<pre class="xhtml" name="code"><cffunction name="verifyMailServer" returntype="struct" access="public" output="true">
<cfargument name="protocol" type="string" required="true" hint="Mail protocol: SMTP, POP3 or IMAP" />
<cfargument name="host" type="string" required="true" hint="Mail server name (Example: pop.gmail.com)"/>
<cfargument name="port" type="numeric" default="-1" hint="Mail server port number. Default is -1, meaning use the default port for this protocol)" />
<cfargument name="user" type="string" required="true" hint="Mail account username" />
<cfargument name="password" type="string" required="true" hint="Mail account password" />
<cfargument name="useSSL" type="boolean" default="false" hint="If true, use SSL (Secure Sockets Layer)" >
<cfargument name="useTLS" type="boolean" default="false" hint="If true, use TLS (Transport Level Security)" >
<cfargument name="enforceTLS" type="boolean" default="false" hint="If true, require TLS support" >
<cfargument name="timeout" type="numeric" default="0" hint="Maximum milliseconds to wait for connection. Default is 0 (wait forever)" />
<cfargument name="debug" type="boolean" default="false" hint="If true, enable debugging. By default information is sent to is sent to System.out." >
<cfargument name="logPath" type="string" default="" hint="Send debugging output to this file. Absolute file path. Has no effect if debugging is disabled." >
<cfargument name="append" type="boolean" default="true" hint="If false, the existing log file will be overwritten" >
<cfset var status = structNew() />
<cfset var props = "" />
<cfset var mailSession = "" />
<cfset var store = "" />
<cfset var transport = "" />
<cfset var logFile = "" />
<cfset var fos = "" />
<cfset var ps = "" />
<!--- validate protocol --->
<cfset arguments.protocol = lcase( trim(arguments.protocol) ) />
<cfif not listFindNocase("pop3,smtp,imap", arguments.protocol)>
<cfthrow type="IllegalArgument" message="Invalid protocol. Allowed values: POP3, IMAP and SMTP" />
</cfif>
<cfscript>
// initialize status messages
status.wasVerified = false;
status.errorType = "";
status.errorDetail = "";
try {
props = createObject("java", "java.util.Properties").init();
// enable securty settings
if (arguments.useSSL or arguments.useTLS) {
// use the secure protocol
// this will set the property mail.{protocol}.ssl.enable = true
if (arguments.useSSL) {
arguments.protocol = arguments.protocol &"s";
}
// enable identity check
props.put("mail."& protocol &".ssl.checkserveridentity", "true");
// enable transport level security and make it mandatory
// so the connection fails if TLS is not supported
if (arguments.useTLS) {
props.put("mail."& protocol &".starttls.required", "true");
props.put("mail."& protocol &".starttls.enable", "true");
}
}
// force authentication command
props.put("mail."& protocol &".auth", "true");
// for simple verifications, apply timeout to both socket connection and I/O
if (structKeyExists(arguments, "timeout")) {
props.put("mail."& protocol &".connectiontimeout", arguments.timeout);
props.put("mail."& protocol &".timeout", arguments.timeout);
}
// create a new mail session
mailSession = createObject("java", "javax.mail.Session").getInstance( props );
// enable debugging
if (arguments.debug) {
mailSession.setDebug( true );
// redirect the output to the given log file
if ( len(trim(arguments.logPath)) ) {
logFile = createObject("java", "java.io.File").init( arguments.logPath );
fos = createObject("java", "java.io.FileOutputStream").init( logFile, arguments.overwrite );
ps = createObject("java", "java.io.PrintStream").init( fos );
mailSession.setDebugOut( ps );
}
}
// Connect to an SMTP server ...
if ( left(arguments.protocol, 4) eq "smtp") {
transport = mailSession.getTransport( protocol );
transport.connect(arguments.host, arguments.port, arguments.user, arguments.password);
transport.close();
// if we reached here, the credentials should be verified
status.wasVerified = true;
}
// Otherwise, it is a POP3 or IMAP server
else {
store = mailSession.getStore( protocol );
store.connect(arguments.host, arguments.port, arguments.user, arguments.password);
store.close();
// if we reached here, the credentials should be verified
status.wasVerified = true;
}
}
//for authentication failures
catch(javax.mail.AuthenticationFailedException e) {
status.errorType = "Authentication";
status.errorDetail = e;
}
// some other failure occurred like a javax.mail.MessagingException
catch(Any e) {
status.errorType = "Other";
status.errorDetail = e;
}
// always close the stream ( messy work-around for lack of finally clause prior to CF9...)
if ( not IsSimpleValue(ps) ) {
ps.close();
}
return status;
</cfscript>
</cffunction>
</pre></span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-73572577705566033362010-04-27T04:29:00.000-07:002010-05-02T02:07:18.830-07:00Railo: CFPOP + Gmail + SSL ExperimentAn interesting question on <a href="http://stackoverflow.com/">stackoverflow.com</a> this week prompted me to explore cfmail/cfpop settings in the three engines: Railo, OpenBD and Adobe CF. If you have used gmail from ColdFusion, no doubt you are aware of one of the <a href="http://www.anujgakhar.com/2008/05/18/cfpop-and-gmail/">work-arounds for the lack of SSL support for CFPOP</a>. Being new to Railo, I did not realize the work-around is only for Adobe CF.<br />
<br />
<span id="fullpost"> <br />
Apparently Adobe CF also checks the default <span style="font-family: "Courier New",Courier,monospace;">java.lang.System </span>properties when creating mail connections. If certain mail settings like <span style="font-family: "Courier New",Courier,monospace;">mail.pop3.socketFactory.class</span> are present, it applies them to the new connection. However, Railo does not. It only uses the supplied tag attributes, which do not include "useSSL". At least as far as I know. So the work-around has no effect in Railo.<br />
<br />
After poking around the <a href="http://java.sun.com/j2ee/1.4/docs/api/javax/mail/Session.html">API</a>, I did manage to get CFPOP working with gmail under Railo.. with a catch. You can specify which provider to use <strike>for the <span style="font-family: "Courier New",Courier,monospace;">pop3</span> protocol</strike> as the default provider for the <span style="font-family: "Courier New",Courier,monospace;">pop3</span> protocol by adding a <span style="font-family: "Courier New",Courier,monospace;">javamail.providers</span> file to your {java_home}\lib directory. <br />
<br />
<b>Display {Java_Home} Path:</b><br />
<pre class="xhtml" name="code"><cfset javaHome = createObject("java", "java.lang.System").getProperty("java.home", "")>
<cfoutput>
javaHome\lib = #javaHome#\lib
</cfoutput>
</pre><br />
Simply add the following line, and save the file as javamail.providers. (The ".providers" file extension is important). All CFPOP connections will now use SSL.<br />
<br />
<b>javamail.providers</b><br />
<pre class="xhtml" name="code">protocol=pop3; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Sun Microsystems, Inc;
</pre><br />
Now, I mentioned a catch. Unlike the work-around for Adobe CF, this setting is all-or-nothing. If you enable it, all CFPOP connections will use SSL. If you connect to a server does not support SSL, the connection will fail. With the Adobe CF work-around both connection types are allowed by setting the property <span style="font-family: "Courier New",Courier,monospace;">mail.pop3.socketFactory.fallback</span> equal to true. So SSL will be used if supported. Otherwise, CF will fall back to a regular socket connection. Keep in mind both work-arounds are system wide. So the settings apply to the entire JVM.<br />
<br />
Now, the fix may be too broad for some. But if you only need SSL connections for CFPOP, it might do the trick. If not, there are other options like sTunnel and custom CFC's. I am sure Railo will implement official support for SSL with CFPOP one of these days. Hopefully, Adobe CF will too ;) <br />
<br />
</span>Unknownnoreply@blogger.com6tag:blogger.com,1999:blog-5824660313967021040.post-932185275632122482010-04-21T05:10:00.000-07:002010-04-22T21:35:36.028-07:00ColdFusion: ZXing - Read / Write QRCode Barcode ExampleI have been slowly working my way through more of the <a href="http://code.google.com/p/zxing/">zxing library</a>. Since I did not find any CF examples in my searches, I am posting some of my code snippets in case it helps someone else.<br />
<br />
<span id="fullpost"><br />
<b>Generate Barcode</b><br />
To generate a barcode, I first created an instance of the QRCodeWriter. Then called its encode() method with four settings: the text to encode, barcode type and the desired width and height. The encode() method does it magic and returns a matrix of bytes. Next I used the MatrixToImageWriter class, and its toBufferedImage(), method to convert the matrix into something useful. Not surprisingly, it returns a BufferedImage which is easily converted to a CF image object.<br />
<br />
<i>Note: This example uses the JavaLoader.cfc </i><br />
<pre class="xhtml" name="code"><!---
Generate barcode
--->
<cftry>
<cfset origText = "http://code.google.com/p/zxing/wiki/GettingStarted" />
<!--- initialize writer and create a new barcode matrix --->
<cfset BarcodeFormat = loader.create("com.google.zxing.BarcodeFormat") />
<cfset writer = loader.create("com.google.zxing.qrcode.QRCodeWriter").init() />
<cfset bitMatrix = writer.encode( origText, BarcodeFormat.QR_CODE, 80, 80 )>
<!--- render the matrix as a bufferedimage --->
<cfset converter = loader.create("com.google.zxing.client.j2se.MatrixToImageWriter")>
<cfset buff = converter.toBufferedImage( bitMatrix ) />
<!--- convert it to a CF compatible image --->
<cfset img = ImageNew( buff ) />
<!--- display results --->
<b>Original Text = </b> <cfoutput>#origText#</cfoutput>
<div>
<cfimage action="writeToBrowser" source="#img#" format="png">
</div>
<!--- add real exception handling here ...--->
<cfcatch>
ERROR: Unable to generate barcode <cfoutput>#cfcatch.message#</cfoutput>
</cfcatch>
</cftry>
</pre><br />
<b>Read Barcode</b><br />
Now to read / decode, I passed the BufferedImage into a series of classes that attempt to locate a barcode within an image and essentially extract it into matrix of bits. Finally, I passed the matrix into a QRCodeReader for decoding and voila - it returned the decoded text.<br />
<br />
<b>Result</b>:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnhe_YsDNcJgllykz1avTeR_lLql4eV3Bjs8mYrLP_hkneY9RDturYxZTpGG8QxNV4iHAjc19V_-qp_gjsF1UIeXpbalZvJMYnK6F-1CPu4eeVAX9GiElnlXZvPwxs8yY5Isv-DM2uxb8/s1600/ColdFusion_ZXing_QR_Code_Example.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnhe_YsDNcJgllykz1avTeR_lLql4eV3Bjs8mYrLP_hkneY9RDturYxZTpGG8QxNV4iHAjc19V_-qp_gjsF1UIeXpbalZvJMYnK6F-1CPu4eeVAX9GiElnlXZvPwxs8yY5Isv-DM2uxb8/s320/ColdFusion_ZXing_QR_Code_Example.png" /></a></div><br />
<br />
<pre class="xhtml" name="code"><!---
Decode barcode
--->
<cftry>
<!--- extract the BufferedImage of the current barcode --->
<cfset buff = ImageGetBufferedImage( img ) />
<!--- prepare the image for decoding --->
<cfset source = loader.create("com.google.zxing.client.j2se.BufferedImageLuminanceSource").init( buff ) />
<cfset binarizer = loader.create("com.google.zxing.common.GlobalHistogramBinarizer").init( source ) />
<cfset bitmap = loader.create("com.google.zxing.BinaryBitmap").init( binarizer ) />
<cfset reader = loader.create("com.google.zxing.qrcode.QRCodeReader").init() />
<!--- decode the barcode. skipping "hints" just for simplicity --->
<cfset decodedResult = reader.decode( bitmap, javacast("null", "")) />
<!--- display results --->
<b>Decoded Text = </b> <cfoutput>#decodedResult.getText()#</cfoutput>
<!--- add real exception handling here ...--->
<cfcatch>
ERROR: Unable to generate barcode <cfoutput>#cfcatch.message#</cfoutput>
</cfcatch>
</cftry>
</pre><br />
<b>Related Entries</b><br />
<ul><li><a href="http://cfsearching.blogspot.com/2009/11/seeing-stripes-experiment-with-zxing.html">Seeing Stripes: Experiment with ZXing</a></li>
</ul></span>Unknownnoreply@blogger.com20tag:blogger.com,1999:blog-5824660313967021040.post-21806588378862926402010-04-19T18:55:00.000-07:002010-04-19T19:38:01.286-07:00ColdFusion: iText / Add JavaScript To Form ExampleI occasionally see questions about adding javascript to existing pdf's. There are (of course) some <a href="http://1t3xt.info/tutorials/keywords/?page=keyword&id=535">great examples on the iText site</a>. I thought this CF translation of the AddJavaScriptToForm example might be helpful. If you review the code and comments, it actually simpler than it looks. There are some minor differences to compensate for the older iText jars in CF9 and CF8. As well as some name changes to avoid reserved word conflicts. <br />
<br />
<b>Source:</b> <a href="http://1t3xt.info/examples/browse/?page=example&id=438">http://1t3xt.info/examples/browse/?page=example&id=438</a><br />
<br />
<span id="fullpost"><br />
<b>Javascript Code (CF8 + CF9)</b> <br />
<pre class="xhtml" name="code"><cfsavecontent variable="jsCode">
function setReadOnly(readonly) {
var partner = this.getField('partner');
if(readonly) {
partner.value = '';
}
partner.readonly = readonly;
}
function validate() {
var married = this.getField('married');
var partner = this.getField('partner');
if (married.value == 'yes' && partner.value == '') {
app.alert('please enter the name of your partner');
}
else {
var prop = new Object();
prop.cURL = "http://1t3xt.info/examples/request.php";
prop.cSubmitAs = "HTML";
this.submitForm( {
cURL: "http://1t3xt.info/examples/request.php",
cSubmitAs: "HTML"
}
);
}
}
</cfsavecontent>
</pre><br />
<b>1) Create a Form (CF9 + CF8)</b><br />
Note: CF8 does not support "finally". So for compatibility, just change the try/finally clause to a try/catch.<br />
<pre class="xhtml" name="code"><cfscript>
outputPath = ExpandPath("form_without_js.pdf");
document = createObject("java", "com.lowagie.text.Document").init();
try {
stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance(document, stream);
document.open();
Element = createObject("java", "com.lowagie.text.Element");
BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
//Note: CF8 + CF9 iText versions do not have a createFont() method with zero params
bf = BaseFont.createFont( BaseFont.HELVETICA, BaseFont.WINANSI, false );
directcontent = writer.getDirectContent();
directcontent.beginText();
directcontent.setFontAndSize(bf, 12);
directcontent.showTextAligned( Element.ALIGN_LEFT, "Married?", 36, 770, 0 );
directcontent.showTextAligned( Element.ALIGN_LEFT, "YES", 58, 750, 0);
directcontent.showTextAligned( Element.ALIGN_LEFT, "NO", 102, 750, 0);
directcontent.showTextAligned( Element.ALIGN_LEFT, "Name partner?", 36, 730, 0 );
directcontent.endText();
// initialize reusable objects
Rectangle = createObject("java", "com.lowagie.text.Rectangle");
PdfFormField = createObject("java", "com.lowagie.text.pdf.PdfFormField");
RadioCheckField = createObject("java", "com.lowagie.text.pdf.RadioCheckField");
TextField = createObject("java", "com.lowagie.text.pdf.TextField");
Color = createObject("java", "java.awt.Color");
married = PdfFormField.createRadioButton(writer, true);
married.setFieldName("married");
writer.addAnnotation( married );
// Note: Field names changed to avoid CF reserved word conflicts (ie "yes", "no")
married.setValueAsName("yes");
rectYes = Rectangle.init( 40, 766, 56, 744 );
yesField = RadioCheckField.init(writer, rectYes, javacast("null", ""), "yes");
yesField.setChecked(true);
married.addKid( yesField.getRadioField() );
rectNo = Rectangle.init( 84, 766, 100, 744 );
noField = RadioCheckField.init(writer, rectNo, javacast("null", ""), "no");
noField.setChecked(false);
married.addKid( noField.getRadioField() );
writer.addAnnotation( married );
rect = Rectangle.init( 40, 710, 200, 726 );
partner = TextField.init( writer, rect, "partner" );
partner.setBorderColor( Color.BLACK );
partner.setBorderWidth( 0.5 );
writer.addAnnotation( partner.getTextField() );
document.close();
WriteOutput("File created! "& outputPath &"<hr>");
}
// cleanup
finally {
if (isDefined("document")) {
document.close();
}
if (isDefined("stream")) {
stream.close();
}
}
</cfscript>
</pre><br />
<b>2) Add the Javascript (CF9 Only)</b> <br />
<pre class="xhtml" name="code"><cfscript>
inputPath = ExpandPath("form_without_js.pdf");
outputPath = ExpandPath("form_plus_js.pdf");
document = createObject("java", "com.lowagie.text.Document").init();
try {
// read in the pdf form
reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );
// add javascript functions to the document
stamper.getWriter().addJavaScript( jsCode );
// create reference objects
PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
// extract the parent option from the form
formObj = stamper.getAcroFields();
fd = formObj.getFieldItem("married");
// retrieve the dictionaries for "yes" radio button
// note: CF9 iText version does not have the getWidgetRef(index) method
dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
yesAction = dictYes.getAsDict( PdfName.AA );
if (not IsDefined("yesAction")) {
yesAction = PdfDictionary.init();
}
// add an onFocus event to this field
yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
dictYes.put( PdfName.AA, yesAction );
// retrieve the dictionaries for "no" radio button
dictNo = reader.getPdfObject( fd.widget_refs.get(1));
noAction = dictNo.getAsDict( PdfName.AA );
if (not IsDefined("noAction")) {
noAction = PdfDictionary.init();
}
// add an onFocus event to this field
noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
dictNo.put(PdfName.AA, noAction);
// create a submit button
writer = stamper.getWriter();
button = PushbuttonField.init( writer, Rectangle.init(40, 690, 200, 710), "submit" );
button.setText( "validate and submit" );
button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
validateAndSubmit = button.getField();
// this will call the validate function when the button is clicked
validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
// add the button to page 1
stamper.addAnnotation(validateAndSubmit, 1);
WriteOutput("File created! "& outputPath &"<hr>");
}
finally {
// cleanup
if (isDefined("stamper")) {
stamper.close();
}
if (isDefined("stream")) {
stream.close();
}
}
</cfscript>
</pre><br />
<b>2) Add the Javascript (CF8 Only)</b> <br />
<pre class="xhtml" name="code"><cfscript>
inputPath = ExpandPath("form_without_js.pdf");
outputPath = ExpandPath("form_plus_js.pdf");
document = createObject("java", "com.lowagie.text.Document").init();
try {
// read in the pdf form
reader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
stream = createObject("java", "java.io.FileOutputStream").init( outputPath );
stamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( reader, stream );
// add javascript functions to the document
stamper.getWriter().addJavaScript( jsCode );
// create reference objects
PdfName = createObject("java", "com.lowagie.text.pdf.PdfName");
PdfAction = createObject("java", "com.lowagie.text.pdf.PdfAction");
PdfDictionary = createObject("java", "com.lowagie.text.pdf.PdfDictionary");
PushbuttonField = createObject("java", "com.lowagie.text.pdf.PushbuttonField");
// prepare to extract form field objects
formObj = stamper.getAcroFields();
// get parent option
fd = formObj.getFieldItem("married");
// retrieve the dictionaries for "yes" radio button
// note: CF8 iText version does not have the getWidgetRef(index) method or getDirectObject()
dictYes = reader.getPdfObject( fd.widget_refs.get(0) );
yesAction = reader.getPdfObject(dictYes.get(PdfName.AA));
if (not IsDefined("yesAction") or not yesAction.isDictionary()) {
yesAction = PdfDictionary.init();
}
// add an onFocus event to this field
yesAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(false);", stamper.getWriter()));
dictYes.put( PdfName.AA, yesAction );
// retrieve the dictionaries for "no" radio button
dictNo = reader.getPdfObject( fd.widget_refs.get(1));
noAction = reader.getPdfObject( dictNo.get(PdfName.AA) );
if (not IsDefined("noAction") or not noAction.isDictionary()) {
noAction = PdfDictionary.init();
}
// add an onFocus event to this field
noAction.put( PdfName.init("Fo"), PdfAction.javaScript("setReadOnly(true);", stamper.getWriter()));
dictNo.put(PdfName.AA, noAction);
// create a submit button
writer = stamper.getWriter();
button = PushbuttonField.init( writer,
Rectangle.init(40, 690, 200, 710),
"submit"
);
button.setText( "validate and submit" );
button.setOptions( PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT );
validateAndSubmit = button.getField();
// this will cal the validate function when the button is clicked
validateAndSubmit.setAction( PdfAction.javaScript("validate();", stamper.getWriter()) );
// add the button to page 1
stamper.addAnnotation(validateAndSubmit, 1);
WriteOutput("File created! "& outputPath &"<hr>");
}
catch(Any e) {
WriteOutput("ERROR: "& e.message);
}
if (isDefined("stamper")) {
stamper.close();
}
if (isDefined("stream")) {
stream.close();
}
</cfscript>
</pre><br />
</span>Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-5824660313967021040.post-8548011585031281072010-04-17T13:19:00.000-07:002010-04-17T13:19:20.488-07:00CFChart Tip (Multiple background colors)A recent question on <a href="http://www.houseoffusion.com/groups/cf-talk/">CF-Talk</a> asked if it were possible to create multiple background colors with cfchart. I was excited to discover it <i>is </i>possible using the Y-Axis "limits" attribute. I should have known I was not the first one to figure this out. Well, in case you too missed the original entry, here it is:<br />
<br />
<div style="text-align: center;"><a href="http://www.coldfusionjedi.com/index.cfm/2008/10/23/Cool-CFCHART-Tip--Background-Ranges">Cool CFCHART Tip - Background Ranges (Raymond Camden)</a></div><div style="text-align: center;"><br />
</div><div style="text-align: center;"><b> Example of multiple background colors using Y-Axis "limits"</b></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipKunnxtrnqxAuKlo7XqCmI-XKA5Qkd88ZQomFgjJXUHjADa-Ab4JP8kY3V0Ge6UdjNb0v-LT6AsQgocKtc_z2Ql3DfRtYuaT_yQLpg4kvmcubZCk7qZkmpIN1h0JN7R25Dya42ZVHus/s1600/chartIdeal1_viaCFChart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhipKunnxtrnqxAuKlo7XqCmI-XKA5Qkd88ZQomFgjJXUHjADa-Ab4JP8kY3V0Ge6UdjNb0v-LT6AsQgocKtc_z2Ql3DfRtYuaT_yQLpg4kvmcubZCk7qZkmpIN1h0JN7R25Dya42ZVHus/s320/chartIdeal1_viaCFChart.png" /></a></div>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-5824660313967021040.post-39419595890042432912010-04-14T18:23:00.000-07:002010-04-14T18:23:34.265-07:00How to Install JODConverter 3.0 under ColdFusion 8<b>ColdFusion 8 Instructions</b><br />
<br />
1. Download latest version of the <a href="http://code.google.com/p/jodconverter/">JODConverter</a> and unzip.<br />
<br />
2. Copy all jars from the <span style="font-family: "Courier New",Courier,monospace;">jodconverter-core-3.0-beta-3\lib directory</span> into the CF classpath. (Your version numbers may vary)<br />
<br />
<i>Note: I am not sure if all of the jars are needed. But this is the configuration that I used.</i><br />
<br />
<span id="fullpost"><br />
Example: Into WEB-INF\lib <br />
<pre>C:\ColdFusion8\wwwroot\WEB-INF\lib\commons-cli-1.1.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\commons-io-1.4.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\jodconverter-core-3.0-beta-3.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\json-20080701.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\juh-3.1.0.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\jurt-3.1.0.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\ridl-3.1.0.jar
C:\ColdFusion8\wwwroot\WEB-INF\lib\unoil-3.1.0.jar
</pre><br />
3. Smart people will make a back-up copy of the<span style="font-family: "Courier New",Courier,monospace;"> jvm.config</span> file here. <br />
<br />
4. Open the <span style="font-family: "Courier New",Courier,monospace;">jvm.config</span> file and locate the <span style="font-family: "Courier New",Courier,monospace;">java.library.path</span> value. Append the appropriate OpenOffice directory, using a comma. The exact directory will depend on your O/S. See the <a href="http://code.google.com/p/jodconverter/wiki/Configuration">configuration page</a> for details.<br />
<br />
Example: On windows the new value might be:<br />
<pre>java.library.path={application.home}/../lib,{application.home}/../jintegra/bin,{application.home}/../jintegra/bin/international,C:/Program Files/OpenOffice.org 3/URE/bin
</pre><br />
4. Stop and restart the CF Server<br />
</span>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-47717863764278141512010-04-14T16:27:00.000-07:002010-04-20T21:32:20.799-07:00JODConverter 3.0 + OpenOffice + CF8I stumbled across a <a href="http://code.google.com/p/jodconverter/">new version</a> of the <a href="http://cfsearching.blogspot.com/2009/04/coldfusion-in-search-of-wordrtf-to-pdf.html">JODConverter</a> a few weeks ago, and finally got around to testing it. Version 3.0 has some interesting new features over the previous version. For one thing, it no longer requires running OpenOffice as a service. The JODConverter can start an instance of OpenOffice on demand, like CF9. Both socket and named pipe connections are supported on windows. There are also new features like pooling and automatic restart, geared towards "improving the reliability and scalability of working with an external OOo [OpenOffice] process.". So I thought it was definitely worth a look (for Word to HTML conversions)<br />
<br />
<span id="fullpost"><br />
<b>Basic Conversion Example </b><br />
I decided to test it under CF8 first. Since I was not running OpenOffice as a service, the first thing I needed to do was fire up a new instance of OpenOffice. Now to start or stop OpenOffice, you will need an OfficeManager object. The simplest way to create one is using the DefaultOfficeManagerConfiguration class. Just create a new configuration object, then call the buildOfficeManager() method. This will create and return a new OfficeManager object pre-configured with the default settings, which should work in most environments. Then call OfficeManager.start() to kick-off the OpenOffice process. <br />
<br />
<div style="text-align: center"><a href="http://cfsearching.blogspot.com/2010/04/how-to-install-jodconverter-30-under.html">How to Install JODConverter 3.0 under ColdFusion 8</a><br />
</div><br />
<pre name="code" class="xhtml"><cfscript>
Config = createObject("java", "org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration").init();
Config.setOfficeHome("C:\Program Files\OpenOffice.org 3\");
Manager = Config.buildOfficeManager();
Manager.start();
</cfscript>
</pre><br />
To convert a document, you first need to create an OfficeDocumentConverter object by passing in your OfficeManager. Then simply call the convert() method with your two files (ie input and output). Finally, use the OfficeManager to stop the OpenOffice instance. That is it. <br />
<br />
<pre name="code" class="xhtml"><cfscript>
inPath = "c:\docs\myTestDocument.docx";
outPath = "c:\docs\myTestDocument_Converted.html";
OfficeDocumentConverter = createObject("java", "org.artofsolving.jodconverter.OfficeDocumentConverter");
converter = OfficeDocumentConverter.init( Manager );
input = createObject("java", "java.io.File").init( inPath );
output = createObject("java", "java.io.File").init( outPath );
Converter.convert(input, output);
WriteOutput("Output file created: "& output);
Manager.stop();
</cfscript>
</pre><br />
Obviously it would be silly to start and stop OpenOffice every time you needed to do a conversion. So a better alternative might be to initialize the OfficeManager once in your Application.cfc, and reuse it. Perform the initialization code in onApplicationStart and add your OfficeManager to the application scope. Then do the cleanup code (ie stopping OpenOffice) in onApplicationEnd. Other than grabbing the OfficeManager from the application scope first, the conversion code is exactly the same.<br />
<br />
<b>Application.cfc (Not including error handling, logging, etcetera)</b><br />
<pre name="code" class="xhtml"><cfcomponent>
<cfset this.name = "jodconverterSample" />
<cfset this.applicationTimeOut = createTimeSpan(0, 1, 0, 0) />
<cffunction name="onApplicationStart" returnType="void">
<cfset var Manager = "" />
<cfset var Config = "" />
<!--- start an instance with the default settings --->
<cfset Config = createObject("java", "org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration").init() />
<cfset Config.setOfficeHome( "C:\Program Files\OpenOffice.org 3\" ) />
<cfset Manager = Config.buildOfficeManager() />
<cfset Manager.start() />
<cfset application.OfficeManager = Manager />
</cffunction>
<cffunction name="onApplicationEnd" returnType="void">
<cfargument name="appScope" type="any" required="true" />
<!--- stop the instance --->
<cfset appScope.OfficeManager.stop() />
</cffunction>
</cfcomponent>
</pre><br />
<b>Settings</b><br />
Earlier, I mentioned there are different types of connections and managers. The DefaultOfficeManagerConfiguration creates a socket connection on port 2002 by default. But you can use the available methods to change the port number, connection type, and a bunch of other settings. For example, you could create a named pipe connection instead. Just set the appropriate configuration properties before creating the OfficeManager.<br />
<br />
<pre name="code" class="xhtml"><cfscript>
Config = createObject("java", "org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration").init() />
Config.setOfficeHome( "C:\Program Files\OpenOffice.org 3\" ) />
// use named pipe connection
Protocol = createObject("java", "org.artofsolving.jodconverter.office.OfficeConnectionProtocol");
Config.setConnectionProtocol( Protocol.PIPE );
Config.setPipeName( "myApp_jod_pipe" );
// kill any task that takes longer than 2 minutes
Config.setTaskExecutionTimeout((2 * 60 * 1000));
Manager = Config.buildOfficeManager();
// ....
</cfscript>
</pre><br />
You could also connect to an external instance that is already running using the ExternalOfficeManagerConfiguration class instead. Though when working with an external process (ie controlled elsewhere ), obviously you do not start() or stop() it. Just connect to it.<br />
<br />
<b>Web Application / Servlet</b><br />
Though JODConverter can be used strictly as a java library, there is also a sample web application/servlet available. It is not currently part of the distribution jar, but you can find it under the project source tab. Just be aware the web application does not fully handle conversions to HTML out-of-the-box. The servlet will produce a single html file, minus any images. As <a href="http://code.google.com/p/jodconverter/wiki/FAQ">mentioned in the FAQ's</a>, that is by design. The best way to handle images really "..depends on your particular requirements". So the implementation of image handling is deliberately left up to you. For more details see the FAQ's. <br />
<br />
<b>OpenOffice Quirks</b><br />
Obviously, OpenOffice has some quirks of its own. Though it does a pretty good job with most documents, it is not perfect. It almost certainly will not be able to convert everything you throw at it. So any conversion code should definitely incorporate some solid error handling. <br />
<br />
<b>CF9 Quirks</b><br />
I was very curious to see how well all of the pieces worked together under CF9. As I expected, there were a few quirks. <br />
<br />
For whatever reason, things only worked smoothly when the JODConverter's instance of OpenOffice was started <i>after </i>CF9 started its instance. In other words, I had to run a small Word to Pdf conversion first, to force CF to start its OpenOffice process. Then afterward start the JODConverter's instance. When they were not started in that order, all sorts of errors ensued. Both from CF and the JODConverter. <br />
<br />
While I had success with socket and external connections, I had zero luck getting a separate named pipe connection to work alongside CF9's instance. Initially I thought it should be possible. But I am not very well versed in UNO or named pipes. So that could just be ignorance on my part. If anyone does know the answer, one way or the other, let me know.<br />
<br />
<b>Conclusions</b><br />
All in all, I was pleased with my initial tests. Though I am still not completely comfortable with the automatic restart feature. I can definitely see its value. OpenOffice can, and on occasion, does crash. But anything that automatically revives itself after death, tends to make me think of zombies ;) So I think I will need to study it (and how to best manage OOO instances) further.<br />
</span>Unknownnoreply@blogger.com8tag:blogger.com,1999:blog-5824660313967021040.post-36293278860535370722010-04-01T01:52:00.000-07:002010-04-01T01:52:17.457-07:00OT: April 1 (Take Your Marks ... Get Set ... Go)I see the day is already off to a rousing start. <br />
<a href="http://googleblog.blogspot.com/2010/04/different-kind-of-company-name.html">http://googleblog.blogspot.com/2010/04/different-kind-of-company-name.html</a><br />
<br />
Wake me when it is over ...Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-5824660313967021040.post-20467184400950902542010-02-15T18:21:00.000-08:002010-02-15T18:29:39.547-08:00ColdFusion 9: CFPDF - Adding Headers and Footers (and a Few Quirks) - Part 2In <a href="http://cfsearching.blogspot.com/2010/02/coldfusion-9-cfpdf-adding-headers.html">Part 1</a> I posted some examples of text and image headers. CF9 also introduced some new constants for working with page numbers and labels. <br />
<br />
<span id="fullpost"><br />
<b>Page Numbers </b><br />
Though "Page X of Y" headers/footers are not exactly earth-shattering, it is a little easier to add them to existing pdf's with the new constants <span style="font-family: "Courier New",Courier,monospace;">_PAGENUMBER</span> and <span style="font-family: "Courier New",Courier,monospace;">_LASTPAGENUMBER</span>. <br />
<br />
<pre class="xhtml" name="code"><!--- Sample Document --->
<cfdocument format="pdf" name="pdfInput">
<cfloop from="1" to="10" index="x">
The Blank Page
<cfif x lt 10>
<cfdocumentitem type="pagebreak" />
</cfif>
</cfloop>
</cfdocument>
<!--- Add Page X of Y Footers --->
<cfpdf action="addFooter"
source="pdfInput"
name="pdfOutput"
text="Page _PAGENUMBER of _LASTPAGENUMBER"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
<b>NumberFormat </b><br />
With the help of the neat numberFormat attribute, you can change boring decimal page numbers into Roman numeral format (both upper and lower case). The supported values are: <span style="font-family: "Courier New",Courier,monospace;">LOWERCASEROMAN</span>, <span style="font-family: "Courier New",Courier,monospace;">NUMERIC</span>, <span style="font-family: "Courier New",Courier,monospace;">UPPERCASEROMAN. </span> <br />
<br />
<pre class="xhtml" name="code"><!--- Sample Document --->
<cfdocument format="pdf" name="pdfInput">
<cfloop from="1" to="10" index="x">
The Blank Page
<cfif x lt 10>
<cfdocumentitem type="pagebreak" />
</cfif>
</cfloop>
</cfdocument>
<!--- Add Page X of Y in Roman Numerals --->
<cfpdf action="addFooter"
source="pdfInput"
name="pdfOutput"
numberFormat="UpperCaseRoman"
text="Page _PAGENUMBER of _LASTPAGENUMBER"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
<b>Page Labels</b><br />
If your pdf contains custom Page Labels, you can also add that text to headers/footers using the _PAGELABEL and/or _LASTPAGELABEL constants. Obviously if your pdf doesn't have custom page labels, you will probably just see the default decimal page numbers instead. But I am pretty sure you can add custom labels with ddx (or alternately with iText).<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWazUIkFZOMwRkZg1VDMvTuVjzfHq1DhBGXGcTG3P7pZLt6jXVZ484FwGS8wjZ6TFdNEJf_RrbBKCo1Wtn7YSUPLf2yNiUq0ORLBRXdnKd34pcu6t1rXIx60_aty57zQkKA5HfflP3hMI/s1600-h/ColdFusion9_CFDPF_PageLabels.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWazUIkFZOMwRkZg1VDMvTuVjzfHq1DhBGXGcTG3P7pZLt6jXVZ484FwGS8wjZ6TFdNEJf_RrbBKCo1Wtn7YSUPLf2yNiUq0ORLBRXdnKd34pcu6t1rXIx60_aty57zQkKA5HfflP3hMI/s320/ColdFusion9_CFDPF_PageLabels.png" /></a></div><br />
<pre class="xhtml" name="code"><!--- Add Page Label to Header --->
<cfpdf action="addHeader"
source="testDocumentWithLabels.pdf"
name="pdfOutput"
text="<b>Label For Page (_PAGENUMBER):</b> _PAGELABEL"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
<b>Things You Cannot Do ...</b><br />
Perhaps this is an obvious point, but these settings have no affect on information already embedded within the pdf. In other words, you cannot change existing pdfs with page numbers already embedded in the file. However, if you use cfpdf to generate the headers/footers you can certainly remove them and add new ones.<br />
<br />
<b>Printing Headers/Footers</b><br />
For cases when you do not always want headers/footers to be printed, you can use the showOnPrint option to control visibility when printing. For example, you could tweak some of the previous snippets to display both the graphic and text headers on screen, but use showOnPrint="false" to make only the text header "printable". Obviously you can also use the pages attribute to apply that behavior only to select pages.<br />
<br />
<pre class="xhtml" name="code"><!---
Display the graphics header only on screen
--->
<cfpdf action="addHeader"
source="inputFile.pdf"
destination="pdfWithImageHeader.pdf"
topMargin="2.6"
leftMargin="0"
rightMargin="0"
align="center"
image="http://www.google.com/logos/olympics10-prsskating-hp.png"
overwrite="true"
showonprint = "false"
/>
....
<!---
Display the text header both on screen and when printed
--->
<cfpdf action="addHeader"
source="pdfWithImageHeader.pdf"
name="pdfOutput"
topMargin="1.7"
leftMargin="0"
rightMargin="0"
align="center"
text="#headerText#"
showonprint = "true"
/>
</pre><br />
As you can see, it is not extremely complex. But hopefully these examples make a good addendum to the limited documentation on the addHeader / addFooters features.<br />
<br />
</span>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-5824660313967021040.post-35259445102664333462010-02-15T14:59:00.000-08:002010-02-15T19:06:57.415-08:00ColdFusion 9: CFPDF - Adding Headers and Footers (and a Few Quirks) - Part 1With the explosion of new features in CFPDF, there is a lot to cover in the documentation. So it is understandably a little lacking on extra examples that go beyond the very basics. While belatedly exploring some of the extended features, I ran into a few quirks with addHeader / addFooter. Probably not news to some of you. But I thought I would share my examples and results in case it saves someone else a little time.<br />
<br />
<span id="fullpost"><br />
<b>Text Headers and Footers</b><br />
The basic concept of adding a textual headers and footers was simple enough. Just use the appropriate action, <span style="font-family: "Courier New",Courier,monospace;">addHeader </span>or <span style="font-family: "Courier New",Courier,monospace;">addFooter</span>, along with a simple <span style="font-family: "Courier New",Courier,monospace;">text </span>value. Setting the text alignment is equally simple, just provide an <span style="font-family: "Courier New",Courier,monospace;">align</span> value (left,center, right) with the desired margins. It is worth noting that if you omit the margin, CF uses its default. While I would not swear to it, the default seems to be 1 inch. At least for letter sized pages. <br />
<br />
<i>Note: If there is too much text to display with the margin bounds, it may flow right off the page. So if working with a lot of text, you may need to adjust it with some html/css.</i><br />
<br />
<pre class="xhtml" name="code"><!--- Align plain text header --->
<cfpdf action="addHeader"
source="inputFile.pdf"
name="pdfOutput"
align="left"
leftMargin="0.5"
text="Left Aligned. One-half (0.5) inch from left side"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><b>Styling Text Headers and Footers</b><br />
Applying html/css styles to the text is very similar to using a text watermark (added in CF 8.0.1). I did encounter one peculiarity with fonts. The names seem to be case sensitive. So on windows the font name "castellar" did nothing, but "Castellar" (with an uppercase "C") did produce the desired font. <br />
<br />
<br />
<pre class="xhtml" name="code"><!---
Add Styled header
--->
<cfsavecontent variable="headerText">
<span style="font-size: 25pt; font-family: Castellar;">
<b style="color: #0080ff;">Vancouver</b>
<b style="color: #0000ff;">2010</b>
<b style="color: #000080;">Olympics</b>
</span>
</cfsavecontent>
<cfpdf action="addHeader"
source="inputFile.pdf"
name="pdfOutput"
topMargin="1"
text="#headerText#"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
<b>HTML/CSS Limitations</b><br />
Not all html tags are supported, such as <div>, <a>, etcetera. I am assuming the same limitations apply to <span style="font-family: "Courier New",Courier,monospace;">action="addWatermark"</span>. So whatever quirks one has, the others probably do as well. <br />
<br />
Now in case you see an error message complaining about an "Invalid Document..", check the stack trace for the real error. Most likely it will say something about either an invalid tag or syntax error in your html/css.<br />
<br />
<br />
<pre>An error occurred during the ADDHEADER operation in the cfpdf tag.
Error: Invalid Document C:\ColdFusion9\wwwroot\test\pdf\inputFile.pdf specified for source or directory.
...
Stack Trace:
com.adobe.internal.pdftoolkit.core.exceptions.PDFInvalidDocumentException: Error during text formatting, internalProcessElement: InvalidInput: Illegal element tag "div".
....
</pre><br />
<b>Differences between Watermarks and Headers/Footers</b><br />
One difference I have noticed is <i>how</i> text is sized. CF 8.0.1 added the <span style="font-family: "Courier New",Courier,monospace;">width </span>and <span style="font-family: "Courier New",Courier,monospace;">height</span> attributes to support text watermarks. From what I have observed, the width and height values seem to control the size of the watermark text, rather than any explicit font sizes in html/css. Since the width and height attributes are not allowed with actions <span style="font-family: "Courier New",Courier,monospace;">addHeader </span>and <span style="font-family: "Courier New",Courier,monospace;">addFooter</span>, that text can be controlled by css font sizes. <br />
<br />
<b>Text and Image Opacity</b><br />
As with watermarks, you can also use the <span style="font-family: "Courier New",Courier,monospace;">opacity </span>attribute to change transparency of the header/footer. Simply use a value between 0 (transparent) and 10 (fully opaque). This example creates a styled footer whose text is 50% transparent. Opacity applies to both text and images.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYon4IFtfWZBAEn4HNnMCgtEz2dFg1Wi-ubcSUt8Y1NnFNRG8u94mxCI53HWuBO5yA879s1prG6FFWNcQh2LxIcRlwNNjVwcDgw-9Z-7ly4wZf8SiP2NJ6VGp6F_6EzJi1x6AutYY35Ek/s1600-h/ColdFusion9_CFPDF_SemiOpaqueTextFooter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYon4IFtfWZBAEn4HNnMCgtEz2dFg1Wi-ubcSUt8Y1NnFNRG8u94mxCI53HWuBO5yA879s1prG6FFWNcQh2LxIcRlwNNjVwcDgw-9Z-7ly4wZf8SiP2NJ6VGp6F_6EzJi1x6AutYY35Ek/s320/ColdFusion9_CFPDF_SemiOpaqueTextFooter.png" /></a></div><br />
<pre class="xhtml" name="code"><cfsavecontent variable="text">
<span style="font-size: 18pt; font-family: Century Gothic;">
<b style="color: #0080ff;">Vancouver</b>
<b style="color: #0000ff;">2010</b>
<b style="color: #000080;">Olympics</b>
</span>
</cfsavecontent>
<cfpdf action="addFooter"
source="inputFile.pdf"
name="pdfOutput"
bottomMargin="0.5"
text="#text#"
align="center"
opacity="5"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
<b>Image Headers and Footers</b><br />
Using images for headers and footers is also supported. The image source can be a file path, url, CF image object, array of bytes, or a base64 string (using isBase64="true"). <br />
<br />
Figuring out image headers/footers took a little while. It is not that the syntax is particularly cryptic. But it was not really clear to me, from the documentation, what should happen in various cases, such as when using an over-sized or undersized image. <br />
<br />
Based on my tests, if an image is too large to fit within the given margin dimensions, it is automatically re-sized. So if the margin is too small, you can end up with a skewed or squished image. I also observed some strange alignment issues in a few of my tests. Though I am not yet sure why. In any case, you may want to ensure your images are correctly sized beforehand, to avoid unexpected resizing behavior by cfpdf.<br />
<br />
If you view the image url below, its size should be around 7.4 x 2.4 inches. To center align the image, I used the code below. The top margin is set to "2.6" (inches). Tall enough to fit the image without resizing, plus a small amount of padding at the top. Both the left and right margin are set to zero (0) so the image is centered across the entire page width. <br />
<br />
Now you may notice that visually this image does not appear completely centered. But that is due to the layout of the underlying graphic. But you can adjust the position by changing the alignment and leftMargin.<br />
<br />
<br />
<pre class="xhtml" name="code"><!---
Sample Document
--->
<cfdocument format="pdf" fileName="inputFile.pdf" pageType="Letter" marginTop="2.6" overwrite="true">
<style type="text/css" media="screen">
div { font-family: Castellar; padding: 10px; color: 204a64; }
</style>
<cfloop from="1" to="10" index="x">
<div>
Alpine Skiing * Biathlon * BobSleigh * Cross-Country Skiing * Figure Skating *
Ice Hockey * Luge * Curling * Snowboard * Speed Skating
</div>
<cfif x lt 10>
<cfdocumentitem type="pagebreak" />
</cfif>
</cfloop>
</cfdocument>
<!---
Add image header (Note: Using image URL just for demonstration purposes)
--->
<cfpdf action="addHeader"
source="inputFile.pdf"
destination="pdfWithImageHeader.pdf"
topMargin="2.6"
leftMargin="0"
rightMargin="0"
align="center"
image="http://www.google.com/logos/olympics10-prsskating-hp.png"
overwrite="true"
/>
</pre><br />
<b>Multiple Headers / Footers</b><br />
It is also possible to layer headers/footers by adding text on top of an existing image. To demonstrate, read in the file created in the previous code snippet and adjust the margin size so the text is placed in the desired position. The results should look something like this<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ZfpN5FOM6kWJwwZ8GJgnK2zGdATiWEPF1GZmMxHYPNU8n7WaeosVEywIqmaQL_5TM9dGUjUP6-MBgz5t1Jz9M0OutQ93_4nbqPXCEtjGCzes8AckkzSUzQbwP3cNFuu_JgFwtb6wZ6E/s1600-h/ColdFusion9_CFPDF_TextAndImageHeader.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="176" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ZfpN5FOM6kWJwwZ8GJgnK2zGdATiWEPF1GZmMxHYPNU8n7WaeosVEywIqmaQL_5TM9dGUjUP6-MBgz5t1Jz9M0OutQ93_4nbqPXCEtjGCzes8AckkzSUzQbwP3cNFuu_JgFwtb6wZ6E/s320/ColdFusion9_CFPDF_TextAndImageHeader.png" width="320" /></a></div><br />
<br />
<pre class="xhtml" name="code"><!---
Add text header on top of existing image header
--->
<cfsavecontent variable="headerText">
<span style="font-size: 25pt; font-family: Castellar;">
<b style="color: #0080ff;">Vancouver</b>
<b style="color: #0000ff;">2010</b>
<b style="color: #000080;">Olympics</b>
</span>
</cfsavecontent>
<cfpdf action="addHeader"
source="pdfWithImageHeader.pdf"
name="pdfOutput"
topMargin="1.7"
leftMargin="0"
rightMargin="0"
align="center"
text="#headerText#"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
<b>Remove Headers/Footers</b> <br />
In the process of running all of these tests, I discovered something worth noting about images. Using an image obviously increases the file size. In some cases substantially (I have not gotten around to doing any size comparisons between cfpdf and iText yet). Since adding a new header/footer does <i>not </i>remove any existing ones, if you mistakenly add an image twice, it will increase the file size .. twice. But you can always use the <span style="font-family: "Courier New",Courier,monospace;">action="removeHeaderFooter"</span> first. (Yes, it does remove both headers and footers).<br />
<br />
<br />
<pre class="xhtml" name="code"><!---
Remove headers and footers
--->
<cfpdf action="removeHeaderFooter"
source="pdfWithImageHeader.pdf"
name="pdfOutput"
/>
<!--- Display results --->
<cfcontent type="application/pdf" variable="#ToBinary(pdfOutput)#" />
</pre><br />
On a side note, if you use cfdocument to generate header and footers, as opposed to the new cfpdf actions, be aware there is a "Known Issue" mentioned in the CF9 release notes.<br />
<br />
<br />
<blockquote style="font-family: "Courier New",Courier,monospace;"><b>Bug 76078:</b><br />
Headers or footers added while generating PDF using cfdocument are not marked and are not recognized as headers or footers. Therefore, cfpdf action="removeheaderfooter" will not be able to remove them from source PDF.</blockquote><br />
<a href="http://cfsearching.blogspot.com/2010/02/coldfusion-9-cfpdf-adding-headers-and.html">ColdFusion 9: CFPDF - Adding Headers and Footers (and a Few Quirks) - Part 2</a><br />
</span>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-5824660313967021040.post-15838844240604003252010-02-11T22:57:00.000-08:002010-02-12T00:46:09.057-08:00ColdFusion: Adding a Link to an Existing PDF with iTextA recent question on <a href="http://stackoverflow.com/">stackoverflow.com</a> asked how to add a hyperlink to an existing pdf with iText. There is most definitely more than one way to do it, and quite possibly better methods than the one mentioned here. But as it uses a few interesting techniques I thought I would share it. Of course any comments or improvements are always welcome. <br />
<br />
<span id="fullpost"><br />
As usual, first open the source pdf with a reader object, and use a stamper to prepare the output file for writing. With that out of the way, you can move on to creating the hyperlink. <br />
<br />
Given that CF8 and CF9 use older versions of iText, I decided the simplest method would be to use a <span style="font-family: "Courier New",Courier,monospace;">Chunk</span> object. If you are not familiar with Chunks, they are a low level object used to represent a bunch of characters all having the same properties (font, color, etcetera). So first initialize a Chunk with whatever text you want to use for the link. Then use <span style="font-family: "Courier New",Courier,monospace;">setAnchor()</span> to specify the link url.<br />
<br />
<pre class="xhtml" name="code"><cfscript>
pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
outStream = createObject("java", "java.io.FileOutputStream").init( outputPath );
pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( pdfReader, outStream );
chunk = createObject("java", "com.lowagie.text.Chunk").init("Rage Against the Machine (on Wikipedia)");
chunk.setAnchor("http://en.wikipedia.org/wiki/Rage_Against_the_Machine");
</cfscript>
</pre><br />
Since the default font is pretty bland, you will probably want to select a different font for the link. There are several ways to work with fonts. But in this example I defined a <span style="font-family: "Courier New",Courier,monospace;">BaseFont </span>by passing in the path to the physical font file, the desired encoding and a flag to ensure the font is embedded. The BaseFont definition is then used to create a <span style="font-family: "Courier New",Courier,monospace;">Font </span>object with the desired settings (such as size, color and style) and applied to the Chunk object. <br />
<br />
<br />
(On a side note, I was going to use the basic <span style="font-family: "Courier New",Courier,monospace;">arial.ttf </span>font. But while perusing the windows/font directory, I was amused to find an odd font named <i>Rage</i> and promptly decided I had use it for this entry instead) <br />
<br />
<br />
<pre class="xhtml" name="code"><cfscript>
// define an embedded font
BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
Font = createObject("java", "com.lowagie.text.Font");
bf = BaseFont.createFont("c:/windows/fonts/rage.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
// create the main font object
textColor = createObject("java", "java.awt.Color").decode("##084f5a");
textFont = Font.init(bf, 18, Font.UNDERLINE, textColor);
chunk.setFont( textFont );
</cfscript>
</pre><br />
<br />
Now to position the Chunk, I decided to use a <span style="font-family: "Courier New",Courier,monospace;">ColumnText </span>object. As the name implies, it is used to layout text in column format. Though used quite simply here, the ColumnText class is capable of some pretty complex operations. <br />
<br />
To create a ColumnText object you must pass in a <span style="font-family: "Courier New",Courier,monospace;">PdfContentByte </span>object. In loose terms that is the canvas where the text will be drawn. In this example, the link is added to the foreground. So <span style="font-family: "Courier New",Courier,monospace;">getOverContent()</span> is used to grab the canvas of the target page from the stamper object and then passed into the ColumnText object. Finally the Chunk is added to the ColumnText object for rendering. <br />
<br />
The next to last step is to define the dimensions of the column. Once the dimensions are defined, <span style="font-family: "Courier New",Courier,monospace;">ColumnText.go()</span> is used to draw the link onto the pdf. (I will not go into the details of positioning here. But if you are unfamiliar with it, <a href="http://cfsearching.blogspot.com/2009/07/coldfusion-basic-example-positioning.html">this entry on buttons</a> describes the typical way in which objects are positioned in iText.) <br />
<br />
<div style="color: red;">Note: This snippet uses deprecated methods for CF8 compatibility. For a CF9 compatible version, see the end of entry</div><br />
<pre class="xhtml" name="code"><cfscript>
cb = pdfStamper.getOverContent(1);
ct = createObject("java", "com.lowagie.text.pdf.ColumnText").init(cb);
ct.addElement( chunk );
// set the column dimensions
page = pdfReader.getPageSize(1);
llx = page.right()- 325;
lly = page.top() - 36;
urx = page.right();
ury = page.top() - 8;
ct.setSimpleColumn(llx, lly, urx, ury);
// write the text
ct.go();
</cfscript>
</pre><br />
Once you close the stamper, the resulting pdf should contain a cool looking link in the top right. Minus the thematic image of course ..<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYQWmq4tBNnUDNRG9SGE11Po1H2TpSU9P91VVLt3XDGAVQZwgdXWSZETm273FFPtdNwwSCs-A4OUi0EfKHgi2k-Bk2t5nGz5PNNvplENuJDafnR3aL9IaPwZ_kT_8DswMVzGc5qguZm34/s1600-h/ColdFusion_AddLinkToExistingPdf_WithItext.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYQWmq4tBNnUDNRG9SGE11Po1H2TpSU9P91VVLt3XDGAVQZwgdXWSZETm273FFPtdNwwSCs-A4OUi0EfKHgi2k-Bk2t5nGz5PNNvplENuJDafnR3aL9IaPwZ_kT_8DswMVzGc5qguZm34/s320/ColdFusion_AddLinkToExistingPdf_WithItext.png" /></a></div><br />
<div style="color: #38761d;"><b>Complete Code (ColdFusion 8)</b></div><pre class="xhtml" name="code"><cfscript>
inputPath = ExpandPath("./myDocument.pdf");
outputPath = ExpandPath("./myDocumentWithLink.pdf");
try {
// initialize objects
pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
outStream = createObject("java", "java.io.FileOutputStream").init( outputPath );
pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( pdfReader, outStream );
// create a chunk with a anchor (ie hyperlink)
chunk = createObject("java", "com.lowagie.text.Chunk").init("Rage Against the Machine (on Wikipedia)");
chunk.setAnchor("http://en.wikipedia.org/wiki/Rage_Against_the_Machine");
// define an embedded font
BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
Font = createObject("java", "com.lowagie.text.Font");
bf = BaseFont.createFont("c:/windows/fonts/rage.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
// create the main font object
textColor = createObject("java", "java.awt.Color").decode("##084f5a");
textFont = Font.init(bf, 18, Font.UNDERLINE, textColor);
// apply the font to the chunk
chunk.setFont( textFont );
// prepare to write the link onto the *first* page only
cb = pdfStamper.getOverContent(1); // first page
ct = createObject("java", "com.lowagie.text.pdf.ColumnText").init(cb);
ct.addElement( chunk );
// position link near top right
// note: using deprecated versions of getRight() and getBottom()
page = pdfReader.getPageSize(1);
llx = page.right()- 325;
lly = page.top() - 36;
urx = page.right();
ury = page.top() - 8;
// initialize column dimensions
ct.setSimpleColumn(llx, lly, urx, ury);
// write the text
ct.go();
}
catch (java.lang.Exception e) {
// Save the error object and use cfdump _outside_
// the cfscript block to display the full error detail
WriteOutput("ERROR: "& e.message &"<hr />");
WriteOutput("DETAIL: "& e.detail);
}
// closing the stamper generates the output file
if (IsDefined("pdfStamper")) {
WriteOutput("Closing pdfStamper ..<hr />");
pdfStamper.close();
}
// also ensure the outstream is always closed
// to avoid locked files if an error occurs early on ..
if (IsDefined("outStream")) {
WriteOutput("Closing outStream ..<hr />");
outStream.close();
}
WriteOutput("Output file generated: "& outputPath );
</cfscript>
</pre><br />
<div style="color: blue;"><b>Complete Code (ColdFusion 9)</b></div><pre class="xhtml" name="code"><cfscript>
inputPath = ExpandPath("./myDocument.pdf");
outputPath = ExpandPath("./myDocumentWithLink.pdf");
try {
// initialize objects
pdfReader = createObject("java", "com.lowagie.text.pdf.PdfReader").init( inputPath );
outStream = createObject("java", "java.io.FileOutputStream").init( outputPath );
pdfStamper = createObject("java", "com.lowagie.text.pdf.PdfStamper").init( pdfReader, outStream );
// create a chunk with a anchor (ie hyperlink)
chunk = createObject("java", "com.lowagie.text.Chunk").init("Rage Against the Machine (on Wikipedia)");
chunk.setAnchor("http://en.wikipedia.org/wiki/Rage_Against_the_Machine");
// define an embedded font
BaseFont = createObject("java", "com.lowagie.text.pdf.BaseFont");
Font = createObject("java", "com.lowagie.text.Font");
bf = BaseFont.createFont("c:/windows/fonts/rage.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
// create the main font object
textColor = createObject("java", "java.awt.Color").decode("##084f5a");
textFont = Font.init(bf, 18, Font.UNDERLINE, textColor);
// apply the font to the chunk
chunk.setFont( textFont );
// prepare to write the link onto the *first* page only
cb = pdfStamper.getOverContent(1); // first page
ct = createObject("java", "com.lowagie.text.pdf.ColumnText").init(cb);
ct.addElement( chunk );
// position link near top right
// note: using deprecated versions of getRight() and getBottom()
page = pdfReader.getPageSize(1);
llx = page.getRight()- 325;
lly = page.getTop() - 36;
urx = page.getRight();
ury = page.getTop() - 8;
// initialize column dimensions
ct.setSimpleColumn(llx, lly, urx, ury);
// write the text
ct.go();
}
catch (java.lang.Exception e) {
// Save the error object and use cfdump _outside_
// the cfscript block to display the full error detail
WriteOutput("ERROR: "& e.message &"<hr />");
WriteOutput("DETAIL: "& e.detail);
}
// closing the stamper generates the output file
if (IsDefined("pdfStamper")) {
WriteOutput("Closing pdfStamper ..<hr />");
pdfStamper.close();
}
// also ensure the outstream is always closed
// to avoid locked files if an error occurs early on ..
if (IsDefined("outStream")) {
WriteOutput("Closing outStream ..<hr />");
outStream.close();
}
WriteOutput("Output file generated: "& outputPath );
</cfscript>
</pre><br />
</span>Unknownnoreply@blogger.com0