actionpack 6.0.3.6 → 6.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +243 -251
 - data/MIT-LICENSE +1 -1
 - data/lib/abstract_controller.rb +1 -0
 - data/lib/abstract_controller/base.rb +35 -2
 - data/lib/abstract_controller/callbacks.rb +2 -2
 - data/lib/abstract_controller/helpers.rb +105 -90
 - data/lib/abstract_controller/rendering.rb +9 -9
 - data/lib/abstract_controller/translation.rb +8 -2
 - data/lib/action_controller.rb +2 -3
 - data/lib/action_controller/api.rb +2 -2
 - data/lib/action_controller/base.rb +4 -2
 - data/lib/action_controller/caching.rb +0 -1
 - data/lib/action_controller/log_subscriber.rb +3 -3
 - data/lib/action_controller/metal.rb +2 -2
 - data/lib/action_controller/metal/conditional_get.rb +10 -2
 - data/lib/action_controller/metal/content_security_policy.rb +1 -1
 - data/lib/action_controller/metal/data_streaming.rb +1 -1
 - data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
 - data/lib/action_controller/metal/exceptions.rb +33 -0
 - data/lib/action_controller/metal/feature_policy.rb +46 -0
 - data/lib/action_controller/metal/head.rb +7 -4
 - data/lib/action_controller/metal/helpers.rb +11 -1
 - data/lib/action_controller/metal/http_authentication.rb +4 -2
 - data/lib/action_controller/metal/implicit_render.rb +1 -1
 - data/lib/action_controller/metal/instrumentation.rb +11 -9
 - data/lib/action_controller/metal/live.rb +1 -1
 - data/lib/action_controller/metal/logging.rb +20 -0
 - data/lib/action_controller/metal/mime_responds.rb +6 -2
 - data/lib/action_controller/metal/parameter_encoding.rb +35 -4
 - data/lib/action_controller/metal/params_wrapper.rb +14 -8
 - data/lib/action_controller/metal/redirecting.rb +1 -1
 - data/lib/action_controller/metal/rendering.rb +6 -0
 - data/lib/action_controller/metal/request_forgery_protection.rb +48 -24
 - data/lib/action_controller/metal/rescue.rb +1 -1
 - data/lib/action_controller/metal/strong_parameters.rb +103 -15
 - data/lib/action_controller/renderer.rb +24 -13
 - data/lib/action_controller/test_case.rb +62 -56
 - data/lib/action_dispatch.rb +3 -2
 - data/lib/action_dispatch/http/cache.rb +12 -10
 - data/lib/action_dispatch/http/content_disposition.rb +2 -2
 - data/lib/action_dispatch/http/content_security_policy.rb +5 -1
 - data/lib/action_dispatch/http/feature_policy.rb +168 -0
 - data/lib/action_dispatch/http/filter_parameters.rb +1 -1
 - data/lib/action_dispatch/http/filter_redirect.rb +1 -1
 - data/lib/action_dispatch/http/headers.rb +3 -2
 - data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
 - data/lib/action_dispatch/http/mime_type.rb +28 -15
 - data/lib/action_dispatch/http/parameters.rb +1 -19
 - data/lib/action_dispatch/http/request.rb +26 -8
 - data/lib/action_dispatch/http/response.rb +17 -16
 - data/lib/action_dispatch/http/url.rb +3 -2
 - data/lib/action_dispatch/journey.rb +0 -2
 - data/lib/action_dispatch/journey/formatter.rb +53 -28
 - data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
 - data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
 - data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
 - data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
 - data/lib/action_dispatch/journey/nodes/node.rb +4 -3
 - data/lib/action_dispatch/journey/parser.rb +13 -13
 - data/lib/action_dispatch/journey/parser.y +1 -1
 - data/lib/action_dispatch/journey/path/pattern.rb +13 -18
 - data/lib/action_dispatch/journey/route.rb +7 -18
 - data/lib/action_dispatch/journey/router.rb +26 -30
 - data/lib/action_dispatch/journey/router/utils.rb +6 -4
 - data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -2
 - data/lib/action_dispatch/middleware/cookies.rb +74 -33
 - data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
 - data/lib/action_dispatch/middleware/debug_view.rb +1 -1
 - data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
 - data/lib/action_dispatch/middleware/host_authorization.rb +28 -17
 - data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
 - data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
 - data/lib/action_dispatch/middleware/request_id.rb +4 -5
 - data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
 - data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
 - data/lib/action_dispatch/middleware/ssl.rb +9 -6
 - data/lib/action_dispatch/middleware/stack.rb +18 -0
 - data/lib/action_dispatch/middleware/static.rb +154 -93
 - data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
 - data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
 - data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
 - data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
 - data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
 - data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
 - data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
 - data/lib/action_dispatch/railtie.rb +3 -2
 - data/lib/action_dispatch/request/session.rb +2 -8
 - data/lib/action_dispatch/request/utils.rb +26 -2
 - data/lib/action_dispatch/routing/inspector.rb +8 -7
 - data/lib/action_dispatch/routing/mapper.rb +102 -71
 - data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
 - data/lib/action_dispatch/routing/redirection.rb +3 -3
 - data/lib/action_dispatch/routing/route_set.rb +49 -41
 - data/lib/action_dispatch/routing/url_for.rb +1 -0
 - data/lib/action_dispatch/system_test_case.rb +29 -24
 - data/lib/action_dispatch/system_testing/browser.rb +33 -27
 - data/lib/action_dispatch/system_testing/driver.rb +6 -7
 - data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
 - data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
 - data/lib/action_dispatch/testing/assertions.rb +1 -1
 - data/lib/action_dispatch/testing/assertions/response.rb +2 -4
 - data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
 - data/lib/action_dispatch/testing/integration.rb +38 -27
 - data/lib/action_dispatch/testing/test_process.rb +29 -4
 - data/lib/action_dispatch/testing/test_request.rb +3 -3
 - data/lib/action_pack.rb +1 -1
 - data/lib/action_pack/gem_version.rb +3 -3
 - metadata +20 -21
 - data/lib/action_controller/metal/force_ssl.rb +0 -58
 - data/lib/action_dispatch/http/parameter_filter.rb +0 -12
 - data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
 - data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
 - data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
 
| 
         @@ -6,21 +6,21 @@ require "rack/utils" 
     | 
|
| 
       6 
6 
     | 
    
         
             
            module ActionDispatch
         
     | 
| 
       7 
7 
     | 
    
         
             
              class ExceptionWrapper
         
     | 
| 
       8 
8 
     | 
    
         
             
                cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
         
     | 
| 
       9 
     | 
    
         
            -
                  "ActionController::RoutingError" 
     | 
| 
       10 
     | 
    
         
            -
                  "AbstractController::ActionNotFound" 
     | 
| 
       11 
     | 
    
         
            -
                  "ActionController::MethodNotAllowed" 
     | 
| 
       12 
     | 
    
         
            -
                  "ActionController::UnknownHttpMethod" 
     | 
| 
       13 
     | 
    
         
            -
                  "ActionController::NotImplemented" 
     | 
| 
       14 
     | 
    
         
            -
                  "ActionController::UnknownFormat" 
     | 
| 
       15 
     | 
    
         
            -
                  " 
     | 
| 
       16 
     | 
    
         
            -
                  "ActionController::MissingExactTemplate" 
     | 
| 
       17 
     | 
    
         
            -
                  "ActionController::InvalidAuthenticityToken" 
     | 
| 
       18 
     | 
    
         
            -
                  "ActionController::InvalidCrossOriginRequest" 
     | 
| 
       19 
     | 
    
         
            -
                  "ActionDispatch::Http::Parameters::ParseError" 
     | 
| 
       20 
     | 
    
         
            -
                  "ActionController::BadRequest" 
     | 
| 
       21 
     | 
    
         
            -
                  "ActionController::ParameterMissing" 
     | 
| 
       22 
     | 
    
         
            -
                  "Rack::QueryParser::ParameterTypeError" 
     | 
| 
       23 
     | 
    
         
            -
                  "Rack::QueryParser::InvalidParameterError" 
     | 
| 
      
 9 
     | 
    
         
            +
                  "ActionController::RoutingError"                     => :not_found,
         
     | 
| 
      
 10 
     | 
    
         
            +
                  "AbstractController::ActionNotFound"                 => :not_found,
         
     | 
| 
      
 11 
     | 
    
         
            +
                  "ActionController::MethodNotAllowed"                 => :method_not_allowed,
         
     | 
| 
      
 12 
     | 
    
         
            +
                  "ActionController::UnknownHttpMethod"                => :method_not_allowed,
         
     | 
| 
      
 13 
     | 
    
         
            +
                  "ActionController::NotImplemented"                   => :not_implemented,
         
     | 
| 
      
 14 
     | 
    
         
            +
                  "ActionController::UnknownFormat"                    => :not_acceptable,
         
     | 
| 
      
 15 
     | 
    
         
            +
                  "ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable,
         
     | 
| 
      
 16 
     | 
    
         
            +
                  "ActionController::MissingExactTemplate"             => :not_acceptable,
         
     | 
| 
      
 17 
     | 
    
         
            +
                  "ActionController::InvalidAuthenticityToken"         => :unprocessable_entity,
         
     | 
| 
      
 18 
     | 
    
         
            +
                  "ActionController::InvalidCrossOriginRequest"        => :unprocessable_entity,
         
     | 
| 
      
 19 
     | 
    
         
            +
                  "ActionDispatch::Http::Parameters::ParseError"       => :bad_request,
         
     | 
| 
      
 20 
     | 
    
         
            +
                  "ActionController::BadRequest"                       => :bad_request,
         
     | 
| 
      
 21 
     | 
    
         
            +
                  "ActionController::ParameterMissing"                 => :bad_request,
         
     | 
| 
      
 22 
     | 
    
         
            +
                  "Rack::QueryParser::ParameterTypeError"              => :bad_request,
         
     | 
| 
      
 23 
     | 
    
         
            +
                  "Rack::QueryParser::InvalidParameterError"           => :bad_request
         
     | 
| 
       24 
24 
     | 
    
         
             
                )
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
                cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
         
     | 
| 
         @@ -36,18 +36,24 @@ module ActionDispatch 
     | 
|
| 
       36 
36 
     | 
    
         
             
                  "ActionView::Template::Error"
         
     | 
| 
       37 
37 
     | 
    
         
             
                ]
         
     | 
| 
       38 
38 
     | 
    
         | 
| 
      
 39 
     | 
    
         
            +
                cattr_accessor :silent_exceptions, default: [
         
     | 
| 
      
 40 
     | 
    
         
            +
                  "ActionController::RoutingError",
         
     | 
| 
      
 41 
     | 
    
         
            +
                  "ActionDispatch::Http::MimeNegotiation::InvalidType"
         
     | 
| 
      
 42 
     | 
    
         
            +
                ]
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
       39 
44 
     | 
    
         
             
                attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
         
     | 
| 
       40 
45 
     | 
    
         | 
| 
       41 
46 
     | 
    
         
             
                def initialize(backtrace_cleaner, exception)
         
     | 
| 
       42 
47 
     | 
    
         
             
                  @backtrace_cleaner = backtrace_cleaner
         
     | 
| 
       43 
48 
     | 
    
         
             
                  @exception = exception
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @exception_class_name = @exception.class.name
         
     | 
| 
       44 
50 
     | 
    
         
             
                  @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
         
     | 
| 
       45 
51 
     | 
    
         | 
| 
       46 
52 
     | 
    
         
             
                  expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
         
     | 
| 
       47 
53 
     | 
    
         
             
                end
         
     | 
| 
       48 
54 
     | 
    
         | 
| 
       49 
55 
     | 
    
         
             
                def unwrapped_exception
         
     | 
| 
       50 
     | 
    
         
            -
                  if wrapper_exceptions.include?( 
     | 
| 
      
 56 
     | 
    
         
            +
                  if wrapper_exceptions.include?(@exception_class_name)
         
     | 
| 
       51 
57 
     | 
    
         
             
                    exception.cause
         
     | 
| 
       52 
58 
     | 
    
         
             
                  else
         
     | 
| 
       53 
59 
     | 
    
         
             
                    exception
         
     | 
| 
         @@ -55,13 +61,19 @@ module ActionDispatch 
     | 
|
| 
       55 
61 
     | 
    
         
             
                end
         
     | 
| 
       56 
62 
     | 
    
         | 
| 
       57 
63 
     | 
    
         
             
                def rescue_template
         
     | 
| 
       58 
     | 
    
         
            -
                  @@rescue_templates[@ 
     | 
| 
      
 64 
     | 
    
         
            +
                  @@rescue_templates[@exception_class_name]
         
     | 
| 
       59 
65 
     | 
    
         
             
                end
         
     | 
| 
       60 
66 
     | 
    
         | 
| 
       61 
67 
     | 
    
         
             
                def status_code
         
     | 
| 
       62 
68 
     | 
    
         
             
                  self.class.status_code_for_exception(unwrapped_exception.class.name)
         
     | 
| 
       63 
69 
     | 
    
         
             
                end
         
     | 
| 
       64 
70 
     | 
    
         | 
| 
      
 71 
     | 
    
         
            +
                def exception_trace
         
     | 
| 
      
 72 
     | 
    
         
            +
                  trace = application_trace
         
     | 
| 
      
 73 
     | 
    
         
            +
                  trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  trace
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
       65 
77 
     | 
    
         
             
                def application_trace
         
     | 
| 
       66 
78 
     | 
    
         
             
                  clean_backtrace(:silent)
         
     | 
| 
       67 
79 
     | 
    
         
             
                end
         
     | 
| 
         @@ -4,7 +4,12 @@ require "action_dispatch/http/request" 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module ActionDispatch
         
     | 
| 
       6 
6 
     | 
    
         
             
              # This middleware guards from DNS rebinding attacks by explicitly permitting
         
     | 
| 
       7 
     | 
    
         
            -
              # the hosts a request can be sent to 
     | 
| 
      
 7 
     | 
    
         
            +
              # the hosts a request can be sent to, and is passed the options set in
         
     | 
| 
      
 8 
     | 
    
         
            +
              # +config.host_authorization+.
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Requests can opt-out of Host Authorization with +exclude+:
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              #    config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
         
     | 
| 
       8 
13 
     | 
    
         
             
              #
         
     | 
| 
       9 
14 
     | 
    
         
             
              # When a request comes to an unauthorized host, the +response_app+
         
     | 
| 
       10 
15 
     | 
    
         
             
              # application will be executed and rendered. If no +response_app+ is given, a
         
     | 
| 
         @@ -66,9 +71,20 @@ module ActionDispatch 
     | 
|
| 
       66 
71 
     | 
    
         
             
                  }, [body]]
         
     | 
| 
       67 
72 
     | 
    
         
             
                end
         
     | 
| 
       68 
73 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
                def initialize(app, hosts,  
     | 
| 
      
 74 
     | 
    
         
            +
                def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
         
     | 
| 
       70 
75 
     | 
    
         
             
                  @app = app
         
     | 
| 
       71 
76 
     | 
    
         
             
                  @permissions = Permissions.new(hosts)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @exclude = exclude
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  unless deprecated_response_app.nil?
         
     | 
| 
      
 80 
     | 
    
         
            +
                    ActiveSupport::Deprecation.warn(<<-MSG.squish)
         
     | 
| 
      
 81 
     | 
    
         
            +
                      `action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 6.2.
         
     | 
| 
      
 82 
     | 
    
         
            +
                      Use the Host Authorization `response_app` setting instead.
         
     | 
| 
      
 83 
     | 
    
         
            +
                    MSG
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    response_app ||= deprecated_response_app
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
       72 
88 
     | 
    
         
             
                  @response_app = response_app || DEFAULT_RESPONSE_APP
         
     | 
| 
       73 
89 
     | 
    
         
             
                end
         
     | 
| 
       74 
90 
     | 
    
         | 
| 
         @@ -77,7 +93,7 @@ module ActionDispatch 
     | 
|
| 
       77 
93 
     | 
    
         | 
| 
       78 
94 
     | 
    
         
             
                  request = Request.new(env)
         
     | 
| 
       79 
95 
     | 
    
         | 
| 
       80 
     | 
    
         
            -
                  if authorized?(request)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  if authorized?(request) || excluded?(request)
         
     | 
| 
       81 
97 
     | 
    
         
             
                    mark_as_authorized(request)
         
     | 
| 
       82 
98 
     | 
    
         
             
                    @app.call(env)
         
     | 
| 
       83 
99 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -87,20 +103,15 @@ module ActionDispatch 
     | 
|
| 
       87 
103 
     | 
    
         | 
| 
       88 
104 
     | 
    
         
             
                private
         
     | 
| 
       89 
105 
     | 
    
         
             
                  def authorized?(request)
         
     | 
| 
       90 
     | 
    
         
            -
                     
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                       
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
                    forwarded_host = valid_host.match(
         
     | 
| 
       100 
     | 
    
         
            -
                      request.x_forwarded_host.to_s.split(/,\s?/).last)
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                    origin_host && @permissions.allows?(origin_host[:host]) && (
         
     | 
| 
       103 
     | 
    
         
            -
                      forwarded_host.nil? || @permissions.allows?(forwarded_host[:host]))
         
     | 
| 
      
 106 
     | 
    
         
            +
                    origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "")
         
     | 
| 
      
 107 
     | 
    
         
            +
                    forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "")
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    @permissions.allows?(origin_host) &&
         
     | 
| 
      
 110 
     | 
    
         
            +
                      (forwarded_host.blank? || @permissions.allows?(forwarded_host))
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                  def excluded?(request)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    @exclude && @exclude.call(request)
         
     | 
| 
       104 
115 
     | 
    
         
             
                  end
         
     | 
| 
       105 
116 
     | 
    
         | 
| 
       106 
117 
     | 
    
         
             
                  def mark_as_authorized(request)
         
     | 
| 
         @@ -23,7 +23,7 @@ module ActionDispatch 
     | 
|
| 
       23 
23 
     | 
    
         
             
                  status       = request.path_info[1..-1].to_i
         
     | 
| 
       24 
24 
     | 
    
         
             
                  begin
         
     | 
| 
       25 
25 
     | 
    
         
             
                    content_type = request.formats.first
         
     | 
| 
       26 
     | 
    
         
            -
                  rescue  
     | 
| 
      
 26 
     | 
    
         
            +
                  rescue ActionDispatch::Http::MimeNegotiation::InvalidType
         
     | 
| 
       27 
27 
     | 
    
         
             
                    content_type = Mime[:text]
         
     | 
| 
       28 
28 
     | 
    
         
             
                  end
         
     | 
| 
       29 
29 
     | 
    
         
             
                  body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
         
     | 
| 
         @@ -33,7 +33,7 @@ module ActionDispatch 
     | 
|
| 
       33 
33 
     | 
    
         
             
                # not be the ultimate client IP in production, and so are discarded. See
         
     | 
| 
       34 
34 
     | 
    
         
             
                # https://en.wikipedia.org/wiki/Private_network for details.
         
     | 
| 
       35 
35 
     | 
    
         
             
                TRUSTED_PROXIES = [
         
     | 
| 
       36 
     | 
    
         
            -
                  "127.0.0. 
     | 
| 
      
 36 
     | 
    
         
            +
                  "127.0.0.0/8",    # localhost IPv4 range, per RFC-3330
         
     | 
| 
       37 
37 
     | 
    
         
             
                  "::1",            # localhost IPv6
         
     | 
| 
       38 
38 
     | 
    
         
             
                  "fc00::/7",       # private IPv6 range fc00::/7
         
     | 
| 
       39 
39 
     | 
    
         
             
                  "10.0.0.0/8",     # private IPv4 range 10.x.x.x
         
     | 
| 
         @@ -143,10 +143,11 @@ module ActionDispatch 
     | 
|
| 
       143 
143 
     | 
    
         
             
                    #   - X-Forwarded-For will be a list of IPs, one per proxy, or blank
         
     | 
| 
       144 
144 
     | 
    
         
             
                    #   - Client-Ip is propagated from the outermost proxy, or is blank
         
     | 
| 
       145 
145 
     | 
    
         
             
                    #   - REMOTE_ADDR will be the IP that made the request to Rack
         
     | 
| 
       146 
     | 
    
         
            -
                    ips = [forwarded_ips, client_ips 
     | 
| 
      
 146 
     | 
    
         
            +
                    ips = [forwarded_ips, client_ips].flatten.compact
         
     | 
| 
       147 
147 
     | 
    
         | 
| 
       148 
     | 
    
         
            -
                    # If every single IP option is in the trusted list,  
     | 
| 
       149 
     | 
    
         
            -
                     
     | 
| 
      
 148 
     | 
    
         
            +
                    # If every single IP option is in the trusted list, return the IP
         
     | 
| 
      
 149 
     | 
    
         
            +
                    # that's furthest away
         
     | 
| 
      
 150 
     | 
    
         
            +
                    filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr
         
     | 
| 
       150 
151 
     | 
    
         
             
                  end
         
     | 
| 
       151 
152 
     | 
    
         | 
| 
       152 
153 
     | 
    
         
             
                  # Memoizes the value returned by #calculate_ip and returns it for
         
     | 
| 
         @@ -15,16 +15,15 @@ module ActionDispatch 
     | 
|
| 
       15 
15 
     | 
    
         
             
              # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
         
     | 
| 
       16 
16 
     | 
    
         
             
              # from multiple pieces of the stack.
         
     | 
| 
       17 
17 
     | 
    
         
             
              class RequestId
         
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                def initialize(app)
         
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(app, header:)
         
     | 
| 
       21 
19 
     | 
    
         
             
                  @app = app
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @header = header
         
     | 
| 
       22 
21 
     | 
    
         
             
                end
         
     | 
| 
       23 
22 
     | 
    
         | 
| 
       24 
23 
     | 
    
         
             
                def call(env)
         
     | 
| 
       25 
24 
     | 
    
         
             
                  req = ActionDispatch::Request.new env
         
     | 
| 
       26 
     | 
    
         
            -
                  req.request_id = make_request_id(req. 
     | 
| 
       27 
     | 
    
         
            -
                  @app.call(env).tap { |_status, headers, _body| headers[ 
     | 
| 
      
 25 
     | 
    
         
            +
                  req.request_id = make_request_id(req.headers[@header])
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id }
         
     | 
| 
       28 
27 
     | 
    
         
             
                end
         
     | 
| 
       29 
28 
     | 
    
         | 
| 
       30 
29 
     | 
    
         
             
                private
         
     | 
| 
         @@ -82,7 +82,7 @@ module ActionDispatch 
     | 
|
| 
       82 
82 
     | 
    
         
             
                  include SessionObject
         
     | 
| 
       83 
83 
     | 
    
         | 
| 
       84 
84 
     | 
    
         
             
                  private
         
     | 
| 
       85 
     | 
    
         
            -
                    def set_cookie(request,  
     | 
| 
      
 85 
     | 
    
         
            +
                    def set_cookie(request, response, cookie)
         
     | 
| 
       86 
86 
     | 
    
         
             
                      request.cookie_jar[key] = cookie
         
     | 
| 
       87 
87 
     | 
    
         
             
                    end
         
     | 
| 
       88 
88 
     | 
    
         
             
                end
         
     | 
| 
         @@ -97,7 +97,7 @@ module ActionDispatch 
     | 
|
| 
       97 
97 
     | 
    
         
             
                  end
         
     | 
| 
       98 
98 
     | 
    
         | 
| 
       99 
99 
     | 
    
         
             
                  private
         
     | 
| 
       100 
     | 
    
         
            -
                    def set_cookie(request,  
     | 
| 
      
 100 
     | 
    
         
            +
                    def set_cookie(request, response, cookie)
         
     | 
| 
       101 
101 
     | 
    
         
             
                      request.cookie_jar[key] = cookie
         
     | 
| 
       102 
102 
     | 
    
         
             
                    end
         
     | 
| 
       103 
103 
     | 
    
         
             
                end
         
     | 
| 
         @@ -10,8 +10,8 @@ module ActionDispatch 
     | 
|
| 
       10 
10 
     | 
    
         
             
                # dramatically faster than the alternatives.
         
     | 
| 
       11 
11 
     | 
    
         
             
                #
         
     | 
| 
       12 
12 
     | 
    
         
             
                # Sessions typically contain at most a user_id and flash message; both fit
         
     | 
| 
       13 
     | 
    
         
            -
                # within the  
     | 
| 
       14 
     | 
    
         
            -
                # you attempt to store more than  
     | 
| 
      
 13 
     | 
    
         
            +
                # within the 4096 bytes cookie size limit. A CookieOverflow exception is raised if
         
     | 
| 
      
 14 
     | 
    
         
            +
                # you attempt to store more than 4096 bytes of data.
         
     | 
| 
       15 
15 
     | 
    
         
             
                #
         
     | 
| 
       16 
16 
     | 
    
         
             
                # The cookie jar used for storage is automatically configured to be the
         
     | 
| 
       17 
17 
     | 
    
         
             
                # best possible option given your application's configuration.
         
     | 
| 
         @@ -13,7 +13,7 @@ module ActionDispatch 
     | 
|
| 
       13 
13 
     | 
    
         
             
              #
         
     | 
| 
       14 
14 
     | 
    
         
             
              #    Requests can opt-out of redirection with +exclude+:
         
     | 
| 
       15 
15 
     | 
    
         
             
              #
         
     | 
| 
       16 
     | 
    
         
            -
              #      config.ssl_options = { redirect: { exclude: -> request { request.path  
     | 
| 
      
 16 
     | 
    
         
            +
              #      config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
         
     | 
| 
       17 
17 
     | 
    
         
             
              #
         
     | 
| 
       18 
18 
     | 
    
         
             
              #    Cookies will not be flagged as secure for excluded requests.
         
     | 
| 
       19 
19 
     | 
    
         
             
              #
         
     | 
| 
         @@ -29,7 +29,7 @@ module ActionDispatch 
     | 
|
| 
       29 
29 
     | 
    
         
             
              #
         
     | 
| 
       30 
30 
     | 
    
         
             
              #    * +expires+: How long, in seconds, these settings will stick. The minimum
         
     | 
| 
       31 
31 
     | 
    
         
             
              #      required to qualify for browser preload lists is 1 year. Defaults to
         
     | 
| 
       32 
     | 
    
         
            -
              #       
     | 
| 
      
 32 
     | 
    
         
            +
              #      2 years (recommended).
         
     | 
| 
       33 
33 
     | 
    
         
             
              #
         
     | 
| 
       34 
34 
     | 
    
         
             
              #    * +subdomains+: Set to +true+ to tell the browser to apply these settings
         
     | 
| 
       35 
35 
     | 
    
         
             
              #      to all subdomains. This protects your cookies from interception by a
         
     | 
| 
         @@ -49,14 +49,14 @@ module ActionDispatch 
     | 
|
| 
       49 
49 
     | 
    
         
             
              class SSL
         
     | 
| 
       50 
50 
     | 
    
         
             
                # :stopdoc:
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
                # Default to  
     | 
| 
       53 
     | 
    
         
            -
                HSTS_EXPIRES_IN =  
     | 
| 
      
 52 
     | 
    
         
            +
                # Default to 2 years as recommended on hstspreload.org.
         
     | 
| 
      
 53 
     | 
    
         
            +
                HSTS_EXPIRES_IN = 63072000
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
       55 
55 
     | 
    
         
             
                def self.default_hsts_options
         
     | 
| 
       56 
56 
     | 
    
         
             
                  { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
         
     | 
| 
       57 
57 
     | 
    
         
             
                end
         
     | 
| 
       58 
58 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                def initialize(app, redirect: {}, hsts: {}, secure_cookies: true)
         
     | 
| 
      
 59 
     | 
    
         
            +
                def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
         
     | 
| 
       60 
60 
     | 
    
         
             
                  @app = app
         
     | 
| 
       61 
61 
     | 
    
         | 
| 
       62 
62 
     | 
    
         
             
                  @redirect = redirect
         
     | 
| 
         @@ -65,6 +65,7 @@ module ActionDispatch 
     | 
|
| 
       65 
65 
     | 
    
         
             
                  @secure_cookies = secure_cookies
         
     | 
| 
       66 
66 
     | 
    
         | 
| 
       67 
67 
     | 
    
         
             
                  @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
         
     | 
| 
      
 68 
     | 
    
         
            +
                  @ssl_default_redirect_status = ssl_default_redirect_status
         
     | 
| 
       68 
69 
     | 
    
         
             
                end
         
     | 
| 
       69 
70 
     | 
    
         | 
| 
       70 
71 
     | 
    
         
             
                def call(env)
         
     | 
| 
         @@ -126,12 +127,14 @@ module ActionDispatch 
     | 
|
| 
       126 
127 
     | 
    
         
             
                    [ @redirect.fetch(:status, redirection_status(request)),
         
     | 
| 
       127 
128 
     | 
    
         
             
                      { "Content-Type" => "text/html",
         
     | 
| 
       128 
129 
     | 
    
         
             
                        "Location" => https_location_for(request) },
         
     | 
| 
       129 
     | 
    
         
            -
                      @redirect 
     | 
| 
      
 130 
     | 
    
         
            +
                      (@redirect[:body] || []) ]
         
     | 
| 
       130 
131 
     | 
    
         
             
                  end
         
     | 
| 
       131 
132 
     | 
    
         | 
| 
       132 
133 
     | 
    
         
             
                  def redirection_status(request)
         
     | 
| 
       133 
134 
     | 
    
         
             
                    if request.get? || request.head?
         
     | 
| 
       134 
135 
     | 
    
         
             
                      301 # Issue a permanent redirect via a GET request.
         
     | 
| 
      
 136 
     | 
    
         
            +
                    elsif @ssl_default_redirect_status
         
     | 
| 
      
 137 
     | 
    
         
            +
                      @ssl_default_redirect_status
         
     | 
| 
       135 
138 
     | 
    
         
             
                    else
         
     | 
| 
       136 
139 
     | 
    
         
             
                      307 # Issue a fresh request redirect to preserve the HTTP method.
         
     | 
| 
       137 
140 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -122,6 +122,24 @@ module ActionDispatch 
     | 
|
| 
       122 
122 
     | 
    
         
             
                  middlewares.delete_if { |m| m.klass == target }
         
     | 
| 
       123 
123 
     | 
    
         
             
                end
         
     | 
| 
       124 
124 
     | 
    
         | 
| 
      
 125 
     | 
    
         
            +
                def move(target, source)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  source_index = assert_index(source, :before)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  source_middleware = middlewares.delete_at(source_index)
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                  target_index = assert_index(target, :before)
         
     | 
| 
      
 130 
     | 
    
         
            +
                  middlewares.insert(target_index, source_middleware)
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                alias_method :move_before, :move
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                def move_after(target, source)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  source_index = assert_index(source, :after)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  source_middleware = middlewares.delete_at(source_index)
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  target_index = assert_index(target, :after)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  middlewares.insert(target_index + 1, source_middleware)
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
       125 
143 
     | 
    
         
             
                def use(klass, *args, &block)
         
     | 
| 
       126 
144 
     | 
    
         
             
                  middlewares.push(build_middleware(klass, args, block))
         
     | 
| 
       127 
145 
     | 
    
         
             
                end
         
     | 
| 
         @@ -4,126 +4,187 @@ require "rack/utils" 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require "active_support/core_ext/uri"
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module ActionDispatch
         
     | 
| 
       7 
     | 
    
         
            -
              # This middleware  
     | 
| 
       8 
     | 
    
         
            -
              #  
     | 
| 
       9 
     | 
    
         
            -
              # when a response containing a file's contents is delivered.
         
     | 
| 
      
 7 
     | 
    
         
            +
              # This middleware serves static files from disk, if available.
         
     | 
| 
      
 8 
     | 
    
         
            +
              # If no file is found, it hands off to the main app.
         
     | 
| 
       10 
9 
     | 
    
         
             
              #
         
     | 
| 
       11 
     | 
    
         
            -
              #  
     | 
| 
       12 
     | 
    
         
            -
              #  
     | 
| 
       13 
     | 
    
         
            -
              # 
     | 
| 
       14 
     | 
    
         
            -
              #  
     | 
| 
       15 
     | 
    
         
            -
              #  
     | 
| 
       16 
     | 
    
         
            -
              # 
     | 
| 
       17 
     | 
    
         
            -
               
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                  @ 
     | 
| 
       21 
     | 
    
         
            -
                  @ 
     | 
| 
      
 10 
     | 
    
         
            +
              # In Rails apps, this middleware is configured to serve assets from
         
     | 
| 
      
 11 
     | 
    
         
            +
              # the +public/+ directory.
         
     | 
| 
      
 12 
     | 
    
         
            +
              #
         
     | 
| 
      
 13 
     | 
    
         
            +
              # Only GET and HEAD requests are served. POST and other HTTP methods
         
     | 
| 
      
 14 
     | 
    
         
            +
              # are handed off to the main app.
         
     | 
| 
      
 15 
     | 
    
         
            +
              #
         
     | 
| 
      
 16 
     | 
    
         
            +
              # Only files in the root directory are served; path traversal is denied.
         
     | 
| 
      
 17 
     | 
    
         
            +
              class Static
         
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(app, path, index: "index", headers: {})
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @file_handler = FileHandler.new(path, index: index, headers: headers)
         
     | 
| 
       22 
21 
     | 
    
         
             
                end
         
     | 
| 
       23 
22 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                   
     | 
| 
      
 23 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @file_handler.attempt(env) || @app.call(env)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              # This endpoint serves static files from disk using Rack::File.
         
     | 
| 
      
 29 
     | 
    
         
            +
              #
         
     | 
| 
      
 30 
     | 
    
         
            +
              # URL paths are matched with static files according to expected
         
     | 
| 
      
 31 
     | 
    
         
            +
              # conventions: +path+, +path+.html, +path+/index.html.
         
     | 
| 
      
 32 
     | 
    
         
            +
              #
         
     | 
| 
      
 33 
     | 
    
         
            +
              # Precompressed versions of these files are checked first. Brotli (.br)
         
     | 
| 
      
 34 
     | 
    
         
            +
              # and gzip (.gz) files are supported. If +path+.br exists, this
         
     | 
| 
      
 35 
     | 
    
         
            +
              # endpoint returns that file with a +Content-Encoding: br+ header.
         
     | 
| 
      
 36 
     | 
    
         
            +
              #
         
     | 
| 
      
 37 
     | 
    
         
            +
              # If no matching file is found, this endpoint responds 404 Not Found.
         
     | 
| 
      
 38 
     | 
    
         
            +
              #
         
     | 
| 
      
 39 
     | 
    
         
            +
              # Pass the +root+ directory to search for matching files, an optional
         
     | 
| 
      
 40 
     | 
    
         
            +
              # +index: "index"+ to change the default +path+/index.html, and optional
         
     | 
| 
      
 41 
     | 
    
         
            +
              # additional response headers.
         
     | 
| 
      
 42 
     | 
    
         
            +
              class FileHandler
         
     | 
| 
      
 43 
     | 
    
         
            +
                # Accept-Encoding value -> file extension
         
     | 
| 
      
 44 
     | 
    
         
            +
                PRECOMPRESSED = {
         
     | 
| 
      
 45 
     | 
    
         
            +
                  "br" => ".br",
         
     | 
| 
      
 46 
     | 
    
         
            +
                  "gzip" => ".gz",
         
     | 
| 
      
 47 
     | 
    
         
            +
                  "identity" => nil
         
     | 
| 
      
 48 
     | 
    
         
            +
                }
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def initialize(root, index: "index", headers: {}, precompressed: %i[ br gzip ], compressible_content_types: /\A(?:text\/|application\/javascript)/)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  @root = root.chomp("/").b
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @index = index
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  @precompressed = Array(precompressed).map(&:to_s) | %w[ identity ]
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @compressible_content_types = compressible_content_types
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  @file_server = ::Rack::File.new(@root, headers)
         
     | 
| 
       47 
58 
     | 
    
         
             
                end
         
     | 
| 
       48 
59 
     | 
    
         | 
| 
       49 
60 
     | 
    
         
             
                def call(env)
         
     | 
| 
       50 
     | 
    
         
            -
                   
     | 
| 
      
 61 
     | 
    
         
            +
                  attempt(env) || @file_server.call(env)
         
     | 
| 
       51 
62 
     | 
    
         
             
                end
         
     | 
| 
       52 
63 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
                def  
     | 
| 
       54 
     | 
    
         
            -
                   
     | 
| 
       55 
     | 
    
         
            -
                  gzip_path = gzip_file_path(path)
         
     | 
| 
      
 64 
     | 
    
         
            +
                def attempt(env)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  request = Rack::Request.new env
         
     | 
| 
       56 
66 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                  if  
     | 
| 
       58 
     | 
    
         
            -
                    request.path_info 
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                    if status == 304
         
     | 
| 
       61 
     | 
    
         
            -
                      return [status, headers, body]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  if request.get? || request.head?
         
     | 
| 
      
 68 
     | 
    
         
            +
                    if found = find_file(request.path_info, accept_encoding: request.accept_encoding)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      serve request, *found
         
     | 
| 
       62 
70 
     | 
    
         
             
                    end
         
     | 
| 
       63 
     | 
    
         
            -
                    headers["Content-Encoding"] = "gzip"
         
     | 
| 
       64 
     | 
    
         
            -
                    headers["Content-Type"]     = content_type(path)
         
     | 
| 
       65 
     | 
    
         
            -
                  else
         
     | 
| 
       66 
     | 
    
         
            -
                    status, headers, body = @file_server.call(request.env)
         
     | 
| 
       67 
71 
     | 
    
         
             
                  end
         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
                  headers["Vary"] = "Accept-Encoding" if gzip_path
         
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
                  [status, headers, body]
         
     | 
| 
       72 
     | 
    
         
            -
                ensure
         
     | 
| 
       73 
     | 
    
         
            -
                  request.path_info = path
         
     | 
| 
       74 
72 
     | 
    
         
             
                end
         
     | 
| 
       75 
73 
     | 
    
         | 
| 
       76 
74 
     | 
    
         
             
                private
         
     | 
| 
       77 
     | 
    
         
            -
                  def  
     | 
| 
       78 
     | 
    
         
            -
                     
     | 
| 
      
 75 
     | 
    
         
            +
                  def serve(request, filepath, content_headers)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    original, request.path_info =
         
     | 
| 
      
 77 
     | 
    
         
            +
                      request.path_info, ::Rack::Utils.escape_path(filepath).b
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                    @file_server.call(request.env).tap do |status, headers, body|
         
     | 
| 
      
 80 
     | 
    
         
            +
                      # Omit Content-Encoding/Type/etc headers for 304 Not Modified
         
     | 
| 
      
 81 
     | 
    
         
            +
                      if status != 304
         
     | 
| 
      
 82 
     | 
    
         
            +
                        headers.update(content_headers)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 86 
     | 
    
         
            +
                    request.path_info = original
         
     | 
| 
       79 
87 
     | 
    
         
             
                  end
         
     | 
| 
       80 
88 
     | 
    
         | 
| 
       81 
     | 
    
         
            -
                   
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
      
 89 
     | 
    
         
            +
                  # Match a URI path to a static file to be served.
         
     | 
| 
      
 90 
     | 
    
         
            +
                  #
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # Used by the +Static+ class to negotiate a servable file in the
         
     | 
| 
      
 92 
     | 
    
         
            +
                  # +public/+ directory (see Static#call).
         
     | 
| 
      
 93 
     | 
    
         
            +
                  #
         
     | 
| 
      
 94 
     | 
    
         
            +
                  # Checks for +path+, +path+.html, and +path+/index.html files,
         
     | 
| 
      
 95 
     | 
    
         
            +
                  # in that order, including .br and .gzip compressed extensions.
         
     | 
| 
      
 96 
     | 
    
         
            +
                  #
         
     | 
| 
      
 97 
     | 
    
         
            +
                  # If a matching file is found, the path and necessary response headers
         
     | 
| 
      
 98 
     | 
    
         
            +
                  # (Content-Type, Content-Encoding) are returned.
         
     | 
| 
      
 99 
     | 
    
         
            +
                  def find_file(path_info, accept_encoding:)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    each_candidate_filepath(path_info) do |filepath, content_type|
         
     | 
| 
      
 101 
     | 
    
         
            +
                      if response = try_files(filepath, content_type, accept_encoding: accept_encoding)
         
     | 
| 
      
 102 
     | 
    
         
            +
                        return response
         
     | 
| 
      
 103 
     | 
    
         
            +
                      end
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
       83 
105 
     | 
    
         
             
                  end
         
     | 
| 
       84 
106 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
                  def  
     | 
| 
       86 
     | 
    
         
            -
                     
     | 
| 
      
 107 
     | 
    
         
            +
                  def try_files(filepath, content_type, accept_encoding:)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    headers = { "Content-Type" => content_type }
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                    if compressible? content_type
         
     | 
| 
      
 111 
     | 
    
         
            +
                      try_precompressed_files filepath, headers, accept_encoding: accept_encoding
         
     | 
| 
      
 112 
     | 
    
         
            +
                    elsif file_readable? filepath
         
     | 
| 
      
 113 
     | 
    
         
            +
                      [ filepath, headers ]
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
       87 
115 
     | 
    
         
             
                  end
         
     | 
| 
       88 
116 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
                  def  
     | 
| 
       90 
     | 
    
         
            -
                     
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
             
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
      
 117 
     | 
    
         
            +
                  def try_precompressed_files(filepath, headers, accept_encoding:)
         
     | 
| 
      
 118 
     | 
    
         
            +
                    each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath|
         
     | 
| 
      
 119 
     | 
    
         
            +
                      if file_readable? precompressed_filepath
         
     | 
| 
      
 120 
     | 
    
         
            +
                        # Identity encoding is default, so we skip Accept-Encoding
         
     | 
| 
      
 121 
     | 
    
         
            +
                        # negotiation and needn't set Content-Encoding.
         
     | 
| 
      
 122 
     | 
    
         
            +
                        #
         
     | 
| 
      
 123 
     | 
    
         
            +
                        # Vary header is expected when we've found other available
         
     | 
| 
      
 124 
     | 
    
         
            +
                        # encodings that Accept-Encoding ruled out.
         
     | 
| 
      
 125 
     | 
    
         
            +
                        if content_encoding == "identity"
         
     | 
| 
      
 126 
     | 
    
         
            +
                          return precompressed_filepath, headers
         
     | 
| 
      
 127 
     | 
    
         
            +
                        else
         
     | 
| 
      
 128 
     | 
    
         
            +
                          headers["Vary"] = "Accept-Encoding"
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                          if accept_encoding.any? { |enc, _| /\b#{content_encoding}\b/i.match?(enc) }
         
     | 
| 
      
 131 
     | 
    
         
            +
                            headers["Content-Encoding"] = content_encoding
         
     | 
| 
      
 132 
     | 
    
         
            +
                            return precompressed_filepath, headers
         
     | 
| 
      
 133 
     | 
    
         
            +
                          end
         
     | 
| 
      
 134 
     | 
    
         
            +
                        end
         
     | 
| 
      
 135 
     | 
    
         
            +
                      end
         
     | 
| 
       96 
136 
     | 
    
         
             
                    end
         
     | 
| 
       97 
137 
     | 
    
         
             
                  end
         
     | 
| 
       98 
     | 
    
         
            -
              end
         
     | 
| 
       99 
138 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
             
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
              # produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
         
     | 
| 
       108 
     | 
    
         
            -
              # requests will result in a file being returned.
         
     | 
| 
       109 
     | 
    
         
            -
              class Static
         
     | 
| 
       110 
     | 
    
         
            -
                def initialize(app, path, index: "index", headers: {})
         
     | 
| 
       111 
     | 
    
         
            -
                  @app = app
         
     | 
| 
       112 
     | 
    
         
            -
                  @file_handler = FileHandler.new(path, index: index, headers: headers)
         
     | 
| 
       113 
     | 
    
         
            -
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
                  def file_readable?(path)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    file_stat = File.stat(File.join(@root, path.b))
         
     | 
| 
      
 141 
     | 
    
         
            +
                  rescue SystemCallError
         
     | 
| 
      
 142 
     | 
    
         
            +
                    false
         
     | 
| 
      
 143 
     | 
    
         
            +
                  else
         
     | 
| 
      
 144 
     | 
    
         
            +
                    file_stat.file? && file_stat.readable?
         
     | 
| 
      
 145 
     | 
    
         
            +
                  end
         
     | 
| 
       114 
146 
     | 
    
         | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
      
 147 
     | 
    
         
            +
                  def compressible?(content_type)
         
     | 
| 
      
 148 
     | 
    
         
            +
                    @compressible_content_types.match?(content_type)
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
       117 
150 
     | 
    
         | 
| 
       118 
     | 
    
         
            -
                   
     | 
| 
       119 
     | 
    
         
            -
                     
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
     | 
    
         
            -
                       
     | 
| 
       122 
     | 
    
         
            -
                      return @file_handler.serve(req)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  def each_precompressed_filepath(filepath)
         
     | 
| 
      
 152 
     | 
    
         
            +
                    @precompressed.each do |content_encoding|
         
     | 
| 
      
 153 
     | 
    
         
            +
                      precompressed_ext = PRECOMPRESSED.fetch(content_encoding)
         
     | 
| 
      
 154 
     | 
    
         
            +
                      yield content_encoding, "#{filepath}#{precompressed_ext}"
         
     | 
| 
       123 
155 
     | 
    
         
             
                    end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                    nil
         
     | 
| 
       124 
158 
     | 
    
         
             
                  end
         
     | 
| 
       125 
159 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                   
     | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
      
 160 
     | 
    
         
            +
                  def each_candidate_filepath(path_info)
         
     | 
| 
      
 161 
     | 
    
         
            +
                    return unless path = clean_path(path_info)
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                    ext = ::File.extname(path)
         
     | 
| 
      
 164 
     | 
    
         
            +
                    content_type = ::Rack::Mime.mime_type(ext, nil)
         
     | 
| 
      
 165 
     | 
    
         
            +
                    yield path, content_type || "text/plain"
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                    # Tack on .html and /index.html only for paths that don't have
         
     | 
| 
      
 168 
     | 
    
         
            +
                    # an explicit, resolvable file extension. No need to check
         
     | 
| 
      
 169 
     | 
    
         
            +
                    # for foo.js.html and foo.js/index.html.
         
     | 
| 
      
 170 
     | 
    
         
            +
                    unless content_type
         
     | 
| 
      
 171 
     | 
    
         
            +
                      default_ext = ::ActionController::Base.default_static_extension
         
     | 
| 
      
 172 
     | 
    
         
            +
                      if ext != default_ext
         
     | 
| 
      
 173 
     | 
    
         
            +
                        default_content_type = ::Rack::Mime.mime_type(default_ext, "text/plain")
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                        yield "#{path}#{default_ext}", default_content_type
         
     | 
| 
      
 176 
     | 
    
         
            +
                        yield "#{path}/#{@index}#{default_ext}", default_content_type
         
     | 
| 
      
 177 
     | 
    
         
            +
                      end
         
     | 
| 
      
 178 
     | 
    
         
            +
                    end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 181 
     | 
    
         
            +
                  end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                  def clean_path(path_info)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    path = ::Rack::Utils.unescape_path path_info.chomp("/")
         
     | 
| 
      
 185 
     | 
    
         
            +
                    if ::Rack::Utils.valid_path? path
         
     | 
| 
      
 186 
     | 
    
         
            +
                      ::Rack::Utils.clean_path_info path
         
     | 
| 
      
 187 
     | 
    
         
            +
                    end
         
     | 
| 
      
 188 
     | 
    
         
            +
                  end
         
     | 
| 
       128 
189 
     | 
    
         
             
              end
         
     | 
| 
       129 
190 
     | 
    
         
             
            end
         
     |