Fighting invalid page exceptions

The will_paginate plugin by Err The Blog feat. Mislav sure is one heck of a must have.

But there’s a tiny thing that’s been bugging me for a while. In December of last year (yeah, last year, been a while..) Mislav committed a change to the plugin that changed the behavior of WillPaginate::Collection to raise an exception if/when the page argument converted to an integer is smaller than 1 (which, quite naturally, any non-integer will eventually end up as).

This change was committed as an attempt to address the invalid SQL generation that happened before (you’d end up with something like LIMIT -10, 10 in the SQL given a non-integer page argument), so this is first and foremost a good thing.

If, however, the only thing that ever makes your page argument a non-integer is during those times where your app is attacked by weird spam bots trying to exploit your PHP (cough) application by shoving random strings down your URIs, things slowly start being annoying if you’re using the exception_notification plugin at the same time and end up with hundreds of exception notifications in your inbox (or, heaven forbid, tickets in your bug tracker) for the WillPaginate::InvalidPage exception on a URL like

http://eins.de/videos?page=http%3A%2F%2Fwww.****.info%2Fimages%2Flebun%2Fisexopo%2F

(Obfuscation mine.)

So what’s an annoyed Ruby hacker to do? Work around it!

The global problem

You can surely go ahead and hack a rescue clause into all of your actions that make use of will_paginate but actually you really shouldn’t. You want these things handled globally.

Initially I was trying to go down the simple route, simply handling it via Rails 2.0’s rescue_from macro:


# app/controllers/application.rb
class ApplicationController < ActionController::Base

  rescue_from WillPaginate::InvalidPage, :with => :invalid_page

  protected

    def invalid_page
      render :text => 'Invalid page requested', :status => 400
    end

end

But there are two problems with this approach.

First of all, I actually wanted to serve a real page instead of an error page. Occasionally someone will mistype a URL or someone will link off of his page erroneously omitting something important and I don’t want him or her looking at an error page. I’d much rather have him look at the first page of the collection he’d like to paginate, which should be closer to the page s/he actually requested.

The second problem is that the application in question isn’t running on Rails 2.0 yet. Yes, I know. Bear with me here.

Hacking the root of the problem

Instead of approaching the problem from the controller side I decided to work on it from the plugin’s perspective. I wanted a default of page = 1 unless a valid page was specified. So I ended up patching WillPaginate::Collection#initialize like this:


# config/initializers/will_paginate_extension.rb
WillPaginate::Collection.send :include, WillPaginate::CollectionExtension

module WillPaginate
  module CollectionExtension

    def self.included(base)
      base.send :alias_method_chain, :initialize, :page_sanitizing
    end

    def initialize_with_page_sanitizing(page, per_page, total = nil)
      page = 1 if page.to_i.zero?
      initialize_without_page_sanitizing page, per_page, total
    end

  end
end

This will simply and quite effectively reset the page to view to 1 if anything that evaluates to a zero integer is passed in.

Okay, enough babbling for such a basic thing. Thanks for reading (if you actually got this far).

Filed Under: Rails