merb-core 0.9.10 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,236 @@
1
+ module Merb
2
+ module Parse
3
+
4
+ # ==== Parameters
5
+ # query_string<String>:: The query string.
6
+ # delimiter<String>:: The query string divider. Defaults to "&".
7
+ # preserve_order<Boolean>:: Preserve order of args. Defaults to false.
8
+ #
9
+ # ==== Returns
10
+ # Mash:: The parsed query string (Dictionary if preserve_order is set).
11
+ #
12
+ # ==== Examples
13
+ # Merb::Parse.query("bar=nik&post[body]=heya")
14
+ # # => { :bar => "nik", :post => { :body => "heya" } }
15
+ #
16
+ # @api plugin
17
+ def self.query(query_string, delimiter = '&;', preserve_order = false)
18
+ query = preserve_order ? Dictionary.new : {}
19
+ for pair in (query_string || '').split(/[#{delimiter}] */n)
20
+ key, value = unescape(pair).split('=',2)
21
+ next if key.nil?
22
+ if key.include?('[')
23
+ normalize_params(query, key, value)
24
+ else
25
+ query[key] = value
26
+ end
27
+ end
28
+ preserve_order ? query : query.to_mash
29
+ end
30
+
31
+ NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
32
+ CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
33
+ FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
34
+ CRLF = "\r\n".freeze
35
+ EOL = CRLF
36
+
37
+ # ==== Parameters
38
+ # request<IO>:: The raw request.
39
+ # boundary<String>:: The boundary string.
40
+ # content_length<Fixnum>:: The length of the content.
41
+ #
42
+ # ==== Raises
43
+ # ControllerExceptions::MultiPartParseError:: Failed to parse request.
44
+ #
45
+ # ==== Returns
46
+ # Hash:: The parsed request.
47
+ #
48
+ # @api plugin
49
+ def self.multipart(request, boundary, content_length)
50
+ boundary = "--#{boundary}"
51
+ paramhsh = {}
52
+ buf = ""
53
+ input = request
54
+ input.binmode if defined? input.binmode
55
+ boundary_size = boundary.size + EOL.size
56
+ bufsize = 16384
57
+ content_length -= boundary_size
58
+ # status is boundary delimiter line
59
+ status = input.read(boundary_size)
60
+ return {} if status == nil || status.empty?
61
+ raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
62
+ # second argument to Regexp.quote is for KCODE
63
+ rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
64
+ loop {
65
+ head = nil
66
+ body = ''
67
+ filename = content_type = name = nil
68
+ read_size = 0
69
+ until head && buf =~ rx
70
+ i = buf.index("\r\n\r\n")
71
+ if( i == nil && read_size == 0 && content_length == 0 )
72
+ content_length = -1
73
+ break
74
+ end
75
+ if !head && i
76
+ head = buf.slice!(0, i+2) # First \r\n
77
+ buf.slice!(0, 2) # Second \r\n
78
+
79
+ # String#[] with 2nd arg here is returning
80
+ # a group from match data
81
+ filename = head[FILENAME_REGEX, 1]
82
+ content_type = head[CONTENT_TYPE_REGEX, 1]
83
+ name = head[NAME_REGEX, 1]
84
+
85
+ if filename && !filename.empty?
86
+ body = Tempfile.new(:Merb)
87
+ body.binmode if defined? body.binmode
88
+ end
89
+ next
90
+ end
91
+
92
+ # Save the read body part.
93
+ if head && (boundary_size+4 < buf.size)
94
+ body << buf.slice!(0, buf.size - (boundary_size+4))
95
+ end
96
+
97
+ read_size = bufsize < content_length ? bufsize : content_length
98
+ if( read_size > 0 )
99
+ c = input.read(read_size)
100
+ raise ControllerExceptions::MultiPartParseError, "bad content body" if c.nil? || c.empty?
101
+ buf << c
102
+ content_length -= c.size
103
+ end
104
+ end
105
+
106
+ # Save the rest.
107
+ if i = buf.index(rx)
108
+ # correct value of i for some edge cases
109
+ if (i > 2) && (j = buf.index(rx, i-2)) && (j < i)
110
+ i = j
111
+ end
112
+ body << buf.slice!(0, i)
113
+ buf.slice!(0, boundary_size+2)
114
+
115
+ content_length = -1 if $1 == "--"
116
+ end
117
+
118
+ if filename && !filename.empty?
119
+ body.rewind
120
+ data = {
121
+ :filename => File.basename(filename),
122
+ :content_type => content_type,
123
+ :tempfile => body,
124
+ :size => File.size(body.path)
125
+ }
126
+ else
127
+ data = body
128
+ end
129
+ paramhsh = normalize_params(paramhsh,name,data)
130
+ break if buf.empty? || content_length == -1
131
+ }
132
+ paramhsh
133
+ end
134
+
135
+ # ==== Parameters
136
+ # value<Array, Hash, Dictionary ~to_s>:: The value for the query string.
137
+ # prefix<~to_s>:: The prefix to add to the query string keys.
138
+ #
139
+ # ==== Returns
140
+ # String:: The query string.
141
+ #
142
+ # ==== Alternatives
143
+ # If the value is a string, the prefix will be used as the key.
144
+ #
145
+ # ==== Examples
146
+ # params_to_query_string(10, "page")
147
+ # # => "page=10"
148
+ # params_to_query_string({ :page => 10, :word => "ruby" })
149
+ # # => "page=10&word=ruby"
150
+ # params_to_query_string({ :page => 10, :word => "ruby" }, "search")
151
+ # # => "search[page]=10&search[word]=ruby"
152
+ # params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
153
+ # # => "shopping_list[]=ice-cream&shopping_list[]=cake"
154
+ #
155
+ # @api plugin
156
+ def self.params_to_query_string(value, prefix = nil)
157
+ case value
158
+ when Array
159
+ value.map { |v|
160
+ params_to_query_string(v, "#{prefix}[]")
161
+ } * "&"
162
+ when Hash, Dictionary
163
+ value.map { |k, v|
164
+ params_to_query_string(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
165
+ } * "&"
166
+ else
167
+ "#{prefix}=#{escape(value)}"
168
+ end
169
+ end
170
+
171
+ # ==== Parameters
172
+ # s<String>:: String to URL escape.
173
+ #
174
+ # ==== returns
175
+ # String:: The escaped string.
176
+ #
177
+ # @api public
178
+ def self.escape(s)
179
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
180
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
181
+ }.tr(' ', '+')
182
+ end
183
+
184
+ # ==== Parameter
185
+ # s<String>:: String to URL unescape.
186
+ #
187
+ # ==== returns
188
+ # String:: The unescaped string.
189
+ #
190
+ # @api public
191
+ def self.unescape(s)
192
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
193
+ [$1.delete('%')].pack('H*')
194
+ }
195
+ end
196
+
197
+ private
198
+
199
+ # Converts a query string snippet to a hash and adds it to existing
200
+ # parameters.
201
+ #
202
+ # ==== Parameters
203
+ # parms<Hash>:: Parameters to add the normalized parameters to.
204
+ # name<String>:: The key of the parameter to normalize.
205
+ # val<String>:: The value of the parameter.
206
+ #
207
+ # ==== Returns
208
+ # Hash:: Normalized parameters
209
+ #
210
+ # @api private
211
+ def self.normalize_params(parms, name, val=nil)
212
+ name =~ %r([\[\]]*([^\[\]]+)\]*)
213
+ key = $1 || ''
214
+ after = $' || ''
215
+
216
+ if after == ""
217
+ parms[key] = val
218
+ elsif after == "[]"
219
+ (parms[key] ||= []) << val
220
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$)
221
+ child_key = $1
222
+ parms[key] ||= []
223
+ if parms[key].last.is_a?(Hash) && !parms[key].last.key?(child_key)
224
+ parms[key].last.update(child_key => val)
225
+ else
226
+ parms[key] << { child_key => val }
227
+ end
228
+ else
229
+ parms[key] ||= {}
230
+ parms[key] = normalize_params(parms[key], after, val)
231
+ end
232
+ parms
233
+ end
234
+
235
+ end
236
+ end
@@ -223,7 +223,7 @@ module Merb
223
223
 
224
224
  ruby << " query_params.delete_if { |key, value| value.nil? }\n"
225
225
  ruby << " unless query_params.empty?\n"
226
- ruby << ' url << "?#{Merb::Request.params_to_query_string(query_params)}"' << "\n"
226
+ ruby << ' url << "?#{Merb::Parse.params_to_query_string(query_params)}"' << "\n"
227
227
  ruby << " end\n"
228
228
  ruby << ' url << "##{fragment}" if fragment' << "\n"
229
229
  ruby << " url\n"
@@ -126,7 +126,7 @@ module Merb
126
126
  def to_cookie
127
127
  unless self.empty?
128
128
  data = self.serialize
129
- value = Merb::Request.escape "#{data}--#{generate_digest(data)}"
129
+ value = Merb::Parse.escape "#{data}--#{generate_digest(data)}"
130
130
  if value.size > MAX
131
131
  msg = "Cookies have limit of 4K. Session contents: #{data.inspect}"
132
132
  Merb.logger.error!(msg)
@@ -164,7 +164,7 @@ module Merb
164
164
  if cookie.blank?
165
165
  {}
166
166
  else
167
- data, digest = Merb::Request.unescape(cookie).split('--')
167
+ data, digest = Merb::Parse.unescape(cookie).split('--')
168
168
  return {} if data.blank? || digest.blank?
169
169
  unless digest == generate_digest(data)
170
170
  clear
@@ -21,7 +21,6 @@ module Merb
21
21
  autoload :Tracer, 'merb-core/rack/middleware/tracer'
22
22
  autoload :ContentLength, 'merb-core/rack/middleware/content_length'
23
23
  autoload :ConditionalGet, 'merb-core/rack/middleware/conditional_get'
24
- autoload :Csrf, 'merb-core/rack/middleware/csrf'
25
24
  autoload :StreamWrapper, 'merb-core/rack/stream_wrapper'
26
25
  autoload :Helpers, 'merb-core/rack/helpers'
27
26
  end # Rack
@@ -168,7 +168,9 @@ module Merb
168
168
  # If it was not fork_for_class_load, we already set up
169
169
  # ctrl-c handlers in the master thread.
170
170
  elsif Merb::Config[:fork_for_class_load]
171
- Merb.trap('INT') { 1 }
171
+ if Merb::Config[:console_trap]
172
+ Merb::Server.add_irb_trap
173
+ end
172
174
  end
173
175
 
174
176
  # In daemonized mode or not, support HUPing the process to
@@ -33,14 +33,14 @@ module Merb
33
33
  # ==== Returns
34
34
  # Boolean:: True if file exists under the server root and is readable.
35
35
  def file_exist?(path)
36
- full_path = ::File.join(@static_server.root, ::Merb::Request.unescape(path))
36
+ full_path = ::File.join(@static_server.root, ::Merb::Parse.unescape(path))
37
37
  ::File.file?(full_path) && ::File.readable?(full_path)
38
38
  end
39
39
 
40
40
  # ==== Parameters
41
41
  # env<Hash>:: Environment variables to pass on to the server.
42
42
  def serve_static(env)
43
- env[Merb::Const::PATH_INFO] = ::Merb::Request.unescape(env[Merb::Const::PATH_INFO])
43
+ env[Merb::Const::PATH_INFO] = ::Merb::Parse.unescape(env[Merb::Const::PATH_INFO])
44
44
  @static_server.call(env)
45
45
  end
46
46
 
@@ -266,7 +266,7 @@ module Merb
266
266
  Merb.fatal! "Failed to store Merb logs in #{File.dirname(file)}, " \
267
267
  "permission denied. ", e
268
268
  end
269
- Merb.logger.warn! "Storing #{type} file to #{file}..." if Merb::Config[:verbose]
269
+ Merb.logger.warn! "Storing pid #{Process.pid} file to #{file}..." if Merb::Config[:verbose]
270
270
  begin
271
271
  File.open(file, 'w'){ |f| f.write(Process.pid.to_s) }
272
272
  rescue Errno::EACCES => e
@@ -292,7 +292,7 @@ module GemManagement
292
292
  end
293
293
  end
294
294
  end
295
-
295
+
296
296
  private
297
297
 
298
298
  def executable_wrapper(spec, bin_file_name, minigems = true)
@@ -316,6 +316,7 @@ end
316
316
  if File.directory?(gems_dir = File.join(Dir.pwd, 'gems')) ||
317
317
  File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
318
318
  $BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
319
+ ENV["PATH"] = "\#{File.dirname(__FILE__)}:\#{gems_dir}/bin:\#{ENV["PATH"]}"
319
320
  if (local_gem = Dir[File.join(gems_dir, "specifications", "#{spec.name}-*.gemspec")].last)
320
321
  version = File.basename(local_gem)[/-([\\.\\d]+)\\.gemspec$/, 1]
321
322
  end
@@ -2,9 +2,10 @@
2
2
  # testing helpers
3
3
  module Merb::Test::Helpers; end
4
4
 
5
+ require "merb-core/test/helpers/cookie_jar"
5
6
  require "merb-core/test/helpers/mock_request_helper"
7
+ require "merb-core/test/helpers/route_helper"
6
8
  require "merb-core/test/helpers/request_helper"
7
9
  require "merb-core/test/helpers/multipart_request_helper"
8
10
  require "merb-core/test/helpers/controller_helper"
9
- require "merb-core/test/helpers/route_helper"
10
11
  require "merb-core/test/helpers/view_helper"
@@ -0,0 +1,106 @@
1
+ require 'uri'
2
+
3
+ module Merb
4
+ module Test
5
+ class Cookie
6
+
7
+ attr_reader :name, :value
8
+
9
+ def initialize(raw, default_host)
10
+ # separate the name / value pair from the cookie options
11
+ @name_value_raw, options = raw.split(/[;,] */n, 2)
12
+
13
+ @name, @value = Merb::Parse.query(@name_value_raw, ';').to_a.first
14
+ @options = Merb::Parse.query(options, ';')
15
+
16
+ @options.delete_if { |k, v| !v || v.empty? }
17
+
18
+ @options["domain"] ||= default_host
19
+ end
20
+
21
+ def raw
22
+ @name_value_raw
23
+ end
24
+
25
+ def empty?
26
+ @value.nil? || @value.empty?
27
+ end
28
+
29
+ def domain
30
+ @options["domain"]
31
+ end
32
+
33
+ def path
34
+ @options["path"] || "/"
35
+ end
36
+
37
+ def expires
38
+ Time.parse(@options["expires"]) if @options["expires"]
39
+ end
40
+
41
+ def expired?
42
+ expires && expires < Time.now
43
+ end
44
+
45
+ def valid?(uri)
46
+ uri.host =~ Regexp.new("#{Regexp.escape(domain)}$") &&
47
+ uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
48
+ end
49
+
50
+ def matches?(uri)
51
+ ! expired? && valid?(uri)
52
+ end
53
+
54
+ def <=>(other)
55
+ # Orders the cookies from least specific to most
56
+ [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
57
+ end
58
+
59
+ end
60
+
61
+ class CookieJar
62
+
63
+ def initialize
64
+ @jars = {}
65
+ end
66
+
67
+ def update(jar, uri, raw_cookies)
68
+ return unless raw_cookies
69
+ # Initialize all the the received cookies
70
+ cookies = []
71
+ raw_cookies.each do |raw|
72
+ c = Cookie.new(raw, uri.host)
73
+ cookies << c if c.valid?(uri)
74
+ end
75
+
76
+ @jars[jar] ||= []
77
+
78
+ # Remove all the cookies that will be updated
79
+ @jars[jar].delete_if do |existing|
80
+ cookies.find { |c| [c.name, c.domain, c.path] == [existing.name, existing.domain, existing.path] }
81
+ end
82
+
83
+ @jars[jar].concat cookies
84
+
85
+ @jars[jar].sort!
86
+ end
87
+
88
+ def for(jar, uri)
89
+ cookies = {}
90
+
91
+ @jars[jar] ||= []
92
+ # The cookies are sorted by most specific first. So, we loop through
93
+ # all the cookies in order and add it to a hash by cookie name if
94
+ # the cookie can be sent to the current URI. It's added to the hash
95
+ # so that when we are done, the cookies will be unique by name and
96
+ # we'll have grabbed the most specific to the URI.
97
+ @jars[jar].each do |cookie|
98
+ cookies[cookie.name] = cookie.raw if cookie.matches?(uri)
99
+ end
100
+
101
+ cookies.values.join
102
+ end
103
+
104
+ end
105
+ end
106
+ end