rack 1.6.13 → 2.0.0.alpha

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