jump to navigation

Plug This: css_browser_selector plugin May 22, 2008

Posted by reidmix in Code, Plugins & Gems, Rails.
Tags: , , ,
3 comments

I’m happy to announce my first plugin based on the css_browser_selector javascript which adds the name of the browser and platform as css class selectors that your client is using on the html element in your page. For example:

<html class="gecko mac js">

I’ve released my first version of the plugin (at 0.9.1) and you can download it from on LATimes RubyForge project or from GitHub. You can read how to use the plugin in its README, it offers a way to include the javascript inline into your views and layouts:

<%= javascript_tag css_browser_selector %>

But wait there’s more! What the plugin also does is offer a body (and html) tag helper that will render css selectors into your views from the backend. The real benefit to this is that these selectors will be available within the page without needing javascript enabled:

<% body do %>
  <%= yield %>
<% end %>

And this will add the selectors as you would expect to your body tag:

<body class="gecko mac">

The body helper is currently aware of page caching and will revert to the javascript version if the page is being cached through the caches_page method.

This is my first plugin and, yes, I still have some work to do (complete my tests, better caching awareness, sensitivity to pre-existing body and html tag helpers), but I wanted to share back to the community. Following Giles Bowkett‘s mantra to release software (often and early).

There’s plenty of more options as explained by the README. I welcome feedback, suggestions, improvements, bugs, and diffs! I’d also like to thank Rafeal Lima who implemented and maintains the javascript and his help and support with the plugin

IE6 Accept Header is Faulty and Makes format.any Suck May 14, 2008

Posted by reidmix in AbstractRequest, ActionController, acts_as_authenticated, Changeset, Code, Example, MimeResponds, Monkey Patch, Plugins & Gems, Rails.
Tags: , , , , , , , , , ,
11 comments

I’ve been using the awesome acts_as_authenticated plugin as the basis for user login and authorization. Its access_denied method takes advantage of the ActionController::MimeResponds.any method which will be invoked on a login_required before filter:

    def access_denied
      respond_to do |format|
        format.html do
          store_location
          redirect_to new_session_path
        end
        format.any do
          request_http_basic_authentication 'Web Password'
        end
      end
    end

This code essentially says, if it’s an html request, store the original request and redirect to the login page, any other requests get the 401 Unauthorized header which the user agent can then use to initiate an HTTP Auth. This bit of code only works correctly with Edge Rail’s 8987 Changeset that allows any to act as a catch-all for any request mime-types not already specified.

Awesome!
Except when Internet Explorer 6 comes into the picture and happily sends this
strange and incomplete ‘Accept:’ header:

image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*

As you can see, it doesn’t specify application/xhtml+xml or even text/html and what’s worse is the subsequent requests to the same page sends the catch-all */*.

What does this all mean?
When IE6 navigates to a page that requires login, instead of redirecting the user to the login page, the user is presented with an HTTP Auth Dialog. What’s worse is that in my application, cookies and other application state for an web-based end-user is set outside of the login_from_basic_auth method — you know — in my login action.

HTTP Auth Prompt by IE6

My guess is when the format.any fix goes out in the next rails release, IE6 users on acts_as_authenticated sites are going to be sad-faced. I’m not entirely sure the correct way to fix this problem but this is how I solved it in my application:

class ActionController::AbstractRequest
  def accepts_with_faulty_header
    @env['HTTP_ACCEPT']='*/*' if @env['HTTP_USER_AGENT'] =~ /msie/i and @env['HTTP_USER_AGENT'] !~ /opera|webtv/i
    accepts_without_faulty_header
  end
  alias_method_chain :accepts, :faulty_header
end

Originally, I thought to patch the Mime::Type.parse function, but decided that I wanted to make sure to look at the user agent for IE6. As you can see, I switch the HTTP_ACCEPT header to the catch-all */* string if the original HTTP_ACCEPT header is a match to the exact (bizarre) IE6 string and the HTTP_USER_AGENT is IE (matches MSIE but not Opera or WebTV). Then I call the original ActionController::AbstractRequest.accepts method.

I’d love to hear your ideas and suggestions — and if others have run into this problem as I didn’t find many users have this exact problem and how they may have solved it.

Update: Based on the conversation in the comments, I’ve re-written the patch to always change the HTTP_ACCEPT header to the catch-all */* if it is an IE Browser.