merb-core 0.9.9 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +31 -3
- data/Rakefile +2 -2
- data/lib/merb-core.rb +18 -2
- data/lib/merb-core/bootloader.rb +80 -46
- data/lib/merb-core/config.rb +2 -2
- data/lib/merb-core/constants.rb +35 -5
- data/lib/merb-core/controller/abstract_controller.rb +4 -4
- data/lib/merb-core/controller/exceptions.rb +2 -2
- data/lib/merb-core/controller/merb_controller.rb +1 -1
- data/lib/merb-core/dispatch/dispatcher.rb +1 -1
- data/lib/merb-core/dispatch/request.rb +191 -184
- data/lib/merb-core/dispatch/router.rb +62 -27
- data/lib/merb-core/dispatch/router/resources.rb +10 -7
- data/lib/merb-core/dispatch/router/route.rb +2 -0
- data/lib/merb-core/dispatch/session.rb +2 -2
- data/lib/merb-core/rack/adapter/abstract.rb +1 -1
- data/lib/merb-core/tasks/gem_management.rb +15 -3
- data/lib/merb-core/test/helpers/request_helper.rb +4 -0
- data/lib/merb-core/test/tasks/spectasks.rb +10 -0
- data/lib/merb-core/test/test_ext/rspec.rb +0 -5
- data/lib/merb-core/version.rb +1 -1
- metadata +4 -4
data/lib/merb-core/constants.rb
CHANGED
@@ -11,15 +11,16 @@
|
|
11
11
|
# some cases. Eventually Rubinius and maybe MRI 2.0 gonna
|
12
12
|
# improve this situation but at the moment, all commonly used
|
13
13
|
# strings, regexp and numbers used as constants so no extra
|
14
|
-
# objects created and VM just
|
14
|
+
# objects created and VM just operates pointers.
|
15
15
|
module Merb
|
16
16
|
module Const
|
17
|
-
|
17
|
+
|
18
18
|
DEFAULT_SEND_FILE_OPTIONS = {
|
19
19
|
:type => 'application/octet-stream'.freeze,
|
20
20
|
:disposition => 'attachment'.freeze
|
21
21
|
}.freeze
|
22
|
-
|
22
|
+
|
23
|
+
RACK_INPUT = 'rack.input'.freeze
|
23
24
|
SET_COOKIE = " %s=%s; path=/; expires=%s".freeze
|
24
25
|
COOKIE_EXPIRATION_FORMAT = "%a, %d-%b-%Y %H:%M:%S GMT".freeze
|
25
26
|
COOKIE_SPLIT = /[;,] */n.freeze
|
@@ -34,24 +35,48 @@ module Merb
|
|
34
35
|
JSON_MIME_TYPE_REGEXP = %r{^application/json|^text/x-json}.freeze
|
35
36
|
XML_MIME_TYPE_REGEXP = %r{^application/xml|^text/xml}.freeze
|
36
37
|
FORM_URL_ENCODED_REGEXP = %r{^application/x-www-form-urlencoded}.freeze
|
38
|
+
LOCAL_IP_REGEXP = /^unknown$|^(127|10|172\.16|192\.168)\./i.freeze
|
39
|
+
XML_HTTP_REQUEST_REGEXP = /XMLHttpRequest/i.freeze
|
37
40
|
UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
38
41
|
CONTENT_TYPE = "Content-Type".freeze
|
39
42
|
DATE = 'Date'.freeze
|
43
|
+
UPCASE_HTTPS = 'HTTPS'.freeze
|
44
|
+
HTTPS = 'https'.freeze
|
45
|
+
HTTP = 'http'.freeze
|
40
46
|
ETAG = 'ETag'.freeze
|
41
47
|
LAST_MODIFIED = "Last-Modified".freeze
|
42
|
-
SLASH = "/".freeze
|
43
48
|
GET = "GET".freeze
|
44
49
|
POST = "POST".freeze
|
45
50
|
HEAD = "HEAD".freeze
|
46
51
|
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
52
|
+
HTTP_CLIENT_IP = "HTTP_CLIENT_IP".freeze
|
53
|
+
HTTP_X_REQUESTED_WITH = "HTTP_X_REQUESTED_WITH".freeze
|
47
54
|
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
|
55
|
+
HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
|
56
|
+
HTTP_X_FORWARDED_HOST = "HTTP_X_FORWARDED_HOST".freeze
|
48
57
|
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze
|
49
58
|
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze
|
50
59
|
HTTP_CONTENT_TYPE = "HTTP_CONTENT_TYPE".freeze
|
51
60
|
HTTP_CONTENT_LENGTH = "HTTP_CONTENT_LENGTH".freeze
|
61
|
+
HTTP_REFERER = "HTTP_REFERER".freeze
|
62
|
+
HTTP_USER_AGENT = "HTTP_USER_AGENT".freeze
|
63
|
+
HTTP_HOST = "HTTP_HOST".freeze
|
64
|
+
HTTP_CONNECTION = "HTTP_CONNECTION".freeze
|
65
|
+
HTTP_KEEP_ALIVE = "HTTP_KEEP_ALIVE".freeze
|
66
|
+
HTTP_ACCEPT = "HTTP_ACCEPT".freeze
|
67
|
+
HTTP_ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING".freeze
|
68
|
+
HTTP_ACCEPT_LANGUAGE = "HTTP_ACCEPT_LANGUAGE".freeze
|
69
|
+
HTTP_ACCEPT_CHARSET = "HTTP_ACCEPT_CHARSET".freeze
|
70
|
+
HTTP_CACHE_CONTROL = "HTTP_CACHE_CONTROL".freeze
|
52
71
|
UPLOAD_ID = "upload_id".freeze
|
53
72
|
PATH_INFO = "PATH_INFO".freeze
|
73
|
+
HTTP_VERSION = "HTTP_VERSION".freeze
|
74
|
+
GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
|
54
75
|
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
76
|
+
SERVER_NAME = "SERVER_NAME".freeze
|
77
|
+
SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
|
78
|
+
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
79
|
+
SERVER_PORT = "SERVER_PORT".freeze
|
55
80
|
REQUEST_URI = "REQUEST_URI".freeze
|
56
81
|
REQUEST_PATH = "REQUEST_PATH".freeze
|
57
82
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
@@ -59,9 +84,14 @@ module Merb
|
|
59
84
|
BREAK_TAG = "<br/>".freeze
|
60
85
|
EMPTY_STRING = "".freeze
|
61
86
|
NEWLINE = "\n".freeze
|
87
|
+
SLASH = "/".freeze
|
88
|
+
DOT = ".".freeze
|
89
|
+
QUESTION_MARK = "?".freeze
|
62
90
|
DOUBLE_NEWLINE = "\n\n".freeze
|
63
91
|
LOCATION = "Location".freeze
|
64
92
|
TEXT_SLASH_HTML = "text/html".freeze
|
65
|
-
|
93
|
+
|
94
|
+
WIN_PLATFORM_REGEXP = /(:?mswin|mingw)/.freeze
|
95
|
+
JAVA_PLATFORM_REGEXP = /java/.freeze
|
66
96
|
end
|
67
97
|
end
|
@@ -271,9 +271,9 @@ class Merb::AbstractController
|
|
271
271
|
#
|
272
272
|
# @api plugin
|
273
273
|
def _dispatch(action)
|
274
|
-
self._before_dispatch_callbacks.each { |cb| cb.call(self) }
|
275
274
|
self.action_name = action
|
276
|
-
|
275
|
+
self._before_dispatch_callbacks.each { |cb| cb.call(self) }
|
276
|
+
|
277
277
|
caught = catch(:halt) do
|
278
278
|
start = Time.now
|
279
279
|
result = _call_filters(_before_filters)
|
@@ -544,7 +544,7 @@ class Merb::AbstractController
|
|
544
544
|
# of protocol and host options.
|
545
545
|
#
|
546
546
|
# @api public
|
547
|
-
def absolute_url(
|
547
|
+
def absolute_url(*args)
|
548
548
|
# FIXME: arrgh, why request.protocol returns http://?
|
549
549
|
# :// is not part of protocol name
|
550
550
|
options = extract_options_from_args!(args) || {}
|
@@ -556,7 +556,7 @@ class Merb::AbstractController
|
|
556
556
|
|
557
557
|
args << options
|
558
558
|
|
559
|
-
protocol + "://" + host + url(
|
559
|
+
protocol + "://" + host + url(*args)
|
560
560
|
end
|
561
561
|
|
562
562
|
# Generates a URL for a single or nested resource.
|
@@ -74,7 +74,7 @@ module Merb
|
|
74
74
|
# This would halt execution of your action and re-route it over to your
|
75
75
|
# Exceptions controller which might look something like:
|
76
76
|
#
|
77
|
-
# class Exceptions <
|
77
|
+
# class Exceptions < Merb::Controller
|
78
78
|
|
79
79
|
# def not_found
|
80
80
|
# render :layout => :none
|
@@ -132,7 +132,7 @@ module Merb
|
|
132
132
|
#
|
133
133
|
# Add the required action to our Exceptions controller
|
134
134
|
#
|
135
|
-
# class Exceptions <
|
135
|
+
# class Exceptions < Merb::Controller
|
136
136
|
|
137
137
|
# def admin_access_required
|
138
138
|
# render
|
@@ -335,7 +335,7 @@ class Merb::Controller < Merb::AbstractController
|
|
335
335
|
options[:protocol] ||= request.protocol
|
336
336
|
options[:host] ||= request.host
|
337
337
|
args << options
|
338
|
-
super(
|
338
|
+
super(*args)
|
339
339
|
end
|
340
340
|
|
341
341
|
# The results of the controller's render, to be returned to Rack.
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'tempfile'
|
2
2
|
|
3
3
|
module Merb
|
4
|
-
|
4
|
+
|
5
5
|
class Request
|
6
6
|
# def env def exceptions def route_params
|
7
7
|
attr_accessor :env, :exceptions, :route
|
8
8
|
attr_reader :route_params
|
9
|
-
|
9
|
+
|
10
10
|
# by setting these to false, auto-parsing is disabled; this way you can
|
11
11
|
# do your own parsing instead
|
12
12
|
cattr_accessor :parse_multipart_params, :parse_json_params,
|
@@ -14,7 +14,7 @@ module Merb
|
|
14
14
|
self.parse_multipart_params = true
|
15
15
|
self.parse_json_params = true
|
16
16
|
self.parse_xml_params = true
|
17
|
-
|
17
|
+
|
18
18
|
# Flash, and some older browsers can't use arbitrary
|
19
19
|
# request methods -- i.e., are limited to GET/POST.
|
20
20
|
# These user-agents can make POST requests in combination
|
@@ -23,39 +23,39 @@ module Merb
|
|
23
23
|
# in the params, or an X-HTTP-Method-Override header
|
24
24
|
cattr_accessor :http_method_overrides
|
25
25
|
self.http_method_overrides = []
|
26
|
-
|
26
|
+
|
27
27
|
# Initialize the request object.
|
28
|
-
#
|
28
|
+
#
|
29
29
|
# ==== Parameters
|
30
|
-
# http_request<~params:~[], ~body:IO>::
|
30
|
+
# http_request<~params:~[], ~body:IO>::
|
31
31
|
# An object like an HTTP Request.
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# @api private
|
34
34
|
def initialize(rack_env)
|
35
35
|
@env = rack_env
|
36
|
-
@body = rack_env[
|
36
|
+
@body = rack_env[Merb::Const::RACK_INPUT]
|
37
37
|
@route_params = {}
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
# Returns the controller object for initialization and dispatching the
|
41
41
|
# request.
|
42
|
-
#
|
42
|
+
#
|
43
43
|
# ==== Returns
|
44
44
|
# Class:: The controller class matching the routed request,
|
45
45
|
# e.g. Posts.
|
46
|
-
#
|
46
|
+
#
|
47
47
|
# @api private
|
48
48
|
def controller
|
49
49
|
unless params[:controller]
|
50
|
-
raise ControllerExceptions::NotFound,
|
50
|
+
raise ControllerExceptions::NotFound,
|
51
51
|
"Route matched, but route did not specify a controller.\n" +
|
52
52
|
"Did you forgot to add :controller => \"people\" or :controller " +
|
53
|
-
"segment to route definition?\nHere is what's specified:\n" +
|
53
|
+
"segment to route definition?\nHere is what's specified:\n" +
|
54
54
|
route.inspect
|
55
55
|
end
|
56
|
-
path = [params[:namespace], params[:controller]].compact.join(
|
56
|
+
path = [params[:namespace], params[:controller]].compact.join(Merb::Const::SLASH)
|
57
57
|
controller = path.snake_case.to_const_string
|
58
|
-
|
58
|
+
|
59
59
|
begin
|
60
60
|
Object.full_const_get(controller)
|
61
61
|
rescue NameError => e
|
@@ -64,7 +64,7 @@ module Merb
|
|
64
64
|
raise ControllerExceptions::NotFound, msg
|
65
65
|
end
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
METHODS = %w{get post put delete head options}
|
69
69
|
|
70
70
|
# ==== Returns
|
@@ -75,11 +75,11 @@ module Merb
|
|
75
75
|
# http_method_overrides will be checked for the masquerading method.
|
76
76
|
# The block will get the controller yielded to it. The first matching workaround wins.
|
77
77
|
# To disable this behavior, set http_method_overrides = []
|
78
|
-
#
|
78
|
+
#
|
79
79
|
# @api public
|
80
80
|
def method
|
81
81
|
@method ||= begin
|
82
|
-
request_method = @env[
|
82
|
+
request_method = @env[Merb::Const::REQUEST_METHOD].downcase.to_sym
|
83
83
|
case request_method
|
84
84
|
when :get, :head, :put, :delete, :options
|
85
85
|
request_method
|
@@ -91,33 +91,33 @@ module Merb
|
|
91
91
|
m.downcase! if m
|
92
92
|
METHODS.include?(m) ? m.to_sym : :post
|
93
93
|
else
|
94
|
-
raise "Unknown REQUEST_METHOD: #{@env[
|
94
|
+
raise "Unknown REQUEST_METHOD: #{@env[Merb::Const::REQUEST_METHOD]}"
|
95
95
|
end
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
# create predicate methods for querying the REQUEST_METHOD
|
100
100
|
# get? post? head? put? etc
|
101
101
|
METHODS.each do |m|
|
102
102
|
class_eval "def #{m}?() method == :#{m} end"
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
# ==== Notes
|
106
106
|
# Find route using requested URI and merges route
|
107
107
|
# parameters (:action, :controller and named segments)
|
108
108
|
# into request params hash.
|
109
|
-
#
|
109
|
+
#
|
110
110
|
# @api private
|
111
111
|
def find_route!
|
112
112
|
@route, @route_params = Merb::Router.route_for(self)
|
113
113
|
params.merge! @route_params if @route_params.is_a?(Hash)
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
# ==== Notes
|
117
117
|
# Processes the return value of a deferred router block
|
118
118
|
# and returns the current route params for the current
|
119
119
|
# request evaluation
|
120
|
-
#
|
120
|
+
#
|
121
121
|
# @api private
|
122
122
|
def _process_block_return(retval)
|
123
123
|
# If the return value is an array, then it is a redirect
|
@@ -127,67 +127,67 @@ module Merb
|
|
127
127
|
matched! if retval.is_a?(Array)
|
128
128
|
retval
|
129
129
|
end
|
130
|
-
|
130
|
+
|
131
131
|
# Sets the request as matched. This will abort evaluating any
|
132
132
|
# further deferred procs.
|
133
|
-
#
|
133
|
+
#
|
134
134
|
# @api private
|
135
135
|
def matched!
|
136
136
|
@matched = true
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
# Checks whether or not the request has been matched to a route.
|
140
|
-
#
|
140
|
+
#
|
141
141
|
# @api private
|
142
142
|
def matched?
|
143
143
|
@matched
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
146
|
# ==== Returns
|
147
147
|
# (Array, Hash):: the route params for the matched route.
|
148
|
-
#
|
148
|
+
#
|
149
149
|
# ==== Notes
|
150
150
|
# If the response is an Array then it is considered a direct Rack response
|
151
151
|
# to be sent back as a response. Otherwise, the route_params is a Hash with
|
152
152
|
# routing data (controller, action, et al).
|
153
|
-
#
|
153
|
+
#
|
154
154
|
# @api private
|
155
155
|
def rack_response
|
156
156
|
@route_params
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
# If @route_params is an Array, then it will be the rack response.
|
160
160
|
# In this case, the request is considered handled.
|
161
|
-
#
|
161
|
+
#
|
162
162
|
# ==== Returns
|
163
163
|
# Boolean:: true if @route_params is an Array, false otherwise.
|
164
|
-
#
|
164
|
+
#
|
165
165
|
# @api private
|
166
166
|
def handled?
|
167
167
|
@route_params.is_a?(Array)
|
168
168
|
end
|
169
|
-
|
169
|
+
|
170
170
|
# == Params
|
171
|
-
#
|
171
|
+
#
|
172
172
|
# Handles processing params from raw data and merging them together to get
|
173
173
|
# the final request params.
|
174
|
-
|
174
|
+
|
175
175
|
private
|
176
|
-
|
176
|
+
|
177
177
|
# ==== Returns
|
178
178
|
# Hash:: Parameters passed from the URL like ?blah=hello.
|
179
|
-
#
|
179
|
+
#
|
180
180
|
# @api private
|
181
181
|
def query_params
|
182
182
|
@query_params ||= self.class.query_parse(query_string || '')
|
183
183
|
end
|
184
|
-
|
184
|
+
|
185
185
|
# Parameters passed in the body of the request. Ajax calls from
|
186
186
|
# prototype.js and other libraries pass content this way.
|
187
187
|
#
|
188
188
|
# ==== Returns
|
189
189
|
# Hash:: The parameters passed in the body.
|
190
|
-
#
|
190
|
+
#
|
191
191
|
# @api private
|
192
192
|
def body_params
|
193
193
|
@body_params ||= begin
|
@@ -196,12 +196,12 @@ module Merb
|
|
196
196
|
end
|
197
197
|
end
|
198
198
|
end
|
199
|
-
|
199
|
+
|
200
200
|
# ==== Returns
|
201
201
|
# Mash::
|
202
202
|
# The parameters gathered from the query string and the request body,
|
203
203
|
# with parameters in the body taking precedence.
|
204
|
-
#
|
204
|
+
#
|
205
205
|
# @api private
|
206
206
|
def body_and_query_params
|
207
207
|
# ^-- FIXME a better name for this method
|
@@ -211,17 +211,17 @@ module Merb
|
|
211
211
|
h.to_mash
|
212
212
|
end
|
213
213
|
end
|
214
|
-
|
214
|
+
|
215
215
|
# ==== Raises
|
216
216
|
# ControllerExceptions::MultiPartParseError::
|
217
217
|
# Unable to parse the multipart form data.
|
218
218
|
#
|
219
219
|
# ==== Returns
|
220
220
|
# Hash:: The parsed multipart parameters.
|
221
|
-
#
|
221
|
+
#
|
222
222
|
# @api private
|
223
223
|
def multipart_params
|
224
|
-
@multipart_params ||=
|
224
|
+
@multipart_params ||=
|
225
225
|
begin
|
226
226
|
# if the content-type is multipart
|
227
227
|
# parse the multipart. Otherwise return {}
|
@@ -229,13 +229,13 @@ module Merb
|
|
229
229
|
self.class.parse_multipart(@body, $1, content_length)
|
230
230
|
else
|
231
231
|
{}
|
232
|
-
end
|
232
|
+
end
|
233
233
|
rescue ControllerExceptions::MultiPartParseError => e
|
234
234
|
@multipart_params = {}
|
235
235
|
raise e
|
236
236
|
end
|
237
237
|
end
|
238
|
-
|
238
|
+
|
239
239
|
# ==== Returns
|
240
240
|
# Hash:: Parameters from body if this is a JSON request.
|
241
241
|
#
|
@@ -244,20 +244,25 @@ module Merb
|
|
244
244
|
# parameters hash. If it parses to anything else (such as an Array, or
|
245
245
|
# if it inflates to an Object) it will be accessible via the inflated_object
|
246
246
|
# parameter.
|
247
|
-
#
|
247
|
+
#
|
248
248
|
# @api private
|
249
249
|
def json_params
|
250
250
|
@json_params ||= begin
|
251
251
|
if Merb::Const::JSON_MIME_TYPE_REGEXP.match(content_type)
|
252
|
-
|
253
|
-
|
252
|
+
begin
|
253
|
+
jobj = JSON.parse(raw_post)
|
254
|
+
jobj = jobj.to_mash if jobj.is_a?(Hash)
|
255
|
+
rescue JSON::ParserError
|
256
|
+
jobj = Mash.new
|
257
|
+
end
|
258
|
+
jobj.is_a?(Hash) ? jobj : { :inflated_object => jobj }
|
254
259
|
end
|
255
260
|
end
|
256
261
|
end
|
257
|
-
|
262
|
+
|
258
263
|
# ==== Returns
|
259
264
|
# Hash:: Parameters from body if this is an XML request.
|
260
|
-
#
|
265
|
+
#
|
261
266
|
# @api private
|
262
267
|
def xml_params
|
263
268
|
@xml_params ||= begin
|
@@ -266,30 +271,30 @@ module Merb
|
|
266
271
|
end
|
267
272
|
end
|
268
273
|
end
|
269
|
-
|
274
|
+
|
270
275
|
public
|
271
|
-
|
276
|
+
|
272
277
|
# ==== Returns
|
273
278
|
# Mash:: All request parameters.
|
274
279
|
#
|
275
280
|
# ==== Notes
|
276
281
|
# The order of precedence for the params is XML, JSON, multipart, body and
|
277
282
|
# request string.
|
278
|
-
#
|
283
|
+
#
|
279
284
|
# @api public
|
280
285
|
def params
|
281
286
|
@params ||= begin
|
282
|
-
h = body_and_query_params.merge(route_params)
|
287
|
+
h = body_and_query_params.merge(route_params)
|
283
288
|
h.merge!(multipart_params) if self.class.parse_multipart_params && multipart_params
|
284
289
|
h.merge!(json_params) if self.class.parse_json_params && json_params
|
285
290
|
h.merge!(xml_params) if self.class.parse_xml_params && xml_params
|
286
291
|
h
|
287
292
|
end
|
288
293
|
end
|
289
|
-
|
294
|
+
|
290
295
|
# ==== Returns
|
291
296
|
# String:: Returns the redirect message Base64 unencoded.
|
292
|
-
#
|
297
|
+
#
|
293
298
|
# @api public
|
294
299
|
def message
|
295
300
|
return {} unless params[:_message]
|
@@ -299,258 +304,260 @@ module Merb
|
|
299
304
|
{}
|
300
305
|
end
|
301
306
|
end
|
302
|
-
|
307
|
+
|
303
308
|
# ==== Notes
|
304
309
|
# Resets the params to a nil value.
|
305
|
-
#
|
310
|
+
#
|
306
311
|
# @api private
|
307
312
|
def reset_params!
|
308
313
|
@params = nil
|
309
314
|
end
|
310
|
-
|
315
|
+
|
311
316
|
# ==== Returns
|
312
317
|
# String:: The raw post.
|
313
|
-
#
|
318
|
+
#
|
314
319
|
# @api private
|
315
320
|
def raw_post
|
316
321
|
@body.rewind if @body.respond_to?(:rewind)
|
317
322
|
@raw_post ||= @body.read
|
318
323
|
end
|
319
|
-
|
324
|
+
|
320
325
|
# ==== Returns
|
321
326
|
# Boolean:: If the request is an XML HTTP request.
|
322
|
-
#
|
327
|
+
#
|
323
328
|
# @api public
|
324
329
|
def xml_http_request?
|
325
|
-
not
|
330
|
+
not Merb::Const::XML_HTTP_REQUEST_REGEXP.match(@env[Merb::Const::HTTP_X_REQUESTED_WITH]).nil?
|
326
331
|
end
|
327
332
|
alias xhr? :xml_http_request?
|
328
333
|
alias ajax? :xml_http_request?
|
329
|
-
|
334
|
+
|
330
335
|
# ==== Returns
|
331
336
|
# String:: The remote IP address.
|
332
|
-
#
|
337
|
+
#
|
333
338
|
# @api public
|
334
339
|
def remote_ip
|
335
|
-
return @env[
|
336
|
-
|
340
|
+
return @env[Merb::Const::HTTP_CLIENT_IP] if @env.include?(Merb::Const::HTTP_CLIENT_IP)
|
341
|
+
|
337
342
|
if @env.include?(Merb::Const::HTTP_X_FORWARDED_FOR) then
|
338
343
|
remote_ips = @env[Merb::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
|
339
|
-
ip =~
|
344
|
+
ip =~ Merb::Const::LOCAL_IP_REGEXP
|
340
345
|
end
|
341
|
-
|
346
|
+
|
342
347
|
return remote_ips.first.strip unless remote_ips.empty?
|
343
348
|
end
|
344
|
-
|
349
|
+
|
345
350
|
return @env[Merb::Const::REMOTE_ADDR]
|
346
351
|
end
|
347
|
-
|
352
|
+
|
348
353
|
# ==== Returns
|
349
354
|
# String::
|
350
355
|
# The protocol, i.e. either "https" or "http" depending on the
|
351
356
|
# HTTPS header.
|
352
|
-
#
|
357
|
+
#
|
353
358
|
# @api public
|
354
359
|
def protocol
|
355
|
-
ssl? ?
|
360
|
+
ssl? ? Merb::Const::HTTPS : Merb::Const::HTTP
|
356
361
|
end
|
357
|
-
|
362
|
+
|
358
363
|
# ==== Returns
|
359
364
|
# Boolean::: True if the request is an SSL request.
|
360
|
-
#
|
365
|
+
#
|
361
366
|
# @api public
|
362
367
|
def ssl?
|
363
|
-
@env[
|
368
|
+
@env[Merb::Const::UPCASE_HTTPS] == 'on' || @env[Merb::Const::HTTP_X_FORWARDED_PROTO] == Merb::Const::HTTPS
|
364
369
|
end
|
365
|
-
|
370
|
+
|
366
371
|
# ==== Returns
|
367
372
|
# String:: The HTTP referer.
|
368
|
-
#
|
373
|
+
#
|
369
374
|
# @api public
|
370
375
|
def referer
|
371
|
-
@env[
|
376
|
+
@env[Merb::Const::HTTP_REFERER]
|
372
377
|
end
|
373
|
-
|
378
|
+
|
374
379
|
# ==== Returns
|
375
380
|
# String:: The full URI, including protocol and host
|
376
|
-
#
|
381
|
+
#
|
377
382
|
# @api public
|
378
383
|
def full_uri
|
379
384
|
protocol + "://" + host + uri
|
380
385
|
end
|
381
|
-
|
386
|
+
|
382
387
|
# ==== Returns
|
383
388
|
# String:: The request URI.
|
384
|
-
#
|
389
|
+
#
|
385
390
|
# @api public
|
386
391
|
def uri
|
387
|
-
@env[
|
392
|
+
@env[Merb::Const::REQUEST_PATH] || @env[Merb::Const::REQUEST_URI] || path_info
|
388
393
|
end
|
389
|
-
|
394
|
+
|
390
395
|
# ==== Returns
|
391
396
|
# String:: The HTTP user agent.
|
392
|
-
#
|
397
|
+
#
|
393
398
|
# @api public
|
394
399
|
def user_agent
|
395
|
-
@env[
|
400
|
+
@env[Merb::Const::HTTP_USER_AGENT]
|
396
401
|
end
|
397
|
-
|
402
|
+
|
398
403
|
# ==== Returns
|
399
404
|
# String:: The server name.
|
400
|
-
#
|
405
|
+
#
|
401
406
|
# @api public
|
402
407
|
def server_name
|
403
|
-
@env[
|
408
|
+
@env[Merb::Const::SERVER_NAME]
|
404
409
|
end
|
405
|
-
|
410
|
+
|
406
411
|
# ==== Returns
|
407
412
|
# String:: The accepted encodings.
|
408
|
-
#
|
413
|
+
#
|
409
414
|
# @api private
|
410
415
|
def accept_encoding
|
411
|
-
@env[
|
416
|
+
@env[Merb::Const::HTTP_ACCEPT_ENCODING]
|
412
417
|
end
|
413
|
-
|
418
|
+
|
414
419
|
# ==== Returns
|
415
420
|
# String:: The script name.
|
416
|
-
#
|
421
|
+
#
|
417
422
|
# @api public
|
418
423
|
def script_name
|
419
|
-
@env[
|
424
|
+
@env[Merb::Const::SCRIPT_NAME]
|
420
425
|
end
|
421
|
-
|
426
|
+
|
422
427
|
# ==== Returns
|
423
428
|
# String:: HTTP cache control.
|
424
|
-
#
|
429
|
+
#
|
425
430
|
# @api public
|
426
431
|
def cache_control
|
427
|
-
@env[
|
432
|
+
@env[Merb::Const::HTTP_CACHE_CONTROL]
|
428
433
|
end
|
429
|
-
|
434
|
+
|
430
435
|
# ==== Returns
|
431
436
|
# String:: The accepted language.
|
432
|
-
#
|
437
|
+
#
|
433
438
|
# @api public
|
434
439
|
def accept_language
|
435
|
-
@env[
|
440
|
+
@env[Merb::Const::HTTP_ACCEPT_LANGUAGE]
|
436
441
|
end
|
437
|
-
|
442
|
+
|
438
443
|
# ==== Returns
|
439
444
|
# String:: The server software.
|
440
|
-
#
|
445
|
+
#
|
441
446
|
# @api public
|
442
447
|
def server_software
|
443
|
-
@env[
|
448
|
+
@env[Merb::Const::SERVER_SOFTWARE]
|
444
449
|
end
|
445
|
-
|
450
|
+
|
446
451
|
# ==== Returns
|
447
452
|
# String:: Value of HTTP_KEEP_ALIVE.
|
448
|
-
#
|
453
|
+
#
|
449
454
|
# @api public
|
450
455
|
def keep_alive
|
451
|
-
@env[
|
456
|
+
@env[Merb::Const::HTTP_KEEP_ALIVE]
|
452
457
|
end
|
453
|
-
|
458
|
+
|
454
459
|
# ==== Returns
|
455
460
|
# String:: The accepted character sets.
|
456
|
-
#
|
461
|
+
#
|
457
462
|
# @api public
|
458
463
|
def accept_charset
|
459
|
-
@env[
|
464
|
+
@env[Merb::Const::HTTP_ACCEPT_CHARSET]
|
460
465
|
end
|
461
|
-
|
466
|
+
|
462
467
|
# ==== Returns
|
463
468
|
# String:: The HTTP version
|
464
|
-
#
|
469
|
+
#
|
465
470
|
# @api private
|
466
471
|
def version
|
467
|
-
@env[
|
472
|
+
@env[Merb::Const::HTTP_VERSION]
|
468
473
|
end
|
469
|
-
|
474
|
+
|
470
475
|
# ==== Returns
|
471
476
|
# String:: The gateway.
|
472
|
-
#
|
477
|
+
#
|
473
478
|
# @api public
|
474
479
|
def gateway
|
475
|
-
@env[
|
480
|
+
@env[Merb::Const::GATEWAY_INTERFACE]
|
476
481
|
end
|
477
|
-
|
482
|
+
|
478
483
|
# ==== Returns
|
479
484
|
# String:: The accepted response types. Defaults to "*/*".
|
480
|
-
#
|
485
|
+
#
|
481
486
|
# @api private
|
482
487
|
def accept
|
483
|
-
@env[
|
488
|
+
@env[Merb::Const::HTTP_ACCEPT].blank? ? "*/*" : @env[Merb::Const::HTTP_ACCEPT]
|
484
489
|
end
|
485
|
-
|
490
|
+
|
486
491
|
# ==== Returns
|
487
492
|
# String:: The HTTP connection.
|
488
|
-
#
|
493
|
+
#
|
489
494
|
# @api private
|
490
495
|
def connection
|
491
|
-
@env[
|
496
|
+
@env[Merb::Const::HTTP_CONNECTION]
|
492
497
|
end
|
493
|
-
|
498
|
+
|
494
499
|
# ==== Returns
|
495
500
|
# String:: The query string.
|
496
|
-
#
|
501
|
+
#
|
497
502
|
# @api private
|
498
503
|
def query_string
|
499
|
-
@env[
|
504
|
+
@env[Merb::Const::QUERY_STRING]
|
500
505
|
end
|
501
|
-
|
506
|
+
|
502
507
|
# ==== Returns
|
503
508
|
# String:: The request content type.
|
504
|
-
#
|
509
|
+
#
|
505
510
|
# @api private
|
506
511
|
def content_type
|
507
|
-
@env[
|
512
|
+
@env[Merb::Const::UPCASE_CONTENT_TYPE]
|
508
513
|
end
|
509
|
-
|
514
|
+
|
510
515
|
# ==== Returns
|
511
516
|
# Fixnum:: The request content length.
|
512
|
-
#
|
517
|
+
#
|
513
518
|
# @api public
|
514
519
|
def content_length
|
515
520
|
@content_length ||= @env[Merb::Const::CONTENT_LENGTH].to_i
|
516
521
|
end
|
517
|
-
|
522
|
+
|
518
523
|
# ==== Returns
|
519
524
|
# String::
|
520
525
|
# The URI without the query string. Strips trailing "/" and reduces
|
521
526
|
# duplicate "/" to a single "/".
|
522
|
-
#
|
527
|
+
#
|
523
528
|
# @api public
|
524
529
|
def path
|
525
|
-
|
530
|
+
# Merb::Const::SLASH is /
|
531
|
+
# Merb::Const::QUESTION_MARK is ?
|
532
|
+
path = (uri.empty? ? Merb::Const::SLASH : uri.split(Merb::Const::QUESTION_MARK).first).squeeze(Merb::Const::SLASH)
|
526
533
|
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
527
534
|
path
|
528
535
|
end
|
529
|
-
|
536
|
+
|
530
537
|
# ==== Returns
|
531
538
|
# String:: The path info.
|
532
|
-
#
|
539
|
+
#
|
533
540
|
# @api public
|
534
541
|
def path_info
|
535
|
-
@path_info ||= self.class.unescape(@env[
|
542
|
+
@path_info ||= self.class.unescape(@env[Merb::Const::PATH_INFO])
|
536
543
|
end
|
537
|
-
|
544
|
+
|
538
545
|
# ==== Returns
|
539
546
|
# Fixnum:: The server port.
|
540
|
-
#
|
547
|
+
#
|
541
548
|
# @api public
|
542
549
|
def port
|
543
|
-
@env[
|
550
|
+
@env[Merb::Const::SERVER_PORT].to_i
|
544
551
|
end
|
545
|
-
|
552
|
+
|
546
553
|
# ==== Returns
|
547
554
|
# String:: The full hostname including the port.
|
548
|
-
#
|
555
|
+
#
|
549
556
|
# @api public
|
550
557
|
def host
|
551
|
-
@env[
|
558
|
+
@env[Merb::Const::HTTP_X_FORWARDED_HOST] || @env[Merb::Const::HTTP_HOST]
|
552
559
|
end
|
553
|
-
|
560
|
+
|
554
561
|
# ==== Parameters
|
555
562
|
# tld_length<Fixnum>::
|
556
563
|
# Number of domains levels to inlclude in the top level domain. Defaults
|
@@ -558,13 +565,13 @@ module Merb
|
|
558
565
|
#
|
559
566
|
# ==== Returns
|
560
567
|
# Array:: All the subdomain parts of the host.
|
561
|
-
#
|
568
|
+
#
|
562
569
|
# @api public
|
563
570
|
def subdomains(tld_length = 1)
|
564
|
-
parts = host.split(
|
571
|
+
parts = host.split(Merb::Const::DOT)
|
565
572
|
parts[0..-(tld_length+2)]
|
566
573
|
end
|
567
|
-
|
574
|
+
|
568
575
|
# ==== Parameters
|
569
576
|
# tld_length<Fixnum>::
|
570
577
|
# Number of domains levels to inlclude in the top level domain. Defaults
|
@@ -572,32 +579,32 @@ module Merb
|
|
572
579
|
#
|
573
580
|
# ==== Returns
|
574
581
|
# String:: The full domain name without the port number.
|
575
|
-
#
|
582
|
+
#
|
576
583
|
# @api public
|
577
584
|
def domain(tld_length = 1)
|
578
|
-
host.split(
|
585
|
+
host.split(Merb::Const::DOT).last(1 + tld_length).join(Merb::Const::DOT).sub(/:\d+$/,'')
|
579
586
|
end
|
580
|
-
|
587
|
+
|
581
588
|
# ==== Returns
|
582
589
|
# Value of If-None-Match request header.
|
583
|
-
#
|
590
|
+
#
|
584
591
|
# @api private
|
585
592
|
def if_none_match
|
586
593
|
@env[Merb::Const::HTTP_IF_NONE_MATCH]
|
587
594
|
end
|
588
|
-
|
595
|
+
|
589
596
|
# ==== Returns
|
590
597
|
# Value of If-Modified-Since request header.
|
591
|
-
#
|
598
|
+
#
|
592
599
|
# @api private
|
593
600
|
def if_modified_since
|
594
601
|
if time = @env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
|
595
602
|
Time.rfc2822(time)
|
596
603
|
end
|
597
604
|
end
|
598
|
-
|
605
|
+
|
599
606
|
class << self
|
600
|
-
|
607
|
+
|
601
608
|
# ==== Parameters
|
602
609
|
# value<Array, Hash, Dictionary ~to_s>:: The value for the query string.
|
603
610
|
# prefix<~to_s>:: The prefix to add to the query string keys.
|
@@ -617,7 +624,7 @@ module Merb
|
|
617
624
|
# # => "search[page]=10&search[word]=ruby"
|
618
625
|
# params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
|
619
626
|
# # => "shopping_list[]=ice-cream&shopping_list[]=cake"
|
620
|
-
#
|
627
|
+
#
|
621
628
|
# @api private
|
622
629
|
def params_to_query_string(value, prefix = nil)
|
623
630
|
case value
|
@@ -633,33 +640,33 @@ module Merb
|
|
633
640
|
"#{prefix}=#{Merb::Request.escape(value)}"
|
634
641
|
end
|
635
642
|
end
|
636
|
-
|
643
|
+
|
637
644
|
# ==== Parameters
|
638
645
|
# s<String>:: String to URL escape.
|
639
646
|
#
|
640
647
|
# ==== returns
|
641
648
|
# String:: The escaped string.
|
642
|
-
#
|
649
|
+
#
|
643
650
|
# @api private
|
644
651
|
def escape(s)
|
645
652
|
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
646
653
|
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
647
654
|
}.tr(' ', '+')
|
648
655
|
end
|
649
|
-
|
656
|
+
|
650
657
|
# ==== Parameter
|
651
658
|
# s<String>:: String to URL unescape.
|
652
659
|
#
|
653
660
|
# ==== returns
|
654
661
|
# String:: The unescaped string.
|
655
|
-
#
|
662
|
+
#
|
656
663
|
# @api private
|
657
664
|
def unescape(s)
|
658
665
|
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
659
666
|
[$1.delete('%')].pack('H*')
|
660
667
|
}
|
661
668
|
end
|
662
|
-
|
669
|
+
|
663
670
|
# ==== Parameters
|
664
671
|
# query_string<String>:: The query string.
|
665
672
|
# delimiter<String>:: The query string divider. Defaults to "&".
|
@@ -671,7 +678,7 @@ module Merb
|
|
671
678
|
# ==== Examples
|
672
679
|
# query_parse("bar=nik&post[body]=heya")
|
673
680
|
# # => { :bar => "nik", :post => { :body => "heya" } }
|
674
|
-
#
|
681
|
+
#
|
675
682
|
# @api private
|
676
683
|
def query_parse(query_string, delimiter = '&;', preserve_order = false)
|
677
684
|
query = preserve_order ? Dictionary.new : {}
|
@@ -680,19 +687,19 @@ module Merb
|
|
680
687
|
next if key.nil?
|
681
688
|
if key.include?('[')
|
682
689
|
normalize_params(query, key, value)
|
683
|
-
else
|
690
|
+
else
|
684
691
|
query[key] = value
|
685
692
|
end
|
686
693
|
end
|
687
694
|
preserve_order ? query : query.to_mash
|
688
695
|
end
|
689
|
-
|
696
|
+
|
690
697
|
NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
|
691
698
|
CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
|
692
699
|
FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
|
693
700
|
CRLF = "\r\n".freeze
|
694
701
|
EOL = CRLF
|
695
|
-
|
702
|
+
|
696
703
|
# ==== Parameters
|
697
704
|
# request<IO>:: The raw request.
|
698
705
|
# boundary<String>:: The boundary string.
|
@@ -703,7 +710,7 @@ module Merb
|
|
703
710
|
#
|
704
711
|
# ==== Returns
|
705
712
|
# Hash:: The parsed request.
|
706
|
-
#
|
713
|
+
#
|
707
714
|
# @api private
|
708
715
|
def parse_multipart(request, boundary, content_length)
|
709
716
|
boundary = "--#{boundary}"
|
@@ -735,19 +742,19 @@ module Merb
|
|
735
742
|
filename = head[FILENAME_REGEX, 1]
|
736
743
|
content_type = head[CONTENT_TYPE_REGEX, 1]
|
737
744
|
name = head[NAME_REGEX, 1]
|
738
|
-
|
745
|
+
|
739
746
|
if filename && !filename.empty?
|
740
747
|
body = Tempfile.new(:Merb)
|
741
748
|
body.binmode if defined? body.binmode
|
742
749
|
end
|
743
750
|
next
|
744
751
|
end
|
745
|
-
|
752
|
+
|
746
753
|
# Save the read body part.
|
747
754
|
if head && (boundary_size+4 < buf.size)
|
748
755
|
body << buf.slice!(0, buf.size - (boundary_size+4))
|
749
756
|
end
|
750
|
-
|
757
|
+
|
751
758
|
read_size = bufsize < content_length ? bufsize : content_length
|
752
759
|
if( read_size > 0 )
|
753
760
|
c = input.read(read_size)
|
@@ -756,22 +763,22 @@ module Merb
|
|
756
763
|
content_length -= c.size
|
757
764
|
end
|
758
765
|
end
|
759
|
-
|
766
|
+
|
760
767
|
# Save the rest.
|
761
768
|
if i = buf.index(rx)
|
762
769
|
body << buf.slice!(0, i)
|
763
770
|
buf.slice!(0, boundary_size+2)
|
764
|
-
|
771
|
+
|
765
772
|
content_length = -1 if $1 == "--"
|
766
773
|
end
|
767
|
-
|
768
|
-
if filename && !filename.empty?
|
774
|
+
|
775
|
+
if filename && !filename.empty?
|
769
776
|
body.rewind
|
770
|
-
data = {
|
771
|
-
:filename => File.basename(filename),
|
772
|
-
:content_type => content_type,
|
773
|
-
:tempfile => body,
|
774
|
-
:size => File.size(body.path)
|
777
|
+
data = {
|
778
|
+
:filename => File.basename(filename),
|
779
|
+
:content_type => content_type,
|
780
|
+
:tempfile => body,
|
781
|
+
:size => File.size(body.path)
|
775
782
|
}
|
776
783
|
else
|
777
784
|
data = body
|
@@ -781,7 +788,7 @@ module Merb
|
|
781
788
|
}
|
782
789
|
paramhsh
|
783
790
|
end
|
784
|
-
|
791
|
+
|
785
792
|
# Converts a query string snippet to a hash and adds it to existing
|
786
793
|
# parameters.
|
787
794
|
#
|
@@ -792,13 +799,13 @@ module Merb
|
|
792
799
|
#
|
793
800
|
# ==== Returns
|
794
801
|
# Hash:: Normalized parameters
|
795
|
-
#
|
802
|
+
#
|
796
803
|
# @api private
|
797
804
|
def normalize_params(parms, name, val=nil)
|
798
805
|
name =~ %r([\[\]]*([^\[\]]+)\]*)
|
799
806
|
key = $1 || ''
|
800
807
|
after = $' || ''
|
801
|
-
|
808
|
+
|
802
809
|
if after == ""
|
803
810
|
parms[key] = val
|
804
811
|
elsif after == "[]"
|