Skip to content


Washing Client Certs in ColdFusion with SOAP – Part 3

Continuing from part 2, I’ve got my SOAP request working, but the solution is limited to the OS platform and I don’t like the security implications. What I would really like is a solution that works without the need of a custom tag that is OS dependent. If only CFHTTP wasn’t broken.

I decided to look at what I already had available; namely Java. ColdFusion runs on top of Java and it’s possible to create Java objects from within ColdFusion.

I’ll spare you the grueling details, but I did get caught up at one point playing around with the Java keystore and importing my client certificate into that keystore. Using things like keytool and OpenSSL I tore apart and reconstructed my client certificate and my Java keystore. Ultimately I didn’t need to do any of that and should have just left the keystore alone.

The path to enlightenment began when I found an example of Java socket operations using  the javax.net.ssl.SSLContext object. The example code I was looking at used this object to create regular sockets, not SSL-enabled sockets. I looked into this object and found a few more examples using it to create SSL connections. A bit of back and forth between these examples and the online Java API spec resulted in the discovery that I could load a PKCS12 file as a keystore and pass that to the SSLContext object then create sockets from that SSLContext. This looked promising.

After a bit of trial and error I finally had something that worked!

Here’s the relevant portion of code. I’ll go line-by-line afterwards.

<cfscript>
  // 01: create input file stream from certificate
  variables.ksf = CreateObject( "java", "java.io.FileInputStream" ).init( variables.cert_file );

  // 02: create keystore object from certificate
  variables.ks = CreateObject( "java", "java.security.KeyStore" ).getInstance( "PKCS12" );
  variables.ks.load( variables.ksf, JavaCast( "String", variables.cert_password ).toCharArray() );

  // 03: create key manager factory out of keystore
  variables.kmf = CreateObject( "java", "javax.net.ssl.KeyManagerFactory" ).getInstance( "SunX509" );
  variables.kmf.init( variables.ks, JavaCast( "String", variables.cert_password ).toCharArray() );

  // 04: create SSL context object using key manager factory
  variables.sslc = CreateObject( "java", "javax.net.ssl.SSLContext" ).getInstance( "TLS" );
  variables.sslc.init( kmf.getKeyManagers(), JavaCast( "null", "" ), JavaCast( "null", "" ) );

  // 05: get SSL socket from SSL context
  variables.factory = sslc.getSocketFactory();

  // 06: connect to the server via SSL
  variables.sock = variables.factory.createSocket( variables.server_address, variables.server_port);
  variables.sock.startHandshake();

  // 07: create input and output streams to read from and write to the socket
  variables.sout = variables.sock.getOutputStream();
  variables.out = createObject( "java", "java.io.PrintWriter" ).init( variables.sout );
  variables.sinput = variables.sock.getInputStream();
  variables.inputStreamReader = createObject( "java", "java.io.InputStreamReader" ).init( variables.sinput );
  variables.input = createObject( "java", "java.io.BufferedReader" ).init( variables.InputStreamReader );

  // 08: send the HTTP request over the socket
  variables.out.println( Trim( variables.request_header ) );
  variables.out.println();
  variables.out.println( Trim( variables.soap_envelope ) );
  variables.out.println();
  variables.out.flush();

  // 09: read the response from the server
  variables.result = "";
  do {
    variables.line = input.readLine();
    variables.lineCheck = IsDefined( "variables.line" );
    if ( variables.lineCheck ) {
      variables.result = variables.result & variables.line & Chr(13) & Chr(10);
    }
  } while ( variables.lineCheck );

  // 10: close the connection
  variables.sock.close();
</cfscript>

So let’s work through this.

01: The variable variables.cert_file contains the full path to the client certificate. This line opens that file for reading.

02: This creats a java.security.KeyStore object that expects a PKCS12 format and then loads the client certificate into the keystore. Note that the second parameter of the KeyStore.load() method is the password of the PKCS12 file. You must pass this as a character array and not as a string, which is why the password must be cast as a String object whose toCharArray() method is called to obtain that character array.

03: This creates a javax.net.ssl.KeyMangerFactory object which is used by the SSLContext object to deal with the certificates in the keystore. Note that the second parameter of the init() is the password to the certificate in character array format. You must provide the password twice, once when creating the keystore, and then again creating the key manager factory.

04: Here we create the javax.net.ssl.SSLContext object. The getInstance() call tells what protocol to use with this object, in this case “TLS“. (Note: “SSLv3” also works. No idea which one I should be using. If one fails, try the other.) Once the object is instantiated we initialize it with a key manager from the key manager factory. The second and third parameters of the init() method could point to a trust manager and a source of randomness. It doesn’t appear that either is needed (this is what I saw in other examples) so I pass null values. Note that you have to use JavaCast() to pass a null.

05: Get a socket factory object from the SSLContext object. This factory will be used to create individual sockets using the parameters established with the SSLContext object.

06: Create a single socket from the socket factory by passing the server address (variables.server_address) and port (variables.server_port) we want to connect to. Then call startHandshake() which will create the connection to the server using SSL.

07: Create objects needed to read from and write to the socket we’ve created.

08: Here we send our SOAP envelope to the server. Note that since we’re doing everything by hand we also have to send the proper HTTP headers. Both the variables.request_header and variables. soap_envelope variables were created using the CFSAVEDCONTENT tag as shown in part 1. You want to create the envelope first so you know the correct content-length value for the HTTP header. Since I decided to stick with SOAP 1.1 the header also contains the SOAPAction field and the content-type is set to text/xml.

Here is an example of how I created the headers:

<cfsavecontent variable="variables.request_header">
POST <cfoutput>#variables.server_request_path#</cfoutput> HTTP/1.0
Host: <cfoutput>#variables.server_address#</cfoutput>
User-Agent: SOAP Washer/1.0
SOAPAction: "<cfoutput>#variables.soap_action#</cfoutput>"
Content-Type: text/xml
Content-Length: <cfoutput>#Len( Trim( variables.soap_envelope ))#</cfoutput>
</cfsavecontent>

Standard HTTP/1.0 headers. You can make the User-Agent field just about whatever you want.

Also worth mentioning is that I didn’t try to combine the two (HTTP headers and envelope) into a single variable. This is because how CFSAVEDCONTENT handles newline characters is different between ColdFusion 8 and ColdFusion 9. By keeping them separate this code is able to operate under either version.

The empty println() commands are there to send a newline. And the flush() call simply sends anything left in the buffers, thus completing the sending of our request.

09: This is where we read the response from the server. The response can only be read one line at a time, so we need a loop to keep reading one line at a time and appending it to the variable where we’ll store the entire response (variables.result).

A tricky thing working between ColdFusion and Java objects is that if you assign the return value of a Java object method to a ColdFusion variable and that return value is null then the ColdFusion variable is unset or deleted. Since the readLine() method returns null when there’s nothing more to read, the only way to know we’ve completed reading the response is to test if variables.line, where we store the return value of readLine(), still exists. If it doesn’t, we’ve reached the end of the response.

10: Close the connection

The ColdFusion variable variables.result will contain the entire response from the server, including the response HTTP headers. Meaning if you want to get a proper XML object from xmlParse() you’re going to need to first strip away those headers.

<cfset variables.temp = REFindNoCase( "content-length: ([0-9]+)", variables.result, 1, "True" )>
 <cfif variables.temp.len[1] GT 0>
  <cfset variables.length = Val( Mid( variables.result, variables.temp.pos[2], variables.temp.len[2] ))>
  <cfset variables.xmlResult = Trim( Right( RTrim( variables.result ), variables.length ))>
</cfif>

This should do the trick. Although you may want to throw in some code to strip out the status code to ensure a good transaction.

And there you have it. Handling transactions with a third-party web site using client certificates under ColdFusion using only native ColdFusion objects.

Posted in Uncategorized.


Washing Client Certs in ColdFusion with SOAP – Part 2

In part 1 I introduced you to basic SOAP consumption in ColdFusion. Let’s see where things go from there.

The task at hand was to integrate our system with a third-party site. I authenticate users locally, request a token from the third-party site that allows the user to access said site, and then redirect the user to that site with the token passed on the URL.

But how does the third-party site know to trust my requests and not the requests from other people? After all you don’t want people forging requests to access the third-party site as some other user. The solution for this third-party site is client certificates. A client certificate is either issued by the third-party site or you provide your public certificate to the third-party site and the third-party’s site is configured to trust the certificate. SOAP requests are then made over SSL using this certificate to confirm your identity and to encrypt the channel.

Starting with ColdFusion 8 CFHTTP accepts two parameters to afford the use of client certs, clientCert and clientCertPassword. The clientCert parameter points to a PKCS12 formatted file containing your public and private keys and, possibly, the certificate chain from the root certificate authority (such as VeriSign or Thawte) down to whoever issued your client certificate. The clientCertPassword parameter contains the password with which the PKCS12 file is encrypted.

A note to ColdFusion developers:
The PKCS12 must be encrypted with a password! For whatever reason (I believe a limitation in the underlying java.security.KeyStore object) your cert must have a password. This is never explicitly stated in the ColdFusion 8 documentation.

I obtained my client certificate and set out to start writing code to talk with this third-party.

The first problem I encountered was I would not be able to use the CreateObject() method covered in part 1. The reason being that there was no way to provide my client certificate to the object. So it’s back to the CFHTTP method.

The second problem I encountered had nothing to do with ColdFusion and everything to do with the documentation provided by the third-party. Turns out the header for the SOAP request changed considerably during development. I had been given some early development documents that did not reflect the current header structure. Once I realized the problem and obtained the current documentation I was able to correctly construct my SOAP request’s envelope header and…

I got another error.

But this time it wasn’t a normal SOAP request error. The CFHTTP object’s filecontent value contained nothing more than “Connection failure”. But the HTTP status code was 200, which indicates a successful request. Previous SOAP request errors would return a 403 status code. This was odd.

In searching the Adobe forums and the internet in general I found sparse comments about possible problems with CFHTTP handling SSLv3 sessions, although there wasn’t any sort of official comment or response to the few reports of this problem. I loaded up the developer edition of ColdFusion 9 on my own computer to see if perhaps this problem had been resolved with the latest copy of ColdFusion. It had not.

To confirm this as a problem with ColdFusion and not my client cert, or my SOAP request, I installed Apache and PHP locally and ran the equivalent PHP code. The PHP code worked perfectly. I started trying to do as much comparison between the two platforms as I could. I event went so far as to run a packet capture on the PHP and CF requests (pointing them at a dummy, local page that wasn’t encrypted so I could see the requests) and compared them to make sure everything was the same, which they were.

Eventually I found a post online that mentioned CFHTTP wasn’t up to the job, but a third-party custom tag written in C++ did work just fine. That custom tag is CFX_HTTP5. I downloaded a demo copy and installed it locally. How CFX_HTTP5 handles client certs is different from CFHTTP. Rather than simply pointing the tag at the client cert, I had to install the client cert into the Windows certificate store and then point the tag at the store. There is a bit of work involved with it, although nothing too difficult and it’s all covered in the CFX_HTTP5 documentation.

Once I had the tag and the certificate imported into the local Windows certificate store, I rewrote the CFHTTP call using CFX_HTTP5 and it worked! The SOAP envelope was the same, the headers were the same, the only difference between the two was the logic underlying the tags.

Something is broken with CFHTTP and it can’t be used to do some operations using client certificates. But at least there is an alternative.

However I didn’t like the alternative.

First, it’s a Windows-only solution. We’re out of luck if we’re running ColdFusion on a Linux machine.

Secondly, the client cert must be stored in a place that the ColdFusion process has permissions to access, and it is accessed without needing to know the client cert password. The result is that any person with permission to create CFM scripts that are executed under this process could authenticate against this third-party web site. In a shared hosting environment this can create a serious security issue. The only solution is to separate the process, but that probably means a separate server and a new OS license and hardware costs. If that option isn’t available you have some problems. You might register the CFX with a name that contains random characters and hope none of the users in the shared environment know how to enumerate registered custom tags. I’m not sure if that is possible, but I’m willing to bet it is.

Long story short, I don’t like the CFX_HTTP5 solution.

So I went back to the drawing board.

Posted in ColdFusion.


Washing Client Certs in ColdFusion with SOAP – Part 1

Recently I was asked to look into integrating our systems with an external application via the third-party’s single sign-on system. The way it works is simple enough. We would have an application that authenticates the user through our system and then sends a request to the third-party asking for a token to sign the user into their system. The third-party would return the token that I then give to the end-user and redirect them to the third-party web site. This token is how the third-party would authenticate the user into their system. (The token is nothing more than a long string of characters that is passed on the URL of the redirect.)

The method of obtaining the token is also fairly simple. The application submits a SOAP request over an SSL session to the third-party’s authentication server and that server would respond with the token. SOAP, for all the technical specs and other crap, is very straightforward. It’s a simple XML document consisting of a root element called ENVELOPE which contains two children called HEADER and BODY. The header isn’t always required and the body typically contains elements with the names of various fields the SOAP function your calling requires with each element containing the value of that parameter. Very straightforward.

A SOAP request in ColdFusion couldn’t be simpler, especially with the CFSAVECONTENT tag. Simply construct your envelope inside a CFSAVECONTENT tag and then use CFHTTP to submit the request. It looks a little something like this:

<cfsavecontent variable="variables.soap">
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetCurrentTime xmlns="http://ws.historicaloptiondata.com/" />
  </soap:Body>
</soap:Envelope>
</cfsavecontent>

<cfhttp
  url="http://ws.historicaloptiondata.com/Service.asmx"
  method="POST"
>
  <cfhttpparam type="header" name="SOAPAction" value="""http://ws.historicaloptiondata.com/GetCurrentTime""" />
  <cfhttpparam type="header" name="Content-Length" value="#Len( Trim( variables.soap ))#" />
  <cfhttpparam type="xml" value="#Trim( variables.soap )#" />
</cfhttp>

<cfdump var="#xmlParse( cfhttp.filecontent )#" />

You should be able to plug this code into a CFM file and run it without having to touch a thing and you should see the CFDUMP of an XML object. (I say “should” because xmlParse() seems to first try and open a file with the name of the content of the passed variable and, when that fails, treat the passed value as an XML document itself. This can trigger errors and make it unusable if you employ any sort of file operation restrictions on your server. In which case modify the code to remove the xmlParse() call and just dump the cfhttp.filecontent.)

A few notes about this code SOAP in general.

  • This is an example of a SOAP 1.1 request. There is another, slightly different format known as SOAP 1.2. The major differences between the two are that the content-type for 1.1 is text/xml, but for 1.2 it is application/soap+xml. Also the SOAPAction HTTP header is no longer needed in 1.2.
  • The SOAPAction HTTP header’s value must be wrapped in double-quotation marks. And sometimes the first character after the open quotes will need to be a pound (#) symbol. This means ColdFusion programmers will need to be certain they escape these special characters in their values.
  • If the CFHTTPPARAM type “xml” is present, CFHTTP automatically sets the content-type to text/xml. I am not sure if it’s possible to override this, but I believe not, therefore you’re almost always going to have to stick with SOAP 1.1 if you’re using CFHTTP for your SOAP requests.
  • You must Trim() the variables.soap variable! The newline at the beginning of the value, which exists because there is a newline immediately after after CFSAVEDCONTENT tag (for visual formatting purposes) will make the XML document you’re sending an invalid XML document and result in errors.

Now comes the WSDL file. A WSDL file is an XML documents that describes the functions and parameters of said functions available through a SOAP service. The CreateObject() function has a “webservice” object type which will consume a WSDL file and create an object with all the available functions offered by the SOAP service. All the stuff with XML and ENVELOPES and CFHTTP becomes transparent and, as it turns out, SOAP can be simpler than my previous example. The above code can be reduced to the following using CreateObject():

<cfset ws = CreateObject( "webservice", "http://ws.historicaloptiondata.com/Service.asmx?WSDL" ) />
<cfset ws.getCurrentTime() />
<cfdump var="#GetSOAPResponse( ws )#" />

This makes this life much simpler for ColdFusion programmers. No need to worry about what version of SOAP you’re using or what URLs you need to submit your request to, the formatting of your SOAP envelope, the SOAPAction HTTP header variable, etc. It’s all taken care of for you by ColdFusion.

So this integration I was asked to do should be a piece of cake, right?

Wrong.

Simple SOAP is simple. Complex SOAP… well, you’ll see in Part 2.

Posted in ColdFusion.


Acrobat Open Parameters

Recently I was tasked with updating the college catalog. Not the content, that was already generated for us and delivered in PDF format by the publisher. I needed to update the web site and the links into the PDFs. Most of the links went to just the PDF itself, but a few pointed to named destinations inside the PDF. These bookmarks don’t exist in the documents we receive from the publisher — we have to create them ourselves.

But I don’t have Acrobat Pro installed on my computer. If the college orders me a copy today we’ll probably have the install DVD  next week, but I don’t want to wait that long.

So what options are at my disposal? Is it possible to link to a specific location inside a PDF without having to create bookmarks inside the PDF?

After scouring the interwebs a bit I find this document on Acrobat Open Parameters.

I can link to a specific page by simply appending to the PDF’s url #page=2 and, magically, Acrobat Reader will load the PDF starting on page 2.

Cool.

But some of the items I’m linking to inside the PDF start halfway down the page. A user clicking on the link might not see the section they’re being linked to within the top area of the page that is visible on their screen and immediately assume they’ve clicked on the wrong thing.

So can we do something more with these URL parameters?

Yes!

The VIEW parameter allows you to define how the PDF is scaled in the browser (fit to window, fit horizontally to window, fit vertically to window, etc.) as well as where on the page to start based on a x,y coordinate system.

In this case I want to set VIEW to FitH (Fit Horizontally) meaning the PDF will size itself so the width of the document is the same as the width of the browser’s viewport. This also allows us to specify a Y component as to the position of the page displayed when the PDF is loaded. Combine this with the PAGE parameter and you can define where in a given page the PDF will start when loaded.

So a link like document.pdf?page=7&view=FitH,300 will load the PDF on page 3 at 300 units down the page.

Now what unit are we talking about? And is 300 units on my screen the same as 300 units on someone else’s screen? I have no idea. The document is light on details, but I believe the coordinate system is based on the parameters of the PDF itself and not the end-user’s screen. So with a little bit of trial-and-error I find just the right number to use with the FitH view and voilà!

I can now link to the Masters of Arts in Teaching program within the Secondary Education section of the catalog. And I did it without having to pay for some bloated Adobe software.

Granted, with named destinations updating the catalog next year wouldn’t require that I touch those links (just the PDFs we get from the publisher) and maybe it’s better from a semantics standpoint, but this was quick and easy.

Will it work with PDF readers other than Acrobat? I don’t know. If you’ve got one installed try it out and let me know. The fact that these “open parameters” have been around since at least Acrobat 6 it’s possible other PDF viewers have incorporated this feature into their application.

But, hey, it works with the most popular and free PDF reader and it didn’t require expensive software to accomplish. That’s good enough for me.

Posted in Uncategorized.


Password Masking

I’ve written up a little page on password masking to read at your convenience.

This was born out of an article written by Jakob Nielsen titled “Stop Password Masking“. In it he argues that masking passwords does little to really prevent password theft, but does quite a lot of harm in the form of user errors as they are unable to tell if they’ve made a mistake in typing their password. Security pro Bruce Schneier weighed in as well, initially agreeing with Nielsen, then rethinking his ideas a bit.

I decided to see what we, on the web side of things, might be able to do to make it a little easier on users, but still stay secure.

Posted in Uncategorized.

Tagged with .


New Gargoyles Comic

A shameless plug for Gargoyles: Clan Building Vol. 2, which contains issues 6-12 of the Gargoyles comic. The comic is written by series co-creator Greg Weisman and it continues where the TV series left off after season two. It’s an awesome book. Very rich. Very dense. This is not just for kids, it appeals to adults as well. It’s well worth your time to check it out.

Sadly this will probably be the last Gargoyles story told in an official capacity. The book’s publisher, SLG, were not able to renew their license with Disney after Disney increased the license fee on Gargoyles. Some rumors are afoot that say if the book sells really well then SLG might decide to take a chance at renewing the license (and risk considerable money if the book doesn’t sell).

But I’ve got a feeling this is going the way of the DVDs, which ended after the first half of season 2 was released 4 years ago and fans were left without the second (and concluding!) half of season 2.

Although it seems Disney is quite good at screwing up quality products other than Gargoyles. For example they’ve yet to green-light a third season of Spectacular Spider-Man. A complete no-brainer and also a show produced by Greg Weisman. Makes me wonder if there’s some sort of bad relationship between Weisman and Disney.

Posted in Uncategorized.


Wordpress Exploit – PHP Programming

A recent exploit was discovered for all versions of Wordpress prior to and including 2.8.3. The vulnerability is in how variables are checked while processing a password reset request. The offending code is quite simple and by itself looks very innocent:

if ( empty( $key ) )

If the $key variable is empty, an error is generated. If $key is not empty it’s passed to a SQL query that looks up the user whose key is being reset. Seems innocent enough, right?

The variable $key is a sanitized copy of $_GET['key']. And what happens if the value passed on the url for key is an array?

Well the sanitization step is a simple preg_replace, like so:

$key = preg_replace('/[^a-z0-9]/i', '', $key);

The PHP documentation states that if the subject ($key) is an array, the return value will be an array as well. It also states that if no matches are found, the original subject is returned. Since $key is empty to begin with, thus no matches will be found, a copy of $key is returned. Essentially $key remains unchanged. So what happens when you pass an empty array to PHP’s empty() function? Well the documentation says it should return FALSE. So what’s the problem?

The problem is the array isn’t really empty. It contains a single node which contains an empty string. So the empty() call returns FALSE, thus no error is generated for an empty $key.

Surely the SQL check will return no entries since there is no key to search for, right?

Wrong. Here’s the code:

$user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->users WHERE user_activation_key = %s", $key));

The $wpdb->prepare()  is used to insert values into SQL statements. $key is treated like an empty string. Therefore the SQL that is generated looks like:

SELECT * FROM wp_users WHERE user_activation_key = ''

And that query will return every user who has an empty activation key — that’s everyone not currently trying to reset their password. Since the first row returned is the record that gets reset, you will almost always be resetting the admin account. If you keep making the same request over and over you will eventually wind up resetting the password of every user in the system!

Reset passwords are auto-generated and then e-mailed out to the users. This sort of attack could be used as a weak style of denial-of-service (or pain-in-the-ass attack as I prefer to call it) or an attacker who has access to a victim’s e-mail account (or can packet sniff their e-mail downloading)  could take control of the blog.

A fix is already on the way as you can see by viewing wp-login.php in Wordpress’ CVS. The fix seems simple enough:

if ( empty( $key ) || !is_string( $key ) )

So $key must be a string and it must be empty. Seems very straightforward. Although I would prefer they went the added step of simply modifying the SQL query to check against only those user_activation_code fields that are not empty strings. In other words add something like

$user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->users WHERE user_activation_key = %s AND user_activaction_key <> ''", $key));

That would more correctly fix the issue, as it would protect against even those attacks that are somehow able to bypass the initial empty() check.

Wordpress appears to also be adding a username to the password reset request so that simply passing an empty key on the URL and hitting reload a bunch of times won’t result in everyone having their password reset — probably something they should have done in the first place.

So there it is. Patch your Wordpresses, either by manually editing wp-login.php or waiting for the official patch to come out sometime later this week.

Posted in Uncategorized.


Contuing To Prove My Now 5+ Year Old Argument

Flash (and JavaScript) should be used as tools to enhance an existing web site, not replace it. If your web site is not accessible to a user who has neither Flash or JavaScript support then you are doing something very wrong.

It appears that there is a new Flash vulnerability that can take over your computer. Awesome.

So how does one protect themselves from such a thing? Well, you could install noscript. It’s a plugin that will disable JavaScript, Flash and other embedded objects from web sites until you explicitly set permissions to enable them for that web site. Noscript won’t save you from web sites that you’ve already set permissions for — so it’s not a catch-all, but it will save you from links to web sites you’re unsure about. And it has the nice side-effect of disabling any JavaScript-powered advertisements.

What this means for you, the web developer, is that it’s time to start assuming the first time people access your web site they will have Flash, JavaScript, etc. disabled. This means you need to make sure your site is still accessible even under those conditions. Otherwise the user will have no reason to trust your web site if he or she can’t access it in the first place. If they don’t trust it they’ll never (well, they SHOULD never) enable script and emedded objects to run from your domain. Thus you will lose customers.

HTML, CSS and text. That should be the core of every web site. Flash and embedded objects to enhance the site, but not to provide the only means to access your content.

That is unless you’re one of a few embedded object kind of web site, like YouTube. But for the majority of us, that won’t be the case. And even so, you can always provide download links to the videos if the user doesn’t have Flash enabled to view them from within your web page.

<strong>TL;DR: Web sites should be accessible even without JavaScript and Flash.</strong>

Posted in Uncategorized.


Equal height columns with borders (and a gap).

I was recently reading an article over at Smashing Magazine about new CSS techniques. One of them was about columns with equal heights and a border. The solution presented requires a background image to be used to fake the gap between the two columns. As I prefer to stay away from images and control the layout with straight CSS, I took a shot at this without any images. This is what I came up with.

Basically I’m using an empty DIV that’s absolutely positioned and with an absurd height value (999,999 pixels) to fake the gap between the two columns rather than use a background image. I then wrap everything in another DIV and set the overflow value to hidden. This hides everything but the bit of that very tall DIV needed to produce the effect. I’m basically clipping the very tall DIV and the clipping is done dynamically (based on the height of the content). The advantage with this solution is that I can control the color of the borders and background with just CSS — I don’t have to create a new image every time I change my color scheme. The downside is you need a couple star-HTML hacks for IE 5 and 6, plus you have to add extra markup in your HTML document that serves no purpose other than to create visual layout objects (which HTML purists feel is wrong, and I kind of agree with them).

Why do we have to fake the visual appearance of two columns and why is it so difficult?

Good question.

HTML block elements that have no height value explicitly defined (such as via the height CSS property) will be take whatever height is needed to contain all its children. So if you’ve got a very large image inside a block element, the element will grow very big — just enough to contain the image. If a small image is placed inside a block element, the element will only grow a little bit, just enough to fit the small image.

So create two block elements that will be our columns. Float them left so they sit next to each other. Now put a border around them. What you will see is that the block elements will only be as tall as the content (the text) inside each column. The columns will not be equal height (unless you’ve put the same content into both block elements).

If you want to create the appearance of two columns with the same height, you’ll have to fake it.

How?

Put your columns inside another block element. Your left and right columns will still have different heights, but the block element that contains the columns will grow as tall as it needs to contain both columns, in other words it will grow to be as tall as the tallest column. With this you now have an element you can apply some CSS to which you know will always be as tall as the tallest column in your web page.

What CSS is that?

Depends. A common approach is to set the background color of the block element that contains the two columns (let’s call it a wrapper since it wraps around the columns) to the color you want your left-hand column’s background to be. Then create a border on the wrapper set to the width of the right-hand column and set the color of that border to the color you want the right-hand column’s background to be. You can then use CSS to position that right-hand column over the border of the wrapper. To the viewer it appears as if there are two separate columns with different backgrounds. You can see an example of this in action in my Skidoo Redux layout.

The trick I presented earlier in this post can then be used to add a border around each column.

But Skidoo already has borders around its columns.

Yes, but those columns share borders with other columns. The trick I present here allows each column to have its own set of borders, not shared with any other column (in other words, there’s a gap between the columns). That’s what makes this trick different.

Posted in Informational.

Tagged with , , , .


Inkscape

Right now work has me creating some maps to put on the web based of scanned drawings from the contractor behind a bunch of on-campus construction. In the past I would use Adobe Illustrator to do this by importing the maps and then tracing over it with vector lines. This process is relatively quick and easy. I can use grids to control the initial lines and keep them straight. I can then go in and manipulate points (or add more as needed) along the lines to fine tune things.

Well I don’t have Illustrator installed and the only version the office has is old and, from my experience, not very responsive. So I set out to find a free alternative. Enter Inkscape. Inkscape is a free, open source vector graphics program that you can use on Windows, Mac, and Linux platforms (and is probably portable to others should you be into that sort of thing).

I’ve been using it for just the day and I’m already in love. The controls are intuitive. There’s a ton of power in just the mouse commands. I hardly need to touch the keyboard for anything (hiding and unhiding the guides I’ve created). Working with layers may feel a bit odd; it’s nothing like Photoshop or Gimp in terms of the UI. But after a few hours I find myself right at home.

It’s native format is SVG, which open standards people will love. But if that’s not your thing you can save is most major vector graphic formats (eps, pdf, etc). You can also export the work as a bitmap and tweak it as needed in your favorite graphics editor.

This is a great application. I suggest you give it a try if you’re ever in the market for a vector graphics format.

Posted in Uncategorized.