mime_type.rb 6.2 KB
Newer Older
1 2
require 'set'

3
module Mime
4
  SET              = []
5 6
  EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
  LOOKUP           = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
7

8 9 10 11
  def self.[](type)
    Type.lookup_by_extension(type)
  end

D
David Heinemeier Hansson 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25
  # Encapsulates the notion of a mime type. Can be used at render time, for example, with:
  #
  #   class PostsController < ActionController::Base
  #     def show
  #       @post = Post.find(params[:id])
  #
  #       respond_to do |format|
  #         format.html
  #         format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"]  }
  #         format.xml { render :xml => @people.to_xml }
  #       end
  #     end
  #   end
  class Type
26
    @@html_types = Set.new [:html, :all]
R
rick 已提交
27 28
    cattr_reader :html_types

29 30 31
    # These are the content types which browsers can generate without using ajax, flash, etc
    # i.e. following a link, getting an image or posting a form.  CSRF protection
    # only needs to protect against these types.
32
    @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
33 34 35
    cattr_reader :browser_generated_types


36
    @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml]
37 38 39 40
    def self.unverifiable_types
      ActiveSupport::Deprecation.warn("unverifiable_types is deprecated and has no effect", caller)
      @@unverifiable_types
    end
41

42 43 44 45 46 47 48
    # A simple helper class used in parsing the accept header
    class AcceptItem #:nodoc:
      attr_accessor :order, :name, :q

      def initialize(order, name, q=nil)
        @order = order
        @name = name.strip
49
        q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
        @q = ((q || 1.0).to_f * 100).to_i
      end

      def to_s
        @name
      end

      def <=>(item)
        result = item.q <=> q
        result = order <=> item.order if result == 0
        result
      end

      def ==(item)
        name == (item.respond_to?(:name) ? item.name : item)
      end
    end

68 69 70 71
    class << self
      def lookup(string)
        LOOKUP[string]
      end
72

73 74 75 76
      def lookup_by_extension(extension)
        EXTENSION_LOOKUP[extension]
      end

77
      # Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for
78 79 80 81 82 83
      # rendering different HTML versions depending on the user agent, like an iPhone.
      def register_alias(string, symbol, extension_synonyms = [])
        register(string, symbol, [], extension_synonyms, true)
      end

      def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
84
        Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
85

86
        SET << Mime.const_get(symbol.to_s.upcase)
87

88
        ([string] + mime_type_synonyms).each { |string| LOOKUP[string] = SET.last } unless skip_lookup
89
        ([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
90 91
      end

92
      def parse(accept_header)
93 94 95 96 97 98 99 100 101 102 103
        if accept_header !~ /,/
          [Mime::Type.lookup(accept_header)]
        else
          # keep track of creation order to keep the subsequent sort stable
          list = []
          accept_header.split(/,/).each_with_index do |header, index| 
            params, q = header.split(/;\s*q=/)       
            if params
              params.strip!          
              list << AcceptItem.new(index, params, q) unless params.empty?
            end
104
          end
105
          list.sort!
106

107 108 109
          # Take care of the broken text/xml entry by renaming or deleting it
          text_xml = list.index("text/xml")
          app_xml = list.index(Mime::XML.to_s)
110

111 112 113
          if text_xml && app_xml
            # set the q value to the max of the two
            list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
114

115 116 117 118 119
            # make sure app_xml is ahead of text_xml in the list
            if app_xml > text_xml
              list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
              app_xml, text_xml = text_xml, app_xml
            end
120

121 122
            # delete text_xml from the list
            list.delete_at(text_xml)
123

124 125 126
          elsif text_xml
            list[text_xml].name = Mime::XML.to_s
          end
127

128
          # Look for more specific XML-based types and sort them ahead of app/xml
129

130 131 132
          if app_xml
            idx = app_xml
            app_xml_type = list[app_xml]
133

134 135 136 137 138 139 140 141
            while(idx < list.length)
              type = list[idx]
              break if type.q < app_xml_type.q
              if type.name =~ /\+xml$/
                list[app_xml], list[idx] = list[idx], list[app_xml]
                app_xml = idx
              end
              idx += 1
142 143
            end
          end
144

145 146 147
          list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
          list
        end
148
      end
149
    end
150 151 152
    
    def initialize(string, symbol = nil, synonyms = [])
      @symbol, @synonyms = symbol, synonyms
153 154 155 156 157
      @string = string
    end
    
    def to_s
      @string
158 159
    end
    
160 161 162 163
    def to_str
      to_s
    end
    
164
    def to_sym
165
      @symbol || @string.to_sym
166 167 168 169
    end

    def ===(list)
      if list.is_a?(Array)
170
        (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
171 172 173 174
      else
        super
      end
    end
175 176
    
    def ==(mime_type)
177
      return false if mime_type.blank?
178 179 180
      (@synonyms + [ self ]).any? do |synonym| 
        synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym 
      end
181
    end
182

183 184
    def =~(mime_type)
      return false if mime_type.blank?
185
      regexp = Regexp.new(Regexp.quote(mime_type.to_s))
186 187 188 189 190
      (@synonyms + [ self ]).any? do |synonym|
        synonym.to_s =~ regexp
      end
    end

P
Pratik Naik 已提交
191
    # Returns true if Action Pack should check requests using this Mime Type for possible request forgery.  See
192
    # ActionController::RequestForgeryProtection.
193
    def verify_request?
194
      @@browser_generated_types.include?(to_sym)
195 196 197 198 199 200
    end

    def html?
      @@html_types.include?(to_sym) || @string =~ /html/
    end

201 202 203
    private
      def method_missing(method, *args)
        if method.to_s =~ /(\w+)\?$/
204
          $1.downcase.to_sym == to_sym
205 206 207 208
        else
          super
        end
      end
209
  end
210
end
211

212
require 'action_controller/mime_types'