RubySpec: Monkeypatch with Confidence

2009 December 07

Bob McWhirter

While the tagline of RubySpec is actually "The Standard You Trust" it might as well be "Monkeypatch with Confidence".

Here's the backstory:

Building TorqueBox, I ultimately wanted to be able to run a Ruby application from within a simple zip bundle of the app.  And handling everything that expects to be living in a relatively-addressable filesystem.  Not just require and load, but things like Dir['*.png'] and File.stat(...).

Plus, JBoss (upon which TorqueBox is built) has this cool ability to think of a jar-within-a-jar-within-a-jar (or a Russian doll nesting of zip files) as still a simple directory-based filesystem.

JRuby, though, isn't quite as swift, in terms of paths for standard core library things such as File, or Dir, or IO.

To get this JBoss functionality (called VFS for Virtual File-System) exposed to any arbitrary Ruby code, I'm having to monkeypatch File, Dir, IO and other classes.

For the most part, the rule for how these patches jack in is "if this is a normal file/directory/io, let the normal core library handle it, otherwise, punt to VFS".

Given this rule, with VFS turned on, all existing code that doesn't touch these zip/jar bundles should exhibit no regressions.  A great way to test that is using RubySpec.  I'm approaching this problem as treating JRuby-with-VFS as a new implementation of Ruby, to be tested alongside general JRuby support.  Enabling VFS should not cause JRuby to become any less compatible with the specs.

Contributors to the VFS gem can now easily run the applicable RubySpecs against JRuby both with and without VFS enabled.

SWFUpload, Rails, REST, and Sessions

2009 November 12

Bob McWhirter

To test TorqueBox, I'm always building little applications.

My latest little application involved uploading a user avatar image, which my app, using Paperclip, would shuffle to Amazon S3.

Form uploads are cool, but AJAXy Flash uploads are cooler.  So I grabbed SWFUpload from the conveniently-named sfwupload.org.

Integrating it into my application was a slight challenge, alas.

My User model directly holds the avatar_* columns Paperclip wants, in the form of

  • avatar_file_name
  • avatar_content_type
  • avatar_file_size
  • avatar_updated_at

To my feeble mind, this seems like a PUT on an existing User resource, if I want to be RESTful.

SWFUpload will gleefully do a POST, and I can add a _method=PUT to my parameters.  This is a concession Rails has made to the rest of the world, realizing that not everything supports every HTTP verb when you want it to.  So, Rails lets you do a POST, and tell it what verb you really meant through a parameter named _method.  Rails only respects _method, though, if it's in the POST parameters.  So I can't use the query-string to inject the psuedo PUT (or DELETE or ...).

But SWFUpload (and most anything Flash) doesn't participate in the containing browser session.  It doesn't have my session cookie.  Or it doesn't send it, if it does. Without session information, the server doesn't really know who is sending this octet stream at the URL. So, how do I secure this update_user execution, if I can't pass a session cookie?

SWFUpload has an extension to include every cookie as a parameter in the request.  It just adds name=value pairs for each cookie into the post parameters, alongside the _method and anything else you've specified.  So, if you had a cookie named session_id, it'd get added to the form paramters as session_id.

But with TorqueBox, the Rack session piggybacks upon the Java Servlet session, which, unfortunately, doesn't know to inspect the POST parameters. By default, the servlet container is looking for a cookie.  I attempted to write a custom valve for the Tomcat request chain, but I only had reliable access to query parameters, not post parameters of the multipart form.

This makes sense, since the POST body is conceivably large, and is 100% the responsibility of the application. App servers don't typically go mucking around in it on their own.  The application expects to be able to get an input stream for the POST body and ready starting from byte 0.  This means either massive buffering, or the simply that the app-server gives it to the app unmolested (and uninspected).

And since Rails expects _method to be in the POST parameters, but session cookie information could only be inspected if present in the URL, I came to an impasse, I thought.

To sum up:

  • Updating a User's avatar is a PUT request against the User resource
  • SWFUpload can only do a POST
  • Rails lets us tunnel PUT through POST using _method=PUT
  • _method=PUT must be in the POST parameters, not URL bits.
  • SWFUpload can inject cookies request parameters.
  • SWFUpload can use either URL-based query parameters XOR POST parameters, but not both.
  • Session lookup can't take advantage of POST parameters, only URL bits.

Though, without any other magic involved, our Java Servlet-based session can be divined by looking for a suffix on the URL of ;jsessionid=<id>.

For instance, you might have come across some Google-cached URLs such as

  • http://foo.com/products.jsp;jsessionid=918jk2j9j09j213

That's the Java app-server's method of passing session cookie information via URL.

It probably breaks 7 of the top 5 tenets of RESTful architectures, but it works.  If we can use this method for passing our session cookie during the upload, then we can successfully secure this interaction.

Ultimately, SWFUpload does a POST to the User resource URL, with ;jsession=<id> appended, and adds the _method=PUT to the POST parameters.  TorqueBox adds a url_suffix() method to the session object, which produces this full suffix.

Similar to this:


    $upload_control.swfupload({
      upload_url: '#{url_for(user)}#{session.url_suffix}',
      post_params: {
        _method: 'PUT',
        authenticity_token: '#{form_authenticity_token}',
      },
      use_query_string: false});
    

Now we successfully pass session information, use CSRF-protection, and still communicate with a nicely RESTful controller on the back-end.

RESTful, Flashy, AJAXy, CSRF-protected and secure.  I'd call that a good day.