Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 6730

Show
Ignore:
Timestamp:
05/14/07 11:14:30 (2 years ago)
Author:
bitsweat
Message:

Rationalize route path escaping according to RFC 2396 section 3.3. Closes #7544, #8307.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r6729 r6730  
    11*SVN* 
     2 
     3* Rationalize route path escaping according to RFC 2396 section 3.3.  #7544, #8307. [Jeremy Kemper, chrisroos, begemot, jugend] 
    24 
    35* Added record identification with polymorphic routes for ActionController::Base#url_for and ActionView::Base#url_for [DHH]. Examples: 
  • trunk/actionpack/lib/action_controller/routing.rb

    r6729 r6730  
    249249  # 
    250250  module Routing 
     251    # TODO: , (comma) should be an allowed path character. 
    251252    SEPARATORS = %w( / ; . , ? ) 
    252253 
     
    548549 
    549550    class Segment #:nodoc: 
     551      # TODO: , (comma) should be an allowed path character. 
     552      RESERVED_PCHAR = ':@&=+$' 
     553      UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze 
     554 
    550555      attr_accessor :is_optional 
    551556      alias_method :optional?, :is_optional 
     
    568573        end 
    569574      end 
    570    
     575 
     576      def interpolation_chunk 
     577        URI.escape(value, UNSAFE_PCHAR) 
     578      end 
     579 
    571580      # Return a string interpolation statement for this segment and those before it. 
    572581      def interpolation_statement(prior_segments) 
     
    612621   
    613622      def interpolation_chunk 
    614         raw? ? value : URI.escape(value) 
     623        raw? ? value : super 
    615624      end 
    616625   
    617626      def regexp_chunk 
    618         chunk = Regexp.escape value 
     627        chunk = Regexp.escape(value) 
    619628        optional? ? Regexp.optionalize(chunk) : chunk 
    620629      end 
     
    693702   
    694703      def interpolation_chunk 
    695         "\#{CGI.escape(#{local_name}.to_s)}" 
     704        "\#{URI.escape(#{local_name}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}" 
    696705      end 
    697706   
     
    724733      end 
    725734      def match_extraction(next_capture) 
    726         # All non code-related keys (such as :id, :slug) have to be unescaped as other CGI params 
     735        # All non code-related keys (such as :id, :slug) are URI-unescaped as 
     736        # path parameters. 
    727737        default_value = default ? default.inspect : nil 
    728738        %[ 
    729739          value = if (m = match[#{next_capture}]) 
    730             m = m.gsub('+', '%2B') 
    731             CGI.unescape(m) 
     740            URI.unescape(m) 
    732741          else 
    733742            #{default_value} 
     
    749758      end 
    750759 
    751       # Don't URI.escape the controller name, since it may have slashes in it, 
    752       # like admin/foo. 
     760      # Don't URI.escape the controller name since it may contain slashes. 
    753761      def interpolation_chunk 
    754762        "\#{#{local_name}.to_s}" 
     
    771779 
    772780    class PathSegment < DynamicSegment #:nodoc: 
    773       EscapedSlash = URI.escape("/") 
     781      RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/" 
     782      UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze 
     783 
    774784      def interpolation_chunk 
    775         "\#{URI.escape(#{local_name}.to_s).gsub(#{EscapedSlash.inspect}, '/')}" 
     785        "\#{URI.escape(#{local_name}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}" 
    776786      end 
    777787 
  • trunk/actionpack/test/controller/routing_test.rb

    r6724 r6730  
    1414end 
    1515 
     16# See RFC 3986, section 3.3 for allowed path characters. 
    1617class UriReservedCharactersRoutingTest < Test::Unit::TestCase 
    17   # See RFC 3986, section 2.2 Reserved Characters 
    18    
    1918  def setup 
    2019    ActionController::Routing.use_controllers! ['controller'] 
    2120    @set = ActionController::Routing::RouteSet.new 
    2221    @set.draw do |map| 
    23       map.connect ':controller/:action/:var' 
    24     end 
    25   end 
    26    
    27   def test_should_escape_reserved_uri_characters_within_individual_path_components 
    28     assert_equal '/controller/action/p1%3Ap2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1:p2') 
    29     assert_equal '/controller/action/p1%2Fp2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1/p2') 
    30     assert_equal '/controller/action/p1%3Fp2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1?p2') 
    31     assert_equal '/controller/action/p1%23p2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1#p2') 
    32     assert_equal '/controller/action/p1%5Bp2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1[p2') 
    33     assert_equal '/controller/action/p1%5Dp2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1]p2') 
    34     assert_equal '/controller/action/p1%40p2', @set.generate(:controller => 'controller', :action => 'action', :var => 'p1@p2') 
    35   end 
    36    
    37   def test_should_recognize_escaped_path_component_and_unescape 
    38     expected_options = {:var => "p1:p2", :controller => "controller", :action => "action"} 
    39     assert_equal expected_options, @set.recognize_path('/controller/action/p1%3Ap2') 
    40     expected_options = {:var => "p1/p2", :controller => "controller", :action => "action"} 
    41     assert_equal expected_options, @set.recognize_path('/controller/action/p1%2Fp2') 
    42     expected_options = {:var => "p1?p2", :controller => "controller", :action => "action"} 
    43     assert_equal expected_options, @set.recognize_path('/controller/action/p1%3Fp2') 
    44     expected_options = {:var => "p1#p2", :controller => "controller", :action => "action"} 
    45     assert_equal expected_options, @set.recognize_path('/controller/action/p1%23p2') 
    46     expected_options = {:var => "p1[p2", :controller => "controller", :action => "action"} 
    47     assert_equal expected_options, @set.recognize_path('/controller/action/p1%5Bp2') 
    48     expected_options = {:var => "p1]p2", :controller => "controller", :action => "action"} 
    49     assert_equal expected_options, @set.recognize_path('/controller/action/p1%5Dp2') 
    50     expected_options = {:var => "p1@p2", :controller => "controller", :action => "action"} 
    51     assert_equal expected_options, @set.recognize_path('/controller/action/p1%40p2') 
    52   end 
    53    
     22      map.connect ':controller/:action/:variable' 
     23    end 
     24 
     25    # TODO: perhaps , (comma) shouldn't be a route separator. 
     26    safe, unsafe = %w(: @ & = + $), %w(, ^ / ? # [ ] ;) 
     27    hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase } 
     28 
     29    @segment = "#{safe}#{unsafe}".freeze 
     30    @escaped = "#{safe}#{hex}".freeze 
     31  end 
     32 
     33  def test_route_generation_escapes_unsafe_path_characters 
     34    assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable", 
     35      @set.generate(:controller => "contr#{@segment}oller", 
     36                    :action => "act#{@segment}ion", 
     37                    :variable => "var#{@segment}iable") 
     38  end 
     39 
     40  def test_route_recognition_unescapes_path_components 
     41    options = { :controller => "controller", 
     42                :action => "act#{@segment}ion", 
     43                :variable => "var#{@segment}iable" } 
     44    assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable") 
     45  end 
    5446end 
    5547 
     
    925917  end 
    926918   
    927   def test_default_route_should_escape_pluses_in_id 
    928     expected = {:controller => 'accounts', :action => 'show', :id => 'hello world'} 
     919  def test_default_route_should_uri_escape_pluses 
     920    expected = { :controller => 'accounts', :action => 'show', :id => 'hello world' } 
     921    assert_equal expected, default_route.recognize('/accounts/show/hello world') 
     922    assert_equal expected, default_route.recognize('/accounts/show/hello%20world') 
     923    assert_equal '/accounts/show/hello%20world', default_route.generate(expected, expected, {}) 
     924 
     925    expected[:id] = 'hello+world' 
    929926    assert_equal expected, default_route.recognize('/accounts/show/hello+world') 
     927    assert_equal expected, default_route.recognize('/accounts/show/hello%2Bworld') 
     928    assert_equal '/accounts/show/hello+world', default_route.generate(expected, expected, {}) 
    930929  end 
    931930