rack 2.2.10 → 3.1.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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +346 -90
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +204 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +1 -3
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +102 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +25 -19
  15. data/lib/rack/conditional_get.rb +18 -15
  16. data/lib/rack/constants.rb +67 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +14 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +9 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +840 -644
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/media_type.rb +8 -3
  30. data/lib/rack/method_override.rb +5 -1
  31. data/lib/rack/mime.rb +14 -5
  32. data/lib/rack/mock.rb +1 -271
  33. data/lib/rack/mock_request.rb +161 -0
  34. data/lib/rack/mock_response.rb +124 -0
  35. data/lib/rack/multipart/generator.rb +7 -5
  36. data/lib/rack/multipart/parser.rb +213 -95
  37. data/lib/rack/multipart/uploaded_file.rb +4 -0
  38. data/lib/rack/multipart.rb +53 -40
  39. data/lib/rack/null_logger.rb +9 -0
  40. data/lib/rack/query_parser.rb +81 -102
  41. data/lib/rack/recursive.rb +2 -0
  42. data/lib/rack/reloader.rb +0 -2
  43. data/lib/rack/request.rb +260 -123
  44. data/lib/rack/response.rb +151 -66
  45. data/lib/rack/rewindable_input.rb +24 -5
  46. data/lib/rack/runtime.rb +7 -6
  47. data/lib/rack/sendfile.rb +30 -25
  48. data/lib/rack/show_exceptions.rb +21 -4
  49. data/lib/rack/show_status.rb +17 -7
  50. data/lib/rack/static.rb +8 -8
  51. data/lib/rack/tempfile_reaper.rb +15 -4
  52. data/lib/rack/urlmap.rb +3 -1
  53. data/lib/rack/utils.rb +236 -237
  54. data/lib/rack/version.rb +1 -9
  55. data/lib/rack.rb +13 -89
  56. metadata +15 -44
  57. data/README.rdoc +0 -320
  58. data/Rakefile +0 -130
  59. data/bin/rackup +0 -5
  60. data/contrib/rack.png +0 -0
  61. data/contrib/rack.svg +0 -150
  62. data/contrib/rack_logo.svg +0 -164
  63. data/contrib/rdoc.css +0 -412
  64. data/example/lobster.ru +0 -6
  65. data/example/protectedlobster.rb +0 -16
  66. data/example/protectedlobster.ru +0 -10
  67. data/lib/rack/auth/digest/md5.rb +0 -131
  68. data/lib/rack/auth/digest/nonce.rb +0 -53
  69. data/lib/rack/auth/digest/params.rb +0 -54
  70. data/lib/rack/auth/digest/request.rb +0 -43
  71. data/lib/rack/chunked.rb +0 -117
  72. data/lib/rack/core_ext/regexp.rb +0 -14
  73. data/lib/rack/file.rb +0 -7
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -85
  87. data/rack.gemspec +0 -46
data/contrib/rdoc.css DELETED
@@ -1,412 +0,0 @@
1
- /* Forked from the Darkfish templates rdoc.css file, much hacked, probably
2
- * imperfect */
3
-
4
- html { max-width: 960px; margin: 0 auto; }
5
- body {
6
- font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif;
7
- }
8
- body.file-popup {
9
- font-size: 90%;
10
- margin-left: 0;
11
- }
12
-
13
- h1 {
14
- color: #4183C4;
15
- }
16
-
17
- :link,
18
- :visited {
19
- color: #4183C4;
20
- text-decoration: none;
21
- }
22
- :link:hover,
23
- :visited:hover {
24
- border-bottom: 1px dotted #4183C4;
25
- }
26
-
27
- pre, pre.description {
28
- font: 12px Monaco,"Courier New","DejaVu Sans Mono","Bitstream Vera Sans Mono",monospace;
29
- padding: 1em;
30
- overflow: auto;
31
- line-height: 1.4;
32
- }
33
-
34
- /* @group Generic Classes */
35
-
36
- .initially-hidden {
37
- display: none;
38
- }
39
-
40
- #search-field {
41
- width: 98%;
42
- }
43
-
44
- .missing-docs {
45
- font-size: 120%;
46
- background: white url(images/wrench_orange.png) no-repeat 4px center;
47
- color: #ccc;
48
- line-height: 2em;
49
- border: 1px solid #d00;
50
- opacity: 1;
51
- text-indent: 24px;
52
- letter-spacing: 3px;
53
- font-weight: bold;
54
- -webkit-border-radius: 5px;
55
- -moz-border-radius: 5px;
56
- }
57
-
58
- .target-section {
59
- border: 2px solid #dcce90;
60
- border-left-width: 8px;
61
- background: #fff3c2;
62
- }
63
-
64
- /* @end */
65
-
66
- /* @group Index Page, Standalone file pages */
67
- .indexpage ul {
68
- line-height: 160%;
69
- list-style: none;
70
- }
71
- .indexpage ul :link,
72
- .indexpage ul :visited {
73
- font-size: 16px;
74
- }
75
-
76
- .indexpage li {
77
- padding-left: 20px;
78
- }
79
-
80
- .indexpage ul > li {
81
- background: url(images/bullet_black.png) no-repeat left 4px;
82
- }
83
- .indexpage li.method {
84
- background: url(images/plugin.png) no-repeat left 4px;
85
- }
86
- .indexpage li.module {
87
- background: url(images/package.png) no-repeat left 4px;
88
- }
89
- .indexpage li.class {
90
- background: url(images/ruby.png) no-repeat left 4px;
91
- }
92
- .indexpage li.file {
93
- background: url(images/page_white_text.png) no-repeat left 4px;
94
- }
95
- .indexpage li li {
96
- background: url(images/tag_blue.png) no-repeat left 4px;
97
- }
98
- .indexpage li .toc-toggle {
99
- width: 16px;
100
- height: 16px;
101
- background: url(images/add.png) no-repeat;
102
- }
103
-
104
- .indexpage li .toc-toggle.open {
105
- background: url(images/delete.png) no-repeat;
106
- }
107
-
108
- /* @end */
109
-
110
- /* @group Top-Level Structure */
111
-
112
- .project-section, #file-metadata, #class-metadata {
113
- display: block;
114
- background: #f5f5f5;
115
- margin-bottom: 1em;
116
- padding: 0.5em;
117
- }
118
- .project-section h3, #file-metadata h3, #class-metadata h3 {
119
- margin: 0;
120
- }
121
-
122
- #metadata {
123
- float: left;
124
- width: 280px;
125
- }
126
-
127
- #documentation {
128
- margin: 2em 1em 2em 300px;
129
- }
130
-
131
- #validator-badges {
132
- clear: both;
133
- margin: 1em 1em 2em;
134
- font-size: smaller;
135
- }
136
-
137
- /* @end */
138
-
139
- /* @group Metadata Section */
140
-
141
- #metadata ul,
142
- #metadata dl,
143
- #metadata p {
144
- padding: 0px;
145
- list-style: none;
146
- }
147
-
148
- dl.svninfo {
149
- color: #666;
150
- margin: 0;
151
- }
152
- dl.svninfo dt {
153
- font-weight: bold;
154
- }
155
-
156
- ul.link-list li {
157
- white-space: nowrap;
158
- }
159
- ul.link-list .type {
160
- font-size: 8px;
161
- text-transform: uppercase;
162
- color: white;
163
- background: #969696;
164
- }
165
-
166
- /* @end */
167
-
168
- /* @group Documentation Section */
169
-
170
- .note-list {
171
- margin: 8px 0;
172
- }
173
-
174
- .label-list {
175
- margin: 8px 1.5em;
176
- border: 1px solid #ccc;
177
- }
178
- .description .label-list {
179
- font-size: 14px;
180
- }
181
-
182
- .note-list dt {
183
- font-weight: bold;
184
- }
185
- .note-list dd {
186
- }
187
-
188
- .label-list dt {
189
- font-weight: bold;
190
- background: #ddd;
191
- }
192
- .label-list dd {
193
- }
194
- .label-list dd + dt,
195
- .note-list dd + dt {
196
- margin-top: 0.7em;
197
- }
198
-
199
- #documentation .section {
200
- font-size: 90%;
201
- }
202
-
203
- #documentation h2.section-header {
204
- color: #333;
205
- font-size: 175%;
206
- }
207
-
208
- .documentation-section-title {
209
- position: relative;
210
- }
211
- .documentation-section-title .section-click-top {
212
- position: absolute;
213
- top: 6px;
214
- right: 12px;
215
- font-size: 10px;
216
- visibility: hidden;
217
- }
218
-
219
- .documentation-section-title:hover .section-click-top {
220
- visibility: visible;
221
- }
222
-
223
- #documentation h3.section-header {
224
- color: #333;
225
- font-size: 150%;
226
- }
227
-
228
- #constants-list > dl,
229
- #attributes-list > dl {
230
- margin: 1em 0 2em;
231
- border: 0;
232
- }
233
- #constants-list > dl dt,
234
- #attributes-list > dl dt {
235
- font-weight: bold;
236
- font-family: Monaco, "Andale Mono";
237
- background: inherit;
238
- }
239
- #constants-list > dl dt a,
240
- #attributes-list > dl dt a {
241
- color: inherit;
242
- }
243
- #constants-list > dl dd,
244
- #attributes-list > dl dd {
245
- margin: 0 0 1em 0;
246
- color: #666;
247
- }
248
-
249
- .documentation-section h2 {
250
- position: relative;
251
- }
252
-
253
- .documentation-section h2 a {
254
- position: absolute;
255
- top: 8px;
256
- right: 10px;
257
- font-size: 12px;
258
- color: #9b9877;
259
- visibility: hidden;
260
- }
261
-
262
- .documentation-section h2:hover a {
263
- visibility: visible;
264
- }
265
-
266
- /* @group Method Details */
267
-
268
- #documentation .method-source-code {
269
- display: none;
270
- }
271
-
272
- #documentation .method-detail {
273
- margin: 0.2em 0.2em;
274
- padding: 0.5em;
275
- }
276
- #documentation .method-detail:hover {
277
- background-color: #f5f5f5;
278
- }
279
- #documentation .method-heading {
280
- cursor: pointer;
281
- position: relative;
282
- font-size: 125%;
283
- line-height: 125%;
284
- font-weight: bold;
285
- color: #333;
286
- background: url(images/brick.png) no-repeat left bottom;
287
- padding-left: 20px;
288
- }
289
- #documentation .method-heading :link,
290
- #documentation .method-heading :visited {
291
- color: inherit;
292
- }
293
- #documentation .method-click-advice {
294
- position: absolute;
295
- right: 5px;
296
- font-size: 10px;
297
- color: #aaa;
298
- visibility: hidden;
299
- background: url(images/zoom.png) no-repeat right 5px;
300
- padding-right: 20px;
301
- overflow: show;
302
- }
303
- #documentation .method-heading:hover .method-click-advice {
304
- visibility: visible;
305
- }
306
-
307
- #documentation .method-alias .method-heading {
308
- color: #666;
309
- background: url(images/brick_link.png) no-repeat left bottom;
310
- }
311
-
312
- #documentation .method-description,
313
- #documentation .aliases {
314
- margin: 0 20px;
315
- color: #666;
316
- }
317
-
318
- #documentation .method-description p,
319
- #documentation .aliases p {
320
- line-height: 1.2em;
321
- }
322
-
323
- #documentation .aliases {
324
- font-style: italic;
325
- cursor: default;
326
- }
327
- #documentation .method-description p {
328
- margin-bottom: 0.5em;
329
- }
330
- #documentation .method-description ul {
331
- margin-left: 1.5em;
332
- }
333
-
334
- #documentation .attribute-method-heading {
335
- background: url(images/tag_green.png) no-repeat left bottom;
336
- }
337
- #documentation #attribute-method-details .method-detail:hover {
338
- background-color: transparent;
339
- cursor: default;
340
- }
341
- #documentation .attribute-access-type {
342
- font-size: 60%;
343
- text-transform: uppercase;
344
- vertical-align: super;
345
- }
346
-
347
- .method-section .method-source-code {
348
- background: white;
349
- }
350
-
351
- /* @group Source Code */
352
-
353
- .ruby-constant .ruby-keyword .ruby-ivar .ruby-operator .ruby-identifier
354
- .ruby-node .ruby-comment .ruby-regexp .ruby-value {
355
- background: transparent;
356
- }
357
-
358
- /* Thanks GitHub!!! */
359
- .ruby-constant { color: #458; font-weight: bold; }
360
- .ruby-keyword { color: black; font-weight: bold; }
361
- .ruby-ivar { color: teal; }
362
- .ruby-operator { color: #000; }
363
- .ruby-identifier { color: black; }
364
- .ruby-node { color: red; }
365
- .ruby-comment { color: #998; font-weight: bold; }
366
- .ruby-regexp { color: #009926; }
367
- .ruby-value { color: #099; }
368
- .ruby-string { color: red; }
369
-
370
- /* @group search results */
371
-
372
- #search-section .section-header {
373
- margin: 0;
374
- padding: 0;
375
- }
376
-
377
- #search-results {
378
- width: 100%;
379
- list-style: none;
380
- margin: 0;
381
- padding: 0;
382
- }
383
-
384
- #search-results h1 {
385
- font-size: 1em;
386
- font-weight: normal;
387
- text-shadow: none;
388
- }
389
-
390
- #search-results .current {
391
- background: #eee;
392
- }
393
-
394
- #search-results li {
395
- list-style: none;
396
- line-height: 1em;
397
- padding: 0.5em;
398
- border-bottom: 1px solid black;
399
- }
400
-
401
- #search-results .search-namespace {
402
- font-weight: bold;
403
- }
404
-
405
- #search-results li em {
406
- background: yellow;
407
- font-style: normal;
408
- }
409
-
410
- #search-results pre {
411
- margin: 0.5em;
412
- }
data/example/lobster.ru DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack/lobster'
4
-
5
- use Rack::ShowExceptions
6
- run Rack::Lobster.new
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack'
4
- require 'rack/lobster'
5
-
6
- lobster = Rack::Lobster.new
7
-
8
- protected_lobster = Rack::Auth::Basic.new(lobster) do |username, password|
9
- Rack::Utils.secure_compare('secret', password)
10
- end
11
-
12
- protected_lobster.realm = 'Lobster 2.0'
13
-
14
- pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster))
15
-
16
- Rack::Server.start app: pretty_protected_lobster, Port: 9292
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack/lobster'
4
-
5
- use Rack::ShowExceptions
6
- use Rack::Auth::Basic, "Lobster 2.0" do |username, password|
7
- Rack::Utils.secure_compare('secret', password)
8
- end
9
-
10
- run Rack::Lobster.new
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../abstract/handler'
4
- require_relative 'request'
5
- require_relative 'params'
6
- require_relative 'nonce'
7
- require 'digest/md5'
8
-
9
- module Rack
10
- module Auth
11
- module Digest
12
- # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
13
- # HTTP Digest Authentication, as per RFC 2617.
14
- #
15
- # Initialize with the [Rack] application that you want protecting,
16
- # and a block that looks up a plaintext password for a given username.
17
- #
18
- # +opaque+ needs to be set to a constant base64/hexadecimal string.
19
- #
20
- class MD5 < AbstractHandler
21
-
22
- attr_accessor :opaque
23
-
24
- attr_writer :passwords_hashed
25
-
26
- def initialize(app, realm = nil, opaque = nil, &authenticator)
27
- @passwords_hashed = nil
28
- if opaque.nil? and realm.respond_to? :values_at
29
- realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
30
- end
31
- super(app, realm, &authenticator)
32
- @opaque = opaque
33
- end
34
-
35
- def passwords_hashed?
36
- !!@passwords_hashed
37
- end
38
-
39
- def call(env)
40
- auth = Request.new(env)
41
-
42
- unless auth.provided?
43
- return unauthorized
44
- end
45
-
46
- if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
47
- return bad_request
48
- end
49
-
50
- if valid?(auth)
51
- if auth.nonce.stale?
52
- return unauthorized(challenge(stale: true))
53
- else
54
- env['REMOTE_USER'] = auth.username
55
-
56
- return @app.call(env)
57
- end
58
- end
59
-
60
- unauthorized
61
- end
62
-
63
-
64
- private
65
-
66
- QOP = 'auth'
67
-
68
- def params(hash = {})
69
- Params.new do |params|
70
- params['realm'] = realm
71
- params['nonce'] = Nonce.new.to_s
72
- params['opaque'] = H(opaque)
73
- params['qop'] = QOP
74
-
75
- hash.each { |k, v| params[k] = v }
76
- end
77
- end
78
-
79
- def challenge(hash = {})
80
- "Digest #{params(hash)}"
81
- end
82
-
83
- def valid?(auth)
84
- valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
85
- end
86
-
87
- def valid_qop?(auth)
88
- QOP == auth.qop
89
- end
90
-
91
- def valid_opaque?(auth)
92
- H(opaque) == auth.opaque
93
- end
94
-
95
- def valid_nonce?(auth)
96
- auth.nonce.valid?
97
- end
98
-
99
- def valid_digest?(auth)
100
- pw = @authenticator.call(auth.username)
101
- pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response)
102
- end
103
-
104
- def md5(data)
105
- ::Digest::MD5.hexdigest(data)
106
- end
107
-
108
- alias :H :md5
109
-
110
- def KD(secret, data)
111
- H "#{secret}:#{data}"
112
- end
113
-
114
- def A1(auth, password)
115
- "#{auth.username}:#{auth.realm}:#{password}"
116
- end
117
-
118
- def A2(auth)
119
- "#{auth.method}:#{auth.uri}"
120
- end
121
-
122
- def digest(auth, password)
123
- password_hash = passwords_hashed? ? password : H(A1(auth, password))
124
-
125
- KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
126
- end
127
-
128
- end
129
- end
130
- end
131
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'digest/md5'
4
-
5
- module Rack
6
- module Auth
7
- module Digest
8
- # Rack::Auth::Digest::Nonce is the default nonce generator for the
9
- # Rack::Auth::Digest::MD5 authentication handler.
10
- #
11
- # +private_key+ needs to set to a constant string.
12
- #
13
- # +time_limit+ can be optionally set to an integer (number of seconds),
14
- # to limit the validity of the generated nonces.
15
-
16
- class Nonce
17
-
18
- class << self
19
- attr_accessor :private_key, :time_limit
20
- end
21
-
22
- def self.parse(string)
23
- new(*string.unpack("m").first.split(' ', 2))
24
- end
25
-
26
- def initialize(timestamp = Time.now, given_digest = nil)
27
- @timestamp, @given_digest = timestamp.to_i, given_digest
28
- end
29
-
30
- def to_s
31
- ["#{@timestamp} #{digest}"].pack("m").strip
32
- end
33
-
34
- def digest
35
- ::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}")
36
- end
37
-
38
- def valid?
39
- digest == @given_digest
40
- end
41
-
42
- def stale?
43
- !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
44
- end
45
-
46
- def fresh?
47
- !stale?
48
- end
49
-
50
- end
51
- end
52
- end
53
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rack
4
- module Auth
5
- module Digest
6
- class Params < Hash
7
-
8
- def self.parse(str)
9
- Params[*split_header_value(str).map do |param|
10
- k, v = param.split('=', 2)
11
- [k, dequote(v)]
12
- end.flatten]
13
- end
14
-
15
- def self.dequote(str) # From WEBrick::HTTPUtils
16
- ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
17
- ret.gsub!(/\\(.)/, "\\1")
18
- ret
19
- end
20
-
21
- def self.split_header_value(str)
22
- str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
23
- end
24
-
25
- def initialize
26
- super()
27
-
28
- yield self if block_given?
29
- end
30
-
31
- def [](k)
32
- super k.to_s
33
- end
34
-
35
- def []=(k, v)
36
- super k.to_s, v.to_s
37
- end
38
-
39
- UNQUOTED = ['nc', 'stale']
40
-
41
- def to_s
42
- map do |k, v|
43
- "#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
44
- end.join(', ')
45
- end
46
-
47
- def quote(str) # From WEBrick::HTTPUtils
48
- '"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
49
- end
50
-
51
- end
52
- end
53
- end
54
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../abstract/request'
4
- require_relative 'params'
5
- require_relative 'nonce'
6
-
7
- module Rack
8
- module Auth
9
- module Digest
10
- class Request < Auth::AbstractRequest
11
- def method
12
- @env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD]
13
- end
14
-
15
- def digest?
16
- "digest" == scheme
17
- end
18
-
19
- def correct_uri?
20
- request.fullpath == uri
21
- end
22
-
23
- def nonce
24
- @nonce ||= Nonce.parse(params['nonce'])
25
- end
26
-
27
- def params
28
- @params ||= Params.parse(parts.last)
29
- end
30
-
31
- def respond_to?(sym, *)
32
- super or params.has_key? sym.to_s
33
- end
34
-
35
- def method_missing(sym, *args)
36
- return super unless params.has_key?(key = sym.to_s)
37
- return params[key] if args.size == 0
38
- raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
39
- end
40
- end
41
- end
42
- end
43
- end