merb-core 0.9.10 → 0.9.11

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.
@@ -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