rack 1.6.11 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/COPYING +1 -1
  3. data/HISTORY.md +138 -8
  4. data/README.rdoc +18 -28
  5. data/Rakefile +6 -14
  6. data/SPEC +10 -11
  7. data/contrib/rack_logo.svg +164 -111
  8. data/example/protectedlobster.rb +1 -1
  9. data/example/protectedlobster.ru +1 -1
  10. data/lib/rack.rb +70 -21
  11. data/lib/rack/auth/abstract/request.rb +5 -1
  12. data/lib/rack/auth/digest/params.rb +2 -3
  13. data/lib/rack/auth/digest/request.rb +1 -1
  14. data/lib/rack/body_proxy.rb +14 -9
  15. data/lib/rack/builder.rb +3 -3
  16. data/lib/rack/chunked.rb +5 -5
  17. data/lib/rack/{commonlogger.rb → common_logger.rb} +3 -3
  18. data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
  19. data/lib/rack/content_length.rb +2 -2
  20. data/lib/rack/deflater.rb +4 -39
  21. data/lib/rack/directory.rb +66 -54
  22. data/lib/rack/etag.rb +5 -4
  23. data/lib/rack/events.rb +154 -0
  24. data/lib/rack/file.rb +64 -40
  25. data/lib/rack/handler.rb +3 -25
  26. data/lib/rack/handler/cgi.rb +15 -16
  27. data/lib/rack/handler/fastcgi.rb +13 -14
  28. data/lib/rack/handler/lsws.rb +11 -11
  29. data/lib/rack/handler/scgi.rb +15 -15
  30. data/lib/rack/handler/thin.rb +3 -0
  31. data/lib/rack/handler/webrick.rb +24 -26
  32. data/lib/rack/head.rb +15 -17
  33. data/lib/rack/lint.rb +40 -40
  34. data/lib/rack/lobster.rb +1 -1
  35. data/lib/rack/lock.rb +15 -10
  36. data/lib/rack/logger.rb +2 -2
  37. data/lib/rack/media_type.rb +38 -0
  38. data/lib/rack/{methodoverride.rb → method_override.rb} +6 -6
  39. data/lib/rack/mime.rb +18 -5
  40. data/lib/rack/mock.rb +36 -54
  41. data/lib/rack/multipart.rb +35 -6
  42. data/lib/rack/multipart/generator.rb +5 -5
  43. data/lib/rack/multipart/parser.rb +270 -157
  44. data/lib/rack/multipart/uploaded_file.rb +1 -2
  45. data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
  46. data/lib/rack/query_parser.rb +192 -0
  47. data/lib/rack/recursive.rb +8 -8
  48. data/lib/rack/request.rb +394 -305
  49. data/lib/rack/response.rb +130 -57
  50. data/lib/rack/rewindable_input.rb +1 -12
  51. data/lib/rack/runtime.rb +10 -18
  52. data/lib/rack/sendfile.rb +5 -7
  53. data/lib/rack/server.rb +30 -23
  54. data/lib/rack/session/abstract/id.rb +121 -75
  55. data/lib/rack/session/cookie.rb +25 -18
  56. data/lib/rack/session/memcache.rb +2 -2
  57. data/lib/rack/session/pool.rb +9 -9
  58. data/lib/rack/show_exceptions.rb +386 -0
  59. data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
  60. data/lib/rack/static.rb +30 -5
  61. data/lib/rack/tempfile_reaper.rb +2 -2
  62. data/lib/rack/urlmap.rb +15 -14
  63. data/lib/rack/utils.rb +136 -211
  64. data/rack.gemspec +10 -9
  65. data/test/builder/an_underscore_app.rb +5 -0
  66. data/test/builder/options.ru +1 -1
  67. data/test/cgi/test.fcgi +1 -0
  68. data/test/cgi/test.gz +0 -0
  69. data/test/helper.rb +34 -0
  70. data/test/multipart/filename_with_encoded_words +7 -0
  71. data/test/multipart/filename_with_single_quote +7 -0
  72. data/test/multipart/quoted +15 -0
  73. data/test/multipart/rack-logo.png +0 -0
  74. data/test/multipart/unity3d_wwwform +11 -0
  75. data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
  76. data/test/spec_auth_basic.rb +27 -19
  77. data/test/spec_auth_digest.rb +47 -46
  78. data/test/spec_body_proxy.rb +27 -27
  79. data/test/spec_builder.rb +51 -41
  80. data/test/spec_cascade.rb +24 -22
  81. data/test/spec_cgi.rb +49 -67
  82. data/test/spec_chunked.rb +37 -35
  83. data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
  84. data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
  85. data/test/spec_config.rb +3 -2
  86. data/test/spec_content_length.rb +18 -17
  87. data/test/spec_content_type.rb +13 -12
  88. data/test/spec_deflater.rb +85 -49
  89. data/test/spec_directory.rb +87 -27
  90. data/test/spec_etag.rb +32 -31
  91. data/test/spec_events.rb +133 -0
  92. data/test/spec_fastcgi.rb +50 -72
  93. data/test/spec_file.rb +120 -77
  94. data/test/spec_handler.rb +19 -34
  95. data/test/spec_head.rb +15 -14
  96. data/test/spec_lint.rb +164 -199
  97. data/test/spec_lobster.rb +24 -23
  98. data/test/spec_lock.rb +79 -39
  99. data/test/spec_logger.rb +4 -3
  100. data/test/spec_media_type.rb +42 -0
  101. data/test/{spec_methodoverride.rb → spec_method_override.rb} +34 -35
  102. data/test/spec_mime.rb +19 -19
  103. data/test/spec_mock.rb +206 -144
  104. data/test/spec_multipart.rb +322 -200
  105. data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
  106. data/test/spec_recursive.rb +17 -14
  107. data/test/spec_request.rb +780 -605
  108. data/test/spec_response.rb +215 -112
  109. data/test/spec_rewindable_input.rb +50 -40
  110. data/test/spec_runtime.rb +11 -10
  111. data/test/spec_sendfile.rb +30 -35
  112. data/test/spec_server.rb +78 -52
  113. data/test/spec_session_abstract_id.rb +11 -33
  114. data/test/spec_session_abstract_session_hash.rb +45 -0
  115. data/test/spec_session_cookie.rb +99 -67
  116. data/test/spec_session_memcache.rb +60 -61
  117. data/test/spec_session_pool.rb +45 -44
  118. data/test/{spec_showexceptions.rb → spec_show_exceptions.rb} +22 -27
  119. data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
  120. data/test/spec_static.rb +71 -32
  121. data/test/spec_tempfile_reaper.rb +11 -10
  122. data/test/spec_thin.rb +55 -50
  123. data/test/spec_urlmap.rb +79 -78
  124. data/test/spec_utils.rb +441 -346
  125. data/test/spec_version.rb +2 -8
  126. data/test/spec_webrick.rb +93 -71
  127. data/test/static/foo.html +1 -0
  128. data/test/testrequest.rb +1 -1
  129. data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
  130. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
  131. metadata +92 -71
  132. data/KNOWN-ISSUES +0 -44
  133. data/lib/rack/backports/uri/common_18.rb +0 -56
  134. data/lib/rack/backports/uri/common_192.rb +0 -52
  135. data/lib/rack/backports/uri/common_193.rb +0 -29
  136. data/lib/rack/handler/evented_mongrel.rb +0 -8
  137. data/lib/rack/handler/mongrel.rb +0 -106
  138. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  139. data/lib/rack/showexceptions.rb +0 -387
  140. data/lib/rack/utils/okjson.rb +0 -600
  141. data/test/spec_mongrel.rb +0 -182
@@ -11,8 +11,7 @@ module Rack
11
11
  raise "#{path} file does not exist" unless ::File.exist?(path)
12
12
  @content_type = content_type
13
13
  @original_filename = ::File.basename(path)
14
- @tempfile = Tempfile.new([@original_filename, ::File.extname(path)])
15
- @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
14
+ @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
16
15
  @tempfile.binmode if binary
17
16
  FileUtils.copy_file(path, @tempfile.path)
18
17
  end
@@ -5,7 +5,7 @@ module Rack
5
5
  end
6
6
 
7
7
  def call(env)
8
- env['rack.logger'] = self
8
+ env[RACK_LOGGER] = self
9
9
  @app.call(env)
10
10
  end
11
11
 
@@ -0,0 +1,192 @@
1
+ module Rack
2
+ class QueryParser
3
+ DEFAULT_SEP = /[&;] */n
4
+ COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
5
+
6
+ # ParameterTypeError is the error that is raised when incoming structural
7
+ # parameters (parsed by parse_nested_query) contain conflicting types.
8
+ class ParameterTypeError < TypeError; end
9
+
10
+ # InvalidParameterError is the error that is raised when incoming structural
11
+ # parameters (parsed by parse_nested_query) contain invalid format or byte
12
+ # sequence.
13
+ class InvalidParameterError < ArgumentError; end
14
+
15
+ def self.make_default(key_space_limit, param_depth_limit)
16
+ new Params, key_space_limit, param_depth_limit
17
+ end
18
+
19
+ attr_reader :key_space_limit, :param_depth_limit
20
+
21
+ def initialize(params_class, key_space_limit, param_depth_limit)
22
+ @params_class = params_class
23
+ @key_space_limit = key_space_limit
24
+ @param_depth_limit = param_depth_limit
25
+ end
26
+
27
+ # Stolen from Mongrel, with some small modifications:
28
+ # Parses a query string by breaking it up at the '&'
29
+ # and ';' characters. You can also use this to parse
30
+ # cookies by changing the characters used in the second
31
+ # parameter (which defaults to '&;').
32
+ def parse_query(qs, d = nil, &unescaper)
33
+ unescaper ||= method(:unescape)
34
+
35
+ params = make_params
36
+
37
+ (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
38
+ next if p.empty?
39
+ k, v = p.split('='.freeze, 2).map!(&unescaper)
40
+
41
+ if cur = params[k]
42
+ if cur.class == Array
43
+ params[k] << v
44
+ else
45
+ params[k] = [cur, v]
46
+ end
47
+ else
48
+ params[k] = v
49
+ end
50
+ end
51
+
52
+ return params.to_params_hash
53
+ end
54
+
55
+ # parse_nested_query expands a query string into structural types. Supported
56
+ # types are Arrays, Hashes and basic value types. It is possible to supply
57
+ # query strings with parameters of conflicting types, in this case a
58
+ # ParameterTypeError is raised. Users are encouraged to return a 400 in this
59
+ # case.
60
+ def parse_nested_query(qs, d = nil)
61
+ return {} if qs.nil? || qs.empty?
62
+ params = make_params
63
+
64
+ (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
65
+ k, v = p.split('='.freeze, 2).map! { |s| unescape(s) }
66
+
67
+ normalize_params(params, k, v, param_depth_limit)
68
+ end
69
+
70
+ return params.to_params_hash
71
+ rescue ArgumentError => e
72
+ raise InvalidParameterError, e.message
73
+ end
74
+
75
+ # normalize_params recursively expands parameters into structural types. If
76
+ # the structural types represented by two different parameter names are in
77
+ # conflict, a ParameterTypeError is raised.
78
+ def normalize_params(params, name, v, depth)
79
+ raise RangeError if depth <= 0
80
+
81
+ name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
82
+ k = $1 || ''.freeze
83
+ after = $' || ''.freeze
84
+
85
+ if k.empty?
86
+ if !v.nil? && name == "[]".freeze
87
+ return Array(v)
88
+ else
89
+ return
90
+ end
91
+ end
92
+
93
+ if after == ''.freeze
94
+ params[k] = v
95
+ elsif after == "[".freeze
96
+ params[name] = v
97
+ elsif after == "[]".freeze
98
+ params[k] ||= []
99
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
100
+ params[k] << v
101
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
102
+ child_key = $1
103
+ params[k] ||= []
104
+ raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
105
+ if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
106
+ normalize_params(params[k].last, child_key, v, depth - 1)
107
+ else
108
+ params[k] << normalize_params(make_params, child_key, v, depth - 1)
109
+ end
110
+ else
111
+ params[k] ||= make_params
112
+ raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
113
+ params[k] = normalize_params(params[k], after, v, depth - 1)
114
+ end
115
+
116
+ params
117
+ end
118
+
119
+ def make_params
120
+ @params_class.new @key_space_limit
121
+ end
122
+
123
+ def new_space_limit(key_space_limit)
124
+ self.class.new @params_class, key_space_limit, param_depth_limit
125
+ end
126
+
127
+ def new_depth_limit(param_depth_limit)
128
+ self.class.new @params_class, key_space_limit, param_depth_limit
129
+ end
130
+
131
+ private
132
+
133
+ def params_hash_type?(obj)
134
+ obj.kind_of?(@params_class)
135
+ end
136
+
137
+ def params_hash_has_key?(hash, key)
138
+ return false if key =~ /\[\]/
139
+
140
+ key.split(/[\[\]]+/).inject(hash) do |h, part|
141
+ next h if part == ''
142
+ return false unless params_hash_type?(h) && h.key?(part)
143
+ h[part]
144
+ end
145
+
146
+ true
147
+ end
148
+
149
+ def unescape(s)
150
+ Utils.unescape(s)
151
+ end
152
+
153
+ class Params
154
+ def initialize(limit)
155
+ @limit = limit
156
+ @size = 0
157
+ @params = {}
158
+ end
159
+
160
+ def [](key)
161
+ @params[key]
162
+ end
163
+
164
+ def []=(key, value)
165
+ @size += key.size if key && !@params.key?(key)
166
+ raise RangeError, 'exceeded available parameter key space' if @size > @limit
167
+ @params[key] = value
168
+ end
169
+
170
+ def key?(key)
171
+ @params.key?(key)
172
+ end
173
+
174
+ def to_params_hash
175
+ hash = @params
176
+ hash.keys.each do |key|
177
+ value = hash[key]
178
+ if value.kind_of?(self.class)
179
+ if value.object_id == self.object_id
180
+ hash[key] = hash
181
+ else
182
+ hash[key] = value.to_params_hash
183
+ end
184
+ elsif value.kind_of?(Array)
185
+ value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
186
+ end
187
+ end
188
+ hash
189
+ end
190
+ end
191
+ end
192
+ end
@@ -14,11 +14,11 @@ module Rack
14
14
  @url = URI(url)
15
15
  @env = env
16
16
 
17
- @env[PATH_INFO] = @url.path
18
- @env[QUERY_STRING] = @url.query if @url.query
19
- @env["HTTP_HOST"] = @url.host if @url.host
20
- @env["HTTP_PORT"] = @url.port if @url.port
21
- @env["rack.url_scheme"] = @url.scheme if @url.scheme
17
+ @env[PATH_INFO] = @url.path
18
+ @env[QUERY_STRING] = @url.query if @url.query
19
+ @env[HTTP_HOST] = @url.host if @url.host
20
+ @env["HTTP_PORT"] = @url.port if @url.port
21
+ @env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
22
22
 
23
23
  super "forwarding to #{url}"
24
24
  end
@@ -40,7 +40,7 @@ module Rack
40
40
 
41
41
  def _call(env)
42
42
  @script_name = env[SCRIPT_NAME]
43
- @app.call(env.merge('rack.recursive.include' => method(:include)))
43
+ @app.call(env.merge(RACK_RECURSIVE_INCLUDE => method(:include)))
44
44
  rescue ForwardRequest => req
45
45
  call(env.merge(req.env))
46
46
  end
@@ -53,9 +53,9 @@ module Rack
53
53
 
54
54
  env = env.merge(PATH_INFO => path,
55
55
  SCRIPT_NAME => @script_name,
56
- REQUEST_METHOD => "GET",
56
+ REQUEST_METHOD => GET,
57
57
  "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "",
58
- "rack.input" => StringIO.new(""))
58
+ RACK_INPUT => StringIO.new(""))
59
59
  @app.call(env)
60
60
  end
61
61
  end
@@ -1,4 +1,5 @@
1
1
  require 'rack/utils'
2
+ require 'rack/media_type'
2
3
 
3
4
  module Rack
4
5
  # Rack::Request provides a convenient interface to a Rack
@@ -10,371 +11,444 @@ module Rack
10
11
  # req.params["data"]
11
12
 
12
13
  class Request
13
- # The environment of the request.
14
- attr_reader :env
15
-
16
14
  SCHEME_WHITELIST = %w(https http).freeze
17
15
 
18
16
  def initialize(env)
19
- @env = env
17
+ @params = nil
18
+ super(env)
20
19
  end
21
20
 
22
- def body; @env["rack.input"] end
23
- def script_name; @env[SCRIPT_NAME].to_s end
24
- def path_info; @env[PATH_INFO].to_s end
25
- def request_method; @env["REQUEST_METHOD"] end
26
- def query_string; @env[QUERY_STRING].to_s end
27
- def content_length; @env['CONTENT_LENGTH'] end
28
-
29
- def content_type
30
- content_type = @env['CONTENT_TYPE']
31
- content_type.nil? || content_type.empty? ? nil : content_type
21
+ def params
22
+ @params ||= super
32
23
  end
33
24
 
34
- def session; @env['rack.session'] ||= {} end
35
- def session_options; @env['rack.session.options'] ||= {} end
36
- def logger; @env['rack.logger'] end
37
-
38
- # The media type (type/subtype) portion of the CONTENT_TYPE header
39
- # without any media type parameters. e.g., when CONTENT_TYPE is
40
- # "text/plain;charset=utf-8", the media-type is "text/plain".
41
- #
42
- # For more information on the use of media types in HTTP, see:
43
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
44
- def media_type
45
- content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
25
+ def update_param(k, v)
26
+ super
27
+ @params = nil
46
28
  end
47
29
 
48
- # The media type parameters provided in CONTENT_TYPE as a Hash, or
49
- # an empty Hash if no CONTENT_TYPE or media-type parameters were
50
- # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
51
- # this method responds with the following Hash:
52
- # { 'charset' => 'utf-8' }
53
- def media_type_params
54
- return {} if content_type.nil?
55
- Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
56
- collect { |s| s.split('=', 2) }.
57
- map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
30
+ def delete_param(k)
31
+ v = super
32
+ @params = nil
33
+ v
58
34
  end
59
35
 
60
- # The character set of the request body if a "charset" media type
61
- # parameter was given, or nil if no "charset" was specified. Note
62
- # that, per RFC2616, text/* media types that specify no explicit
63
- # charset are to be considered ISO-8859-1.
64
- def content_charset
65
- media_type_params['charset']
66
- end
36
+ module Env
37
+ # The environment of the request.
38
+ attr_reader :env
67
39
 
68
- def scheme
69
- if @env['HTTPS'] == 'on'
70
- 'https'
71
- elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
72
- 'https'
73
- elsif forwarded_scheme
74
- forwarded_scheme
75
- else
76
- @env["rack.url_scheme"]
40
+ def initialize(env)
41
+ @env = env
42
+ super()
77
43
  end
78
- end
79
44
 
80
- def ssl?
81
- scheme == 'https'
82
- end
45
+ # Predicate method to test to see if `name` has been set as request
46
+ # specific data
47
+ def has_header?(name)
48
+ @env.key? name
49
+ end
83
50
 
84
- def host_with_port
85
- if forwarded = @env["HTTP_X_FORWARDED_HOST"]
86
- forwarded.split(/,\s?/).last
87
- else
88
- @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
51
+ # Get a request specific value for `name`.
52
+ def get_header(name)
53
+ @env[name]
89
54
  end
90
- end
91
55
 
92
- def port
93
- if port = host_with_port.split(/:/)[1]
94
- port.to_i
95
- elsif port = @env['HTTP_X_FORWARDED_PORT']
96
- port.to_i
97
- elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
98
- DEFAULT_PORTS[scheme]
99
- elsif @env.has_key?("HTTP_X_FORWARDED_PROTO")
100
- DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]]
101
- else
102
- @env["SERVER_PORT"].to_i
56
+ # If a block is given, it yields to the block if the value hasn't been set
57
+ # on the request.
58
+ def fetch_header(name, &block)
59
+ @env.fetch(name, &block)
103
60
  end
104
- end
105
61
 
106
- def host
107
- # Remove port number.
108
- host_with_port.to_s.sub(/:\d+\z/, '')
109
- end
62
+ # Loops through each key / value pair in the request specific data.
63
+ def each_header(&block)
64
+ @env.each(&block)
65
+ end
110
66
 
111
- def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
112
- def path_info=(s); @env["PATH_INFO"] = s.to_s end
67
+ # Set a request specific value for `name` to `v`
68
+ def set_header(name, v)
69
+ @env[name] = v
70
+ end
113
71
 
72
+ # Add a header that may have multiple values.
73
+ #
74
+ # Example:
75
+ # request.add_header 'Accept', 'image/png'
76
+ # request.add_header 'Accept', '*/*'
77
+ #
78
+ # assert_equal 'image/png,*/*', request.get_header('Accept')
79
+ #
80
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
81
+ def add_header key, v
82
+ if v.nil?
83
+ get_header key
84
+ elsif has_header? key
85
+ set_header key, "#{get_header key},#{v}"
86
+ else
87
+ set_header key, v
88
+ end
89
+ end
114
90
 
115
- # Checks the HTTP request method (or verb) to see if it was of type DELETE
116
- def delete?; request_method == "DELETE" end
91
+ # Delete a request specific value for `name`.
92
+ def delete_header(name)
93
+ @env.delete name
94
+ end
117
95
 
118
- # Checks the HTTP request method (or verb) to see if it was of type GET
119
- def get?; request_method == GET end
96
+ def initialize_copy(other)
97
+ @env = other.env.dup
98
+ end
99
+ end
120
100
 
121
- # Checks the HTTP request method (or verb) to see if it was of type HEAD
122
- def head?; request_method == HEAD end
101
+ module Helpers
102
+ # The set of form-data media-types. Requests that do not indicate
103
+ # one of the media types presents in this list will not be eligible
104
+ # for form-data / param parsing.
105
+ FORM_DATA_MEDIA_TYPES = [
106
+ 'application/x-www-form-urlencoded',
107
+ 'multipart/form-data'
108
+ ]
123
109
 
124
- # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
125
- def options?; request_method == "OPTIONS" end
110
+ # The set of media-types. Requests that do not indicate
111
+ # one of the media types presents in this list will not be eligible
112
+ # for param parsing like soap attachments or generic multiparts
113
+ PARSEABLE_DATA_MEDIA_TYPES = [
114
+ 'multipart/related',
115
+ 'multipart/mixed'
116
+ ]
126
117
 
127
- # Checks the HTTP request method (or verb) to see if it was of type LINK
128
- def link?; request_method == "LINK" end
118
+ # Default ports depending on scheme. Used to decide whether or not
119
+ # to include the port in a generated URI.
120
+ DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
121
+
122
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
123
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
124
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
125
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'.freeze
126
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
127
+
128
+ def body; get_header(RACK_INPUT) end
129
+ def script_name; get_header(SCRIPT_NAME).to_s end
130
+ def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
131
+
132
+ def path_info; get_header(PATH_INFO).to_s end
133
+ def path_info=(s); set_header(PATH_INFO, s.to_s) end
134
+
135
+ def request_method; get_header(REQUEST_METHOD) end
136
+ def query_string; get_header(QUERY_STRING).to_s end
137
+ def content_length; get_header('CONTENT_LENGTH') end
138
+ def logger; get_header(RACK_LOGGER) end
139
+ def user_agent; get_header('HTTP_USER_AGENT') end
140
+ def multithread?; get_header(RACK_MULTITHREAD) end
141
+
142
+ # the referer of the client
143
+ def referer; get_header('HTTP_REFERER') end
144
+ alias referrer referer
145
+
146
+ def session
147
+ fetch_header(RACK_SESSION) do |k|
148
+ set_header RACK_SESSION, default_session
149
+ end
150
+ end
129
151
 
130
- # Checks the HTTP request method (or verb) to see if it was of type PATCH
131
- def patch?; request_method == "PATCH" end
152
+ def session_options
153
+ fetch_header(RACK_SESSION_OPTIONS) do |k|
154
+ set_header RACK_SESSION_OPTIONS, {}
155
+ end
156
+ end
132
157
 
133
- # Checks the HTTP request method (or verb) to see if it was of type POST
134
- def post?; request_method == "POST" end
158
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
159
+ def delete?; request_method == DELETE end
135
160
 
136
- # Checks the HTTP request method (or verb) to see if it was of type PUT
137
- def put?; request_method == "PUT" end
161
+ # Checks the HTTP request method (or verb) to see if it was of type GET
162
+ def get?; request_method == GET end
138
163
 
139
- # Checks the HTTP request method (or verb) to see if it was of type TRACE
140
- def trace?; request_method == "TRACE" end
164
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
165
+ def head?; request_method == HEAD end
141
166
 
142
- # Checks the HTTP request method (or verb) to see if it was of type UNLINK
143
- def unlink?; request_method == "UNLINK" end
167
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
168
+ def options?; request_method == OPTIONS end
144
169
 
170
+ # Checks the HTTP request method (or verb) to see if it was of type LINK
171
+ def link?; request_method == LINK end
145
172
 
146
- # The set of form-data media-types. Requests that do not indicate
147
- # one of the media types presents in this list will not be eligible
148
- # for form-data / param parsing.
149
- FORM_DATA_MEDIA_TYPES = [
150
- 'application/x-www-form-urlencoded',
151
- 'multipart/form-data'
152
- ]
173
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
174
+ def patch?; request_method == PATCH end
153
175
 
154
- # The set of media-types. Requests that do not indicate
155
- # one of the media types presents in this list will not be eligible
156
- # for param parsing like soap attachments or generic multiparts
157
- PARSEABLE_DATA_MEDIA_TYPES = [
158
- 'multipart/related',
159
- 'multipart/mixed'
160
- ]
176
+ # Checks the HTTP request method (or verb) to see if it was of type POST
177
+ def post?; request_method == POST end
161
178
 
162
- # Default ports depending on scheme. Used to decide whether or not
163
- # to include the port in a generated URI.
164
- DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
179
+ # Checks the HTTP request method (or verb) to see if it was of type PUT
180
+ def put?; request_method == PUT end
165
181
 
166
- # Determine whether the request body contains form-data by checking
167
- # the request Content-Type for one of the media-types:
168
- # "application/x-www-form-urlencoded" or "multipart/form-data". The
169
- # list of form-data media types can be modified through the
170
- # +FORM_DATA_MEDIA_TYPES+ array.
171
- #
172
- # A request body is also assumed to contain form-data when no
173
- # Content-Type header is provided and the request_method is POST.
174
- def form_data?
175
- type = media_type
176
- meth = env["rack.methodoverride.original_method"] || env[REQUEST_METHOD]
177
- (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
178
- end
182
+ # Checks the HTTP request method (or verb) to see if it was of type TRACE
183
+ def trace?; request_method == TRACE end
179
184
 
180
- # Determine whether the request body contains data by checking
181
- # the request media_type against registered parse-data media-types
182
- def parseable_data?
183
- PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
184
- end
185
+ # Checks the HTTP request method (or verb) to see if it was of type UNLINK
186
+ def unlink?; request_method == UNLINK end
185
187
 
186
- # Returns the data received in the query string.
187
- def GET
188
- if @env["rack.request.query_string"] == query_string
189
- @env["rack.request.query_hash"]
190
- else
191
- p = parse_query({ :query => query_string, :separator => '&;' })
192
- @env["rack.request.query_string"] = query_string
193
- @env["rack.request.query_hash"] = p
188
+ def scheme
189
+ if get_header(HTTPS) == 'on'
190
+ 'https'
191
+ elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
192
+ 'https'
193
+ elsif forwarded_scheme
194
+ forwarded_scheme
195
+ else
196
+ get_header(RACK_URL_SCHEME)
197
+ end
198
+ end
199
+
200
+ def authority
201
+ get_header(SERVER_NAME) + ':' + get_header(SERVER_PORT)
194
202
  end
195
- end
196
203
 
197
- # Returns the data received in the request body.
198
- #
199
- # This method support both application/x-www-form-urlencoded and
200
- # multipart/form-data.
201
- def POST
202
- if @env["rack.input"].nil?
203
- raise "Missing rack.input"
204
- elsif @env["rack.request.form_input"].equal? @env["rack.input"]
205
- @env["rack.request.form_hash"]
206
- elsif form_data? || parseable_data?
207
- unless @env["rack.request.form_hash"] = parse_multipart(env)
208
- form_vars = @env["rack.input"].read
209
-
210
- # Fix for Safari Ajax postings that always append \0
211
- # form_vars.sub!(/\0\z/, '') # performance replacement:
212
- form_vars.slice!(-1) if form_vars[-1] == ?\0
213
-
214
- @env["rack.request.form_vars"] = form_vars
215
- @env["rack.request.form_hash"] = parse_query({ :query => form_vars, :separator => '&' })
216
-
217
- @env["rack.input"].rewind
204
+ def cookies
205
+ hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
206
+ set_header(k, {})
218
207
  end
219
- @env["rack.request.form_input"] = @env["rack.input"]
220
- @env["rack.request.form_hash"]
221
- else
222
- {}
208
+ string = get_header HTTP_COOKIE
209
+
210
+ return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
211
+ hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
212
+ set_header(RACK_REQUEST_COOKIE_STRING, string)
213
+ hash
223
214
  end
224
- end
225
215
 
226
- # The union of GET and POST data.
227
- #
228
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
229
- def params
230
- @params ||= self.GET.merge(self.POST)
231
- rescue EOFError
232
- self.GET.dup
233
- end
216
+ def content_type
217
+ content_type = get_header('CONTENT_TYPE')
218
+ content_type.nil? || content_type.empty? ? nil : content_type
219
+ end
234
220
 
235
- # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
236
- #
237
- # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
238
- #
239
- # env['rack.input'] is not touched.
240
- def update_param(k, v)
241
- found = false
242
- if self.GET.has_key?(k)
243
- found = true
244
- self.GET[k] = v
221
+ def xhr?
222
+ get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
245
223
  end
246
- if self.POST.has_key?(k)
247
- found = true
248
- self.POST[k] = v
224
+
225
+ def host_with_port
226
+ if forwarded = get_header(HTTP_X_FORWARDED_HOST)
227
+ forwarded.split(/,\s?/).last
228
+ else
229
+ get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
230
+ end
249
231
  end
250
- unless found
251
- self.GET[k] = v
232
+
233
+ def host
234
+ # Remove port number.
235
+ host_with_port.to_s.sub(/:\d+\z/, '')
252
236
  end
253
- @params = nil
254
- nil
255
- end
256
237
 
257
- # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
258
- #
259
- # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
260
- #
261
- # env['rack.input'] is not touched.
262
- def delete_param(k)
263
- v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
264
- @params = nil
265
- v
266
- end
238
+ def port
239
+ if port = host_with_port.split(/:/)[1]
240
+ port.to_i
241
+ elsif port = get_header(HTTP_X_FORWARDED_PORT)
242
+ port.to_i
243
+ elsif has_header?(HTTP_X_FORWARDED_HOST)
244
+ DEFAULT_PORTS[scheme]
245
+ elsif has_header?(HTTP_X_FORWARDED_PROTO)
246
+ DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
247
+ else
248
+ get_header(SERVER_PORT).to_i
249
+ end
250
+ end
267
251
 
268
- # shortcut for request.params[key]
269
- def [](key)
270
- params[key.to_s]
271
- end
252
+ def ssl?
253
+ scheme == 'https'
254
+ end
272
255
 
273
- # shortcut for request.params[key] = value
274
- #
275
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
276
- def []=(key, value)
277
- params[key.to_s] = value
278
- end
256
+ def ip
257
+ remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
258
+ remote_addrs = reject_trusted_ip_addresses(remote_addrs)
279
259
 
280
- # like Hash#values_at
281
- def values_at(*keys)
282
- keys.map{|key| params[key] }
283
- end
260
+ return remote_addrs.first if remote_addrs.any?
284
261
 
285
- # the referer of the client
286
- def referer
287
- @env['HTTP_REFERER']
288
- end
289
- alias referrer referer
262
+ forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
290
263
 
291
- def user_agent
292
- @env['HTTP_USER_AGENT']
293
- end
264
+ return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
265
+ end
294
266
 
295
- def cookies
296
- hash = @env["rack.request.cookie_hash"] ||= {}
297
- string = @env["HTTP_COOKIE"]
298
-
299
- return hash if string == @env["rack.request.cookie_string"]
300
- hash.clear
301
-
302
- # According to RFC 2109:
303
- # If multiple cookies satisfy the criteria above, they are ordered in
304
- # the Cookie header such that those with more specific Path attributes
305
- # precede those with less specific. Ordering with respect to other
306
- # attributes (e.g., Domain) is unspecified.
307
- cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
308
- cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
309
- @env["rack.request.cookie_string"] = string
310
- hash
311
- end
267
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
268
+ # without any media type parameters. e.g., when CONTENT_TYPE is
269
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
270
+ #
271
+ # For more information on the use of media types in HTTP, see:
272
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
273
+ def media_type
274
+ MediaType.type(content_type)
275
+ end
312
276
 
313
- def xhr?
314
- @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
315
- end
277
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
278
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
279
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
280
+ # this method responds with the following Hash:
281
+ # { 'charset' => 'utf-8' }
282
+ def media_type_params
283
+ MediaType.params(content_type)
284
+ end
316
285
 
317
- def base_url
318
- url = "#{scheme}://#{host}"
319
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
320
- url
321
- end
286
+ # The character set of the request body if a "charset" media type
287
+ # parameter was given, or nil if no "charset" was specified. Note
288
+ # that, per RFC2616, text/* media types that specify no explicit
289
+ # charset are to be considered ISO-8859-1.
290
+ def content_charset
291
+ media_type_params['charset']
292
+ end
322
293
 
323
- # Tries to return a remake of the original request URL as a string.
324
- def url
325
- base_url + fullpath
326
- end
294
+ # Determine whether the request body contains form-data by checking
295
+ # the request Content-Type for one of the media-types:
296
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
297
+ # list of form-data media types can be modified through the
298
+ # +FORM_DATA_MEDIA_TYPES+ array.
299
+ #
300
+ # A request body is also assumed to contain form-data when no
301
+ # Content-Type header is provided and the request_method is POST.
302
+ def form_data?
303
+ type = media_type
304
+ meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
305
+ (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
306
+ end
327
307
 
328
- def path
329
- script_name + path_info
330
- end
308
+ # Determine whether the request body contains data by checking
309
+ # the request media_type against registered parse-data media-types
310
+ def parseable_data?
311
+ PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
312
+ end
331
313
 
332
- def fullpath
333
- query_string.empty? ? path : "#{path}?#{query_string}"
334
- end
314
+ # Returns the data received in the query string.
315
+ def GET
316
+ if get_header(RACK_REQUEST_QUERY_STRING) == query_string
317
+ get_header(RACK_REQUEST_QUERY_HASH)
318
+ else
319
+ query_hash = parse_query(query_string, '&;')
320
+ set_header(RACK_REQUEST_QUERY_STRING, query_string)
321
+ set_header(RACK_REQUEST_QUERY_HASH, query_hash)
322
+ end
323
+ end
335
324
 
336
- def accept_encoding
337
- parse_http_accept_header(@env["HTTP_ACCEPT_ENCODING"])
338
- end
325
+ # Returns the data received in the request body.
326
+ #
327
+ # This method support both application/x-www-form-urlencoded and
328
+ # multipart/form-data.
329
+ def POST
330
+ if get_header(RACK_INPUT).nil?
331
+ raise "Missing rack.input"
332
+ elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
333
+ get_header(RACK_REQUEST_FORM_HASH)
334
+ elsif form_data? || parseable_data?
335
+ unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
336
+ form_vars = get_header(RACK_INPUT).read
337
+
338
+ # Fix for Safari Ajax postings that always append \0
339
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
340
+ form_vars.slice!(-1) if form_vars[-1] == ?\0
341
+
342
+ set_header RACK_REQUEST_FORM_VARS, form_vars
343
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
344
+
345
+ get_header(RACK_INPUT).rewind
346
+ end
347
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
348
+ get_header RACK_REQUEST_FORM_HASH
349
+ else
350
+ {}
351
+ end
352
+ end
339
353
 
340
- def accept_language
341
- parse_http_accept_header(@env["HTTP_ACCEPT_LANGUAGE"])
342
- end
354
+ # The union of GET and POST data.
355
+ #
356
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
357
+ def params
358
+ self.GET.merge(self.POST)
359
+ rescue EOFError
360
+ self.GET.dup
361
+ end
343
362
 
344
- def trusted_proxy?(ip)
345
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
346
- end
363
+ # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
364
+ #
365
+ # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
366
+ #
367
+ # <tt>env['rack.input']</tt> is not touched.
368
+ def update_param(k, v)
369
+ found = false
370
+ if self.GET.has_key?(k)
371
+ found = true
372
+ self.GET[k] = v
373
+ end
374
+ if self.POST.has_key?(k)
375
+ found = true
376
+ self.POST[k] = v
377
+ end
378
+ unless found
379
+ self.GET[k] = v
380
+ end
381
+ end
347
382
 
348
- def ip
349
- remote_addrs = split_ip_addresses(@env['REMOTE_ADDR'])
350
- remote_addrs = reject_trusted_ip_addresses(remote_addrs)
383
+ # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
384
+ #
385
+ # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
386
+ #
387
+ # <tt>env['rack.input']</tt> is not touched.
388
+ def delete_param(k)
389
+ [ self.POST.delete(k), self.GET.delete(k) ].compact.first
390
+ end
351
391
 
352
- return remote_addrs.first if remote_addrs.any?
392
+ def base_url
393
+ url = "#{scheme}://#{host}"
394
+ url << ":#{port}" if port != DEFAULT_PORTS[scheme]
395
+ url
396
+ end
353
397
 
354
- forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR'])
398
+ # Tries to return a remake of the original request URL as a string.
399
+ def url
400
+ base_url + fullpath
401
+ end
355
402
 
356
- return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"]
357
- end
403
+ def path
404
+ script_name + path_info
405
+ end
358
406
 
359
- protected
360
- def split_ip_addresses(ip_addresses)
361
- ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
407
+ def fullpath
408
+ query_string.empty? ? path : "#{path}?#{query_string}"
362
409
  end
363
410
 
364
- def reject_trusted_ip_addresses(ip_addresses)
365
- ip_addresses.reject { |ip| trusted_proxy?(ip) }
411
+ def accept_encoding
412
+ parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
413
+ end
414
+
415
+ def accept_language
416
+ parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
366
417
  end
367
418
 
368
- def parse_query(qs)
369
- d = '&'
370
- qs, d = qs[:query], qs[:separator] if Hash === qs
371
- Utils.parse_nested_query(qs, d)
419
+ def trusted_proxy?(ip)
420
+ ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
372
421
  end
373
422
 
374
- def parse_multipart(env)
375
- Rack::Multipart.parse_multipart(env)
423
+ # shortcut for <tt>request.params[key]</tt>
424
+ def [](key)
425
+ if $VERBOSE
426
+ warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
427
+ end
428
+
429
+ params[key.to_s]
376
430
  end
377
431
 
432
+ # shortcut for <tt>request.params[key] = value</tt>
433
+ #
434
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
435
+ def []=(key, value)
436
+ if $VERBOSE
437
+ warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
438
+ end
439
+
440
+ params[key.to_s] = value
441
+ end
442
+
443
+ # like Hash#values_at
444
+ def values_at(*keys)
445
+ keys.map { |key| params[key] }
446
+ end
447
+
448
+ private
449
+
450
+ def default_session; {}; end
451
+
378
452
  def parse_http_accept_header(header)
379
453
  header.to_s.split(/\s*,\s*/).map do |part|
380
454
  attribute, parameters = part.split(/\s*;\s*/, 2)
@@ -386,26 +460,41 @@ module Rack
386
460
  end
387
461
  end
388
462
 
389
- private
390
- def strip_doublequotes(s)
391
- if s[0] == ?" && s[-1] == ?"
392
- s[1..-2]
393
- else
394
- s
463
+ def query_parser
464
+ Utils.default_query_parser
395
465
  end
396
- end
397
466
 
398
- def forwarded_scheme
399
- scheme_headers = [
400
- @env['HTTP_X_FORWARDED_SCHEME'],
401
- @env['HTTP_X_FORWARDED_PROTO'].to_s.split(',')[0]
402
- ]
467
+ def parse_query(qs, d='&')
468
+ query_parser.parse_nested_query(qs, d)
469
+ end
470
+
471
+ def parse_multipart
472
+ Rack::Multipart.extract_multipart(self, query_parser)
473
+ end
403
474
 
404
- scheme_headers.each do |header|
405
- return header if SCHEME_WHITELIST.include?(header)
475
+ def split_ip_addresses(ip_addresses)
476
+ ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
406
477
  end
407
478
 
408
- nil
479
+ def reject_trusted_ip_addresses(ip_addresses)
480
+ ip_addresses.reject { |ip| trusted_proxy?(ip) }
481
+ end
482
+
483
+ def forwarded_scheme
484
+ scheme_headers = [
485
+ get_header(HTTP_X_FORWARDED_SCHEME),
486
+ get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
487
+ ]
488
+
489
+ scheme_headers.each do |header|
490
+ return header if SCHEME_WHITELIST.include?(header)
491
+ end
492
+
493
+ nil
494
+ end
409
495
  end
496
+
497
+ include Env
498
+ include Helpers
410
499
  end
411
500
  end