actionpack 5.0.0.beta1.1 → 5.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -28
- data/MIT-LICENSE +1 -1
- data/lib/abstract_controller/base.rb +2 -2
- data/lib/abstract_controller/rendering.rb +5 -5
- data/lib/action_controller.rb +4 -0
- data/lib/action_controller/api.rb +1 -1
- data/lib/action_controller/api/api_rendering.rb +14 -0
- data/lib/action_controller/metal.rb +2 -1
- data/lib/action_controller/metal/conditional_get.rb +1 -1
- data/lib/action_controller/metal/head.rb +0 -1
- data/lib/action_controller/metal/mime_responds.rb +9 -4
- data/lib/action_controller/metal/renderers.rb +75 -32
- data/lib/action_controller/metal/request_forgery_protection.rb +54 -11
- data/lib/action_controller/metal/strong_parameters.rb +33 -10
- data/lib/action_controller/test_case.rb +8 -8
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_dispatch/http/cache.rb +10 -2
- data/lib/action_dispatch/http/headers.rb +15 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +3 -3
- data/lib/action_dispatch/http/mime_type.rb +38 -47
- data/lib/action_dispatch/http/parameters.rb +1 -1
- data/lib/action_dispatch/http/request.rb +1 -1
- data/lib/action_dispatch/http/response.rb +8 -1
- data/lib/action_dispatch/journey/path/pattern.rb +1 -1
- data/lib/action_dispatch/middleware/ssl.rb +23 -17
- data/lib/action_dispatch/middleware/stack.rb +9 -0
- data/lib/action_dispatch/middleware/static.rb +5 -1
- data/lib/action_dispatch/request/session.rb +3 -3
- data/lib/action_dispatch/routing.rb +2 -1
- data/lib/action_dispatch/routing/inspector.rb +22 -10
- data/lib/action_dispatch/routing/mapper.rb +41 -35
- data/lib/action_dispatch/routing/route_set.rb +11 -2
- data/lib/action_dispatch/testing/assertion_response.rb +49 -0
- data/lib/action_dispatch/testing/assertions/response.rb +14 -14
- data/lib/action_dispatch/testing/test_process.rb +0 -1
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +1 -1
- metadata +12 -9
@@ -81,6 +81,10 @@ module ActionController #:nodoc:
|
|
81
81
|
config_accessor :forgery_protection_origin_check
|
82
82
|
self.forgery_protection_origin_check = false
|
83
83
|
|
84
|
+
# Controls whether form-action/method specific CSRF tokens are used.
|
85
|
+
config_accessor :per_form_csrf_tokens
|
86
|
+
self.per_form_csrf_tokens = false
|
87
|
+
|
84
88
|
helper_method :form_authenticity_token
|
85
89
|
helper_method :protect_against_forgery?
|
86
90
|
end
|
@@ -277,16 +281,25 @@ module ActionController #:nodoc:
|
|
277
281
|
end
|
278
282
|
|
279
283
|
# Sets the token value for the current session.
|
280
|
-
def form_authenticity_token
|
281
|
-
masked_authenticity_token(session)
|
284
|
+
def form_authenticity_token(form_options: {})
|
285
|
+
masked_authenticity_token(session, form_options: form_options)
|
282
286
|
end
|
283
287
|
|
284
288
|
# Creates a masked version of the authenticity token that varies
|
285
289
|
# on each request. The masking is used to mitigate SSL attacks
|
286
290
|
# like BREACH.
|
287
|
-
def masked_authenticity_token(session)
|
291
|
+
def masked_authenticity_token(session, form_options: {})
|
292
|
+
action, method = form_options.values_at(:action, :method)
|
293
|
+
|
294
|
+
raw_token = if per_form_csrf_tokens && action && method
|
295
|
+
action_path = normalize_action_path(action)
|
296
|
+
per_form_csrf_token(session, action_path, method)
|
297
|
+
else
|
298
|
+
real_csrf_token(session)
|
299
|
+
end
|
300
|
+
|
288
301
|
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
|
289
|
-
encrypted_csrf_token = xor_byte_strings(one_time_pad,
|
302
|
+
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
|
290
303
|
masked_token = one_time_pad + encrypted_csrf_token
|
291
304
|
Base64.strict_encode64(masked_token)
|
292
305
|
end
|
@@ -316,28 +329,54 @@ module ActionController #:nodoc:
|
|
316
329
|
compare_with_real_token masked_token, session
|
317
330
|
|
318
331
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
319
|
-
|
320
|
-
# value and decrypt it
|
321
|
-
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
322
|
-
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
323
|
-
csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
324
|
-
|
325
|
-
compare_with_real_token csrf_token, session
|
332
|
+
csrf_token = unmask_token(masked_token)
|
326
333
|
|
334
|
+
compare_with_real_token(csrf_token, session) ||
|
335
|
+
valid_per_form_csrf_token?(csrf_token, session)
|
327
336
|
else
|
328
337
|
false # Token is malformed
|
329
338
|
end
|
330
339
|
end
|
331
340
|
|
341
|
+
def unmask_token(masked_token)
|
342
|
+
# Split the token into the one-time pad and the encrypted
|
343
|
+
# value and decrypt it
|
344
|
+
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
345
|
+
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
346
|
+
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
347
|
+
end
|
348
|
+
|
332
349
|
def compare_with_real_token(token, session)
|
333
350
|
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
|
334
351
|
end
|
335
352
|
|
353
|
+
def valid_per_form_csrf_token?(token, session)
|
354
|
+
if per_form_csrf_tokens
|
355
|
+
correct_token = per_form_csrf_token(
|
356
|
+
session,
|
357
|
+
normalize_action_path(request.fullpath),
|
358
|
+
request.request_method
|
359
|
+
)
|
360
|
+
|
361
|
+
ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
|
362
|
+
else
|
363
|
+
false
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
336
367
|
def real_csrf_token(session)
|
337
368
|
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
|
338
369
|
Base64.strict_decode64(session[:_csrf_token])
|
339
370
|
end
|
340
371
|
|
372
|
+
def per_form_csrf_token(session, action_path, method)
|
373
|
+
OpenSSL::HMAC.digest(
|
374
|
+
OpenSSL::Digest::SHA256.new,
|
375
|
+
real_csrf_token(session),
|
376
|
+
[action_path, method.downcase].join("#")
|
377
|
+
)
|
378
|
+
end
|
379
|
+
|
341
380
|
def xor_byte_strings(s1, s2)
|
342
381
|
s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
|
343
382
|
end
|
@@ -362,5 +401,9 @@ module ActionController #:nodoc:
|
|
362
401
|
true
|
363
402
|
end
|
364
403
|
end
|
404
|
+
|
405
|
+
def normalize_action_path(action_path)
|
406
|
+
action_path.split('?').first.to_s.chomp('/')
|
407
|
+
end
|
365
408
|
end
|
366
409
|
end
|
@@ -109,7 +109,8 @@ module ActionController
|
|
109
109
|
cattr_accessor :permit_all_parameters, instance_accessor: false
|
110
110
|
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
|
111
111
|
|
112
|
-
delegate :keys, :key?, :has_key?, :empty?, :inspect,
|
112
|
+
delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect,
|
113
|
+
:as_json, to: :@parameters
|
113
114
|
|
114
115
|
# By default, never raise an UnpermittedParameters exception if these
|
115
116
|
# params are present. The default includes both 'controller' and 'action'
|
@@ -159,7 +160,11 @@ module ActionController
|
|
159
160
|
if other_hash.respond_to?(:permitted?)
|
160
161
|
super
|
161
162
|
else
|
162
|
-
|
163
|
+
if other_hash.is_a?(Hash)
|
164
|
+
@parameters == other_hash.with_indifferent_access
|
165
|
+
else
|
166
|
+
@parameters == other_hash
|
167
|
+
end
|
163
168
|
end
|
164
169
|
end
|
165
170
|
|
@@ -176,7 +181,7 @@ module ActionController
|
|
176
181
|
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
|
177
182
|
def to_h
|
178
183
|
if permitted?
|
179
|
-
convert_parameters_to_hashes(@parameters)
|
184
|
+
convert_parameters_to_hashes(@parameters, :to_h)
|
180
185
|
else
|
181
186
|
slice(*self.class.always_permitted_parameters).permit!.to_h
|
182
187
|
end
|
@@ -186,7 +191,7 @@ module ActionController
|
|
186
191
|
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
|
187
192
|
# parameter.
|
188
193
|
def to_unsafe_h
|
189
|
-
convert_parameters_to_hashes(@parameters)
|
194
|
+
convert_parameters_to_hashes(@parameters, :to_unsafe_h)
|
190
195
|
end
|
191
196
|
alias_method :to_unsafe_hash, :to_unsafe_h
|
192
197
|
|
@@ -419,7 +424,7 @@ module ActionController
|
|
419
424
|
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
|
420
425
|
# params.fetch(:none, 'Francesco') # => "Francesco"
|
421
426
|
# params.fetch(:none) { 'Francesco' } # => "Francesco"
|
422
|
-
def fetch(key, *args
|
427
|
+
def fetch(key, *args)
|
423
428
|
convert_value_to_parameters(
|
424
429
|
@parameters.fetch(key) {
|
425
430
|
if block_given?
|
@@ -514,7 +519,7 @@ module ActionController
|
|
514
519
|
# to key. If the key is not found, returns the default value. If the
|
515
520
|
# optional code block is given and the key is not found, pass in the key
|
516
521
|
# and return the result of block.
|
517
|
-
def delete(key
|
522
|
+
def delete(key)
|
518
523
|
convert_value_to_parameters(@parameters.delete(key))
|
519
524
|
end
|
520
525
|
|
@@ -579,6 +584,24 @@ module ActionController
|
|
579
584
|
dup
|
580
585
|
end
|
581
586
|
|
587
|
+
def method_missing(method_sym, *args, &block)
|
588
|
+
if @parameters.respond_to?(method_sym)
|
589
|
+
message = <<-DEPRECATE.squish
|
590
|
+
Method #{method_sym} is deprecated and will be removed in Rails 5.1,
|
591
|
+
as `ActionController::Parameters` no longer inherits from
|
592
|
+
hash. Using this deprecated behavior exposes potential security
|
593
|
+
problems. If you continue to use this method you may be creating
|
594
|
+
a security vulnerability in your app that can be exploited. Instead,
|
595
|
+
consider using one of these documented methods which are not
|
596
|
+
deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
|
597
|
+
DEPRECATE
|
598
|
+
ActiveSupport::Deprecation.warn(message)
|
599
|
+
@parameters.public_send(method_sym, *args, &block)
|
600
|
+
else
|
601
|
+
super
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
582
605
|
protected
|
583
606
|
def permitted=(new_permitted)
|
584
607
|
@permitted = new_permitted
|
@@ -595,16 +618,16 @@ module ActionController
|
|
595
618
|
end
|
596
619
|
end
|
597
620
|
|
598
|
-
def convert_parameters_to_hashes(value)
|
621
|
+
def convert_parameters_to_hashes(value, using)
|
599
622
|
case value
|
600
623
|
when Array
|
601
|
-
value.map { |v| convert_parameters_to_hashes(v) }
|
624
|
+
value.map { |v| convert_parameters_to_hashes(v, using) }
|
602
625
|
when Hash
|
603
626
|
value.transform_values do |v|
|
604
|
-
convert_parameters_to_hashes(v)
|
627
|
+
convert_parameters_to_hashes(v, using)
|
605
628
|
end.with_indifferent_access
|
606
629
|
when Parameters
|
607
|
-
value.
|
630
|
+
value.send(using)
|
608
631
|
else
|
609
632
|
value
|
610
633
|
end
|
@@ -8,6 +8,10 @@ require 'rails-dom-testing'
|
|
8
8
|
|
9
9
|
module ActionController
|
10
10
|
# :stopdoc:
|
11
|
+
class Metal
|
12
|
+
include Testing::Functional
|
13
|
+
end
|
14
|
+
|
11
15
|
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
|
12
16
|
# Please use ActionDispatch::IntegrationTest going forward.
|
13
17
|
class TestRequest < ActionDispatch::TestRequest #:nodoc:
|
@@ -455,16 +459,16 @@ module ActionController
|
|
455
459
|
parameters = nil
|
456
460
|
end
|
457
461
|
|
458
|
-
if parameters
|
462
|
+
if parameters || session || flash
|
459
463
|
non_kwarg_request_warning
|
460
464
|
end
|
461
465
|
end
|
462
466
|
|
463
|
-
if body
|
467
|
+
if body
|
464
468
|
@request.set_header 'RAW_POST_DATA', body
|
465
469
|
end
|
466
470
|
|
467
|
-
if http_method
|
471
|
+
if http_method
|
468
472
|
http_method = http_method.to_s.upcase
|
469
473
|
else
|
470
474
|
http_method = "GET"
|
@@ -472,16 +476,12 @@ module ActionController
|
|
472
476
|
|
473
477
|
parameters ||= {}
|
474
478
|
|
475
|
-
if format
|
479
|
+
if format
|
476
480
|
parameters[:format] = format
|
477
481
|
end
|
478
482
|
|
479
483
|
@html_document = nil
|
480
484
|
|
481
|
-
unless @controller.respond_to?(:recycle!)
|
482
|
-
@controller.extend(Testing::Functional)
|
483
|
-
end
|
484
|
-
|
485
485
|
self.cookies.update @request.cookies
|
486
486
|
self.cookies.update_cookies_from_jar
|
487
487
|
@request.set_header 'HTTP_COOKIE', cookies.to_header
|
data/lib/action_dispatch.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2004-
|
2
|
+
# Copyright (c) 2004-2016 David Heinemeier Hansson
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -95,6 +95,7 @@ module ActionDispatch
|
|
95
95
|
autoload :TestProcess
|
96
96
|
autoload :TestRequest
|
97
97
|
autoload :TestResponse
|
98
|
+
autoload :AssertionResponse
|
98
99
|
end
|
99
100
|
end
|
100
101
|
|
@@ -80,9 +80,17 @@ module ActionDispatch
|
|
80
80
|
set_header DATE, utc_time.httpdate
|
81
81
|
end
|
82
82
|
|
83
|
+
# This method allows you to set the ETag for cached content, which
|
84
|
+
# will be returned to the end user.
|
85
|
+
#
|
86
|
+
# By default, Action Dispatch sets all ETags to be weak.
|
87
|
+
# This ensures that if the content changes only semantically,
|
88
|
+
# the whole page doesn't have to be regenerated from scratch
|
89
|
+
# by the web server. With strong ETags, pages are compared
|
90
|
+
# byte by byte, and are regenerated only if they are not exactly equal.
|
83
91
|
def etag=(etag)
|
84
92
|
key = ActiveSupport::Cache.expand_cache_key(etag)
|
85
|
-
super %("#{Digest::MD5.hexdigest(key)}")
|
93
|
+
super %(W/"#{Digest::MD5.hexdigest(key)}")
|
86
94
|
end
|
87
95
|
|
88
96
|
def etag?; etag; end
|
@@ -91,7 +99,7 @@ module ActionDispatch
|
|
91
99
|
|
92
100
|
DATE = 'Date'.freeze
|
93
101
|
LAST_MODIFIED = "Last-Modified".freeze
|
94
|
-
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate])
|
102
|
+
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
|
95
103
|
|
96
104
|
def cache_control_segments
|
97
105
|
if cache_control = _cache_control
|
@@ -2,9 +2,23 @@ module ActionDispatch
|
|
2
2
|
module Http
|
3
3
|
# Provides access to the request's HTTP headers from the environment.
|
4
4
|
#
|
5
|
-
# env = { "CONTENT_TYPE" => "text/plain" }
|
5
|
+
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
6
6
|
# headers = ActionDispatch::Http::Headers.new(env)
|
7
7
|
# headers["Content-Type"] # => "text/plain"
|
8
|
+
# headers["User-Agent"] # => "curl/7/43/0"
|
9
|
+
#
|
10
|
+
# Also note that when headers are mapped to CGI-like variables by the Rack
|
11
|
+
# server, both dashes and underscores are converted to underscores. This
|
12
|
+
# ambiguity cannot be resolved at this stage anymore. Both underscores and
|
13
|
+
# dashes have to be interpreted as if they were originally sent as dashes.
|
14
|
+
#
|
15
|
+
# # GET / HTTP/1.1
|
16
|
+
# # ...
|
17
|
+
# # User-Agent: curl/7.43.0
|
18
|
+
# # X_Custom_Header: token
|
19
|
+
#
|
20
|
+
# headers["X_Custom_Header"] # => nil
|
21
|
+
# headers["X-Custom-Header"] # => "token"
|
8
22
|
class Headers
|
9
23
|
CGI_VARIABLES = Set.new(%W[
|
10
24
|
AUTH_TYPE
|
@@ -67,10 +67,10 @@ module ActionDispatch
|
|
67
67
|
|
68
68
|
v = if params_readable
|
69
69
|
Array(Mime[parameters[:format]])
|
70
|
-
elsif format = format_from_path_extension
|
71
|
-
Array(Mime[format])
|
72
70
|
elsif use_accept_header && valid_accept_header
|
73
71
|
accepts
|
72
|
+
elsif extension_format = format_from_path_extension
|
73
|
+
[extension_format]
|
74
74
|
elsif xhr?
|
75
75
|
[Mime[:js]]
|
76
76
|
else
|
@@ -166,7 +166,7 @@ module ActionDispatch
|
|
166
166
|
def format_from_path_extension
|
167
167
|
path = @env['action_dispatch.original_path'] || @env['PATH_INFO']
|
168
168
|
if match = path && path.match(/\.(\w+)\z/)
|
169
|
-
match.captures.first
|
169
|
+
Mime[match.captures.first]
|
170
170
|
end
|
171
171
|
end
|
172
172
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- frozen-string-literal: true -*-
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
require 'active_support/core_ext/module/attribute_accessors'
|
3
5
|
require 'active_support/core_ext/string/starts_ends_with'
|
@@ -106,70 +108,58 @@ module Mime
|
|
106
108
|
result = @index <=> item.index if result == 0
|
107
109
|
result
|
108
110
|
end
|
109
|
-
|
110
|
-
def ==(item)
|
111
|
-
@name == item.to_s
|
112
|
-
end
|
113
111
|
end
|
114
112
|
|
115
|
-
class AcceptList
|
116
|
-
def
|
117
|
-
sort!
|
113
|
+
class AcceptList #:nodoc:
|
114
|
+
def self.sort!(list)
|
115
|
+
list.sort!
|
116
|
+
|
117
|
+
text_xml_idx = find_item_by_name list, 'text/xml'
|
118
|
+
app_xml_idx = find_item_by_name list, Mime[:xml].to_s
|
118
119
|
|
119
120
|
# Take care of the broken text/xml entry by renaming or deleting it
|
120
121
|
if text_xml_idx && app_xml_idx
|
122
|
+
app_xml = list[app_xml_idx]
|
123
|
+
text_xml = list[text_xml_idx]
|
124
|
+
|
121
125
|
app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
|
122
|
-
|
123
|
-
|
126
|
+
if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
|
127
|
+
list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml
|
128
|
+
app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx
|
129
|
+
end
|
130
|
+
list.delete_at(text_xml_idx) # delete text_xml from the list
|
124
131
|
elsif text_xml_idx
|
125
|
-
|
132
|
+
list[text_xml_idx].name = Mime[:xml].to_s
|
126
133
|
end
|
127
134
|
|
128
135
|
# Look for more specific XML-based types and sort them ahead of app/xml
|
129
136
|
if app_xml_idx
|
137
|
+
app_xml = list[app_xml_idx]
|
130
138
|
idx = app_xml_idx
|
131
139
|
|
132
|
-
while idx < length
|
133
|
-
type =
|
140
|
+
while idx < list.length
|
141
|
+
type = list[idx]
|
134
142
|
break if type.q < app_xml.q
|
135
143
|
|
136
144
|
if type.name.ends_with? '+xml'
|
137
|
-
|
138
|
-
|
145
|
+
list[app_xml_idx], list[idx] = list[idx], app_xml
|
146
|
+
app_xml_idx = idx
|
139
147
|
end
|
140
148
|
idx += 1
|
141
149
|
end
|
142
150
|
end
|
143
151
|
|
144
|
-
map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
145
|
-
|
152
|
+
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
153
|
+
list
|
146
154
|
end
|
147
155
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
def app_xml_idx
|
154
|
-
@app_xml_idx ||= index(Mime[:xml].to_s)
|
155
|
-
end
|
156
|
-
|
157
|
-
def text_xml
|
158
|
-
self[text_xml_idx]
|
159
|
-
end
|
160
|
-
|
161
|
-
def app_xml
|
162
|
-
self[app_xml_idx]
|
163
|
-
end
|
164
|
-
|
165
|
-
def exchange_xml_items
|
166
|
-
self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml
|
167
|
-
@app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx
|
168
|
-
end
|
156
|
+
def self.find_item_by_name(array, name)
|
157
|
+
array.index { |item| item.name == name }
|
158
|
+
end
|
169
159
|
end
|
170
160
|
|
171
161
|
class << self
|
172
|
-
TRAILING_STAR_REGEXP =
|
162
|
+
TRAILING_STAR_REGEXP = /^(text|application)\/\*/
|
173
163
|
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
|
174
164
|
|
175
165
|
def register_callback(&block)
|
@@ -209,21 +199,22 @@ module Mime
|
|
209
199
|
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
|
210
200
|
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
|
211
201
|
else
|
212
|
-
list, index =
|
202
|
+
list, index = [], 0
|
213
203
|
accept_header.split(',').each do |header|
|
214
204
|
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
|
215
|
-
if params.present?
|
216
|
-
params.strip!
|
217
205
|
|
218
|
-
|
206
|
+
next unless params
|
207
|
+
params.strip!
|
208
|
+
next if params.empty?
|
209
|
+
|
210
|
+
params = parse_trailing_star(params) || [params]
|
219
211
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
212
|
+
params.each do |m|
|
213
|
+
list << AcceptItem.new(index, m.to_s, q)
|
214
|
+
index += 1
|
224
215
|
end
|
225
216
|
end
|
226
|
-
|
217
|
+
AcceptList.sort! list
|
227
218
|
end
|
228
219
|
end
|
229
220
|
|