merb-core 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- 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 == "[]"
|