rack 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

@@ -511,6 +511,23 @@ run on port 11211) and memcache-client installed.
511
511
  * Added hash-like methods to Abstract::ID::SessionHash for compatibility
512
512
  * Various documentation corrections
513
513
 
514
+ * February 7th, Thirty fifth public release 1.1.6, 1.2.8, 1.3.10
515
+ * Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
516
+
517
+ * February 7th, Thirty fifth public release 1.4.5
518
+ * Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
519
+ * Fix CVE-2013-0262, symlink path traversal in Rack::File
520
+
521
+ * February 7th, Thirty fifth public release 1.5.2
522
+ * Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
523
+ * Fix CVE-2013-0262, symlink path traversal in Rack::File
524
+ * Add various methods to Session for enhanced Rails compatibility
525
+ * Request#trusted_proxy? now only matches whole stirngs
526
+ * Add JSON cookie coder, to be default in Rack 1.6+ due to security concerns
527
+ * URLMap host matching in environments that don't set the Host header fixed
528
+ * Fix a race condition that could result in overwritten pidfiles
529
+ * Various documentation additions
530
+
514
531
  == Contact
515
532
 
516
533
  Please post bugs, suggestions and patches to
@@ -80,4 +80,8 @@ module Rack
80
80
  autoload :Pool, "rack/session/pool"
81
81
  autoload :Memcache, "rack/session/memcache"
82
82
  end
83
+
84
+ module Utils
85
+ autoload :OkJson, "rack/utils/okjson"
86
+ end
83
87
  end
@@ -13,7 +13,7 @@ module Rack
13
13
  # a conditional GET matches.
14
14
  #
15
15
  # Adapted from Michael Klishin's Merb implementation:
16
- # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
16
+ # https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb
17
17
  class ConditionalGet
18
18
  def initialize(app)
19
19
  @app = app
@@ -4,6 +4,18 @@ require "time" # for Time.httpdate
4
4
  require 'rack/utils'
5
5
 
6
6
  module Rack
7
+ # This middleware enables compression of http responses.
8
+ #
9
+ # Currently supported compression algorithms:
10
+ #
11
+ # * gzip
12
+ # * deflate
13
+ # * identity (no transformation)
14
+ #
15
+ # The middleware automatically detects when compression is supported
16
+ # and allowed. For example no transformation is made when a cache
17
+ # directive of 'no-transform' is present, or when the response status
18
+ # code is one that doesn't allow an entity body.
7
19
  class Deflater
8
20
  def initialize(app)
9
21
  @app = app
@@ -41,19 +41,14 @@ module Rack
41
41
  path_info = Utils.unescape(env["PATH_INFO"])
42
42
  parts = path_info.split SEPS
43
43
 
44
- parts.inject(0) do |depth, part|
45
- case part
46
- when '', '.'
47
- depth
48
- when '..'
49
- return fail(404, "Not Found") if depth - 1 < 0
50
- depth - 1
51
- else
52
- depth + 1
53
- end
44
+ clean = []
45
+
46
+ parts.each do |part|
47
+ next if part.empty? || part == '.'
48
+ part == '..' ? clean.pop : clean << part
54
49
  end
55
50
 
56
- @path = F.join(@root, *parts)
51
+ @path = F.join(@root, *clean)
57
52
 
58
53
  available = begin
59
54
  F.file?(@path) && F.readable?(@path)
@@ -340,7 +340,7 @@ module Rack
340
340
  end
341
341
 
342
342
  def trusted_proxy?(ip)
343
- ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$|^unix$|^unix:/i
343
+ ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
344
344
  end
345
345
 
346
346
  def ip
@@ -329,8 +329,11 @@ module Rack
329
329
  end
330
330
 
331
331
  def write_pid
332
- ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
332
+ ::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") }
333
333
  at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
334
+ rescue Errno::EEXIST
335
+ check_pid!
336
+ retry
334
337
  end
335
338
 
336
339
  def check_pid!
@@ -24,6 +24,14 @@ module Rack
24
24
  include Enumerable
25
25
  attr_writer :id
26
26
 
27
+ def self.find(env)
28
+ env[ENV_SESSION_KEY]
29
+ end
30
+
31
+ def self.set(env, session)
32
+ env[ENV_SESSION_KEY] = session
33
+ end
34
+
27
35
  def self.set_options(env, options)
28
36
  env[ENV_SESSION_OPTIONS_KEY] = options.dup
29
37
  end
@@ -65,6 +65,19 @@ module Rack
65
65
  ::Marshal.load(super(str)) rescue nil
66
66
  end
67
67
  end
68
+
69
+ # N.B. Unlike other encoding methods, the contained objects must be a
70
+ # valid JSON composite type, either a Hash or an Array.
71
+ class JSON < Base64
72
+ def encode(obj)
73
+ super(::Rack::Utils::OkJson.encode(obj))
74
+ end
75
+
76
+ def decode(str)
77
+ return unless str
78
+ ::Rack::Utils::OkJson.decode(super(str)) rescue nil
79
+ end
80
+ end
68
81
  end
69
82
 
70
83
  # Use no encoding for session cookies
@@ -152,7 +165,7 @@ module Rack
152
165
  def digest_match?(data, digest)
153
166
  return unless data && digest
154
167
  @secrets.any? do |secret|
155
- digest == generate_hmac(data, secret)
168
+ Rack::Utils.secure_compare(digest, generate_hmac(data, secret))
156
169
  end
157
170
  end
158
171
 
@@ -13,6 +13,7 @@ module Rack
13
13
 
14
14
  class URLMap
15
15
  NEGATIVE_INFINITY = -1.0 / 0.0
16
+ INFINITY = 1.0 / 0.0
16
17
 
17
18
  def initialize(map = {})
18
19
  remap(map)
@@ -35,7 +36,7 @@ module Rack
35
36
 
36
37
  [host, location, match, app]
37
38
  }.sort_by do |(host, location, _, _)|
38
- [host ? -host.size : NEGATIVE_INFINITY, -location.size]
39
+ [host ? -host.size : INFINITY, -location.size]
39
40
  end
40
41
  end
41
42
 
@@ -395,6 +395,18 @@ module Rack
395
395
  end
396
396
  module_function :byte_ranges
397
397
 
398
+ # Constant time string comparison.
399
+ def secure_compare(a, b)
400
+ return false unless bytesize(a) == bytesize(b)
401
+
402
+ l = a.unpack("C*")
403
+
404
+ r, i = 0, -1
405
+ b.each_byte { |v| r |= v ^ l[i+=1] }
406
+ r == 0
407
+ end
408
+ module_function :secure_compare
409
+
398
410
  # Context allows the use of a compatible middleware at different points
399
411
  # in a request handling stack. A compatible middleware must define
400
412
  # #context which should take the arguments env and app. The first of which
@@ -0,0 +1,599 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2011, 2012 Keith Rarick
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ # See https://github.com/kr/okjson for updates.
24
+ # Imported from the above repo @ d4e8643ad92e14b37d11326855499c7e4108ed17
25
+ # Namespace modified for vendoring under Rack::Utils
26
+
27
+ require 'stringio'
28
+
29
+ # Some parts adapted from
30
+ # http://golang.org/src/pkg/json/decode.go and
31
+ # http://golang.org/src/pkg/utf8/utf8.go
32
+ module Rack::Utils::OkJson
33
+ Upstream = 'LTD7LBKLZWFF7OZK'
34
+ extend self
35
+
36
+
37
+ # Decodes a json document in string s and
38
+ # returns the corresponding ruby value.
39
+ # String s must be valid UTF-8. If you have
40
+ # a string in some other encoding, convert
41
+ # it first.
42
+ #
43
+ # String values in the resulting structure
44
+ # will be UTF-8.
45
+ def decode(s)
46
+ ts = lex(s)
47
+ v, ts = textparse(ts)
48
+ if ts.length > 0
49
+ raise Error, 'trailing garbage'
50
+ end
51
+ v
52
+ end
53
+
54
+
55
+ # Parses a "json text" in the sense of RFC 4627.
56
+ # Returns the parsed value and any trailing tokens.
57
+ # Note: this is almost the same as valparse,
58
+ # except that it does not accept atomic values.
59
+ def textparse(ts)
60
+ if ts.length < 0
61
+ raise Error, 'empty'
62
+ end
63
+
64
+ typ, _, val = ts[0]
65
+ case typ
66
+ when '{' then objparse(ts)
67
+ when '[' then arrparse(ts)
68
+ else
69
+ raise Error, "unexpected #{val.inspect}"
70
+ end
71
+ end
72
+
73
+
74
+ # Parses a "value" in the sense of RFC 4627.
75
+ # Returns the parsed value and any trailing tokens.
76
+ def valparse(ts)
77
+ if ts.length < 0
78
+ raise Error, 'empty'
79
+ end
80
+
81
+ typ, _, val = ts[0]
82
+ case typ
83
+ when '{' then objparse(ts)
84
+ when '[' then arrparse(ts)
85
+ when :val,:str then [val, ts[1..-1]]
86
+ else
87
+ raise Error, "unexpected #{val.inspect}"
88
+ end
89
+ end
90
+
91
+
92
+ # Parses an "object" in the sense of RFC 4627.
93
+ # Returns the parsed value and any trailing tokens.
94
+ def objparse(ts)
95
+ ts = eat('{', ts)
96
+ obj = {}
97
+
98
+ if ts[0][0] == '}'
99
+ return obj, ts[1..-1]
100
+ end
101
+
102
+ k, v, ts = pairparse(ts)
103
+ obj[k] = v
104
+
105
+ if ts[0][0] == '}'
106
+ return obj, ts[1..-1]
107
+ end
108
+
109
+ loop do
110
+ ts = eat(',', ts)
111
+
112
+ k, v, ts = pairparse(ts)
113
+ obj[k] = v
114
+
115
+ if ts[0][0] == '}'
116
+ return obj, ts[1..-1]
117
+ end
118
+ end
119
+ end
120
+
121
+
122
+ # Parses a "member" in the sense of RFC 4627.
123
+ # Returns the parsed values and any trailing tokens.
124
+ def pairparse(ts)
125
+ (typ, _, k), ts = ts[0], ts[1..-1]
126
+ if typ != :str
127
+ raise Error, "unexpected #{k.inspect}"
128
+ end
129
+ ts = eat(':', ts)
130
+ v, ts = valparse(ts)
131
+ [k, v, ts]
132
+ end
133
+
134
+
135
+ # Parses an "array" in the sense of RFC 4627.
136
+ # Returns the parsed value and any trailing tokens.
137
+ def arrparse(ts)
138
+ ts = eat('[', ts)
139
+ arr = []
140
+
141
+ if ts[0][0] == ']'
142
+ return arr, ts[1..-1]
143
+ end
144
+
145
+ v, ts = valparse(ts)
146
+ arr << v
147
+
148
+ if ts[0][0] == ']'
149
+ return arr, ts[1..-1]
150
+ end
151
+
152
+ loop do
153
+ ts = eat(',', ts)
154
+
155
+ v, ts = valparse(ts)
156
+ arr << v
157
+
158
+ if ts[0][0] == ']'
159
+ return arr, ts[1..-1]
160
+ end
161
+ end
162
+ end
163
+
164
+
165
+ def eat(typ, ts)
166
+ if ts[0][0] != typ
167
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
168
+ end
169
+ ts[1..-1]
170
+ end
171
+
172
+
173
+ # Scans s and returns a list of json tokens,
174
+ # excluding white space (as defined in RFC 4627).
175
+ def lex(s)
176
+ ts = []
177
+ while s.length > 0
178
+ typ, lexeme, val = tok(s)
179
+ if typ == nil
180
+ raise Error, "invalid character at #{s[0,10].inspect}"
181
+ end
182
+ if typ != :space
183
+ ts << [typ, lexeme, val]
184
+ end
185
+ s = s[lexeme.length..-1]
186
+ end
187
+ ts
188
+ end
189
+
190
+
191
+ # Scans the first token in s and
192
+ # returns a 3-element list, or nil
193
+ # if s does not begin with a valid token.
194
+ #
195
+ # The first list element is one of
196
+ # '{', '}', ':', ',', '[', ']',
197
+ # :val, :str, and :space.
198
+ #
199
+ # The second element is the lexeme.
200
+ #
201
+ # The third element is the value of the
202
+ # token for :val and :str, otherwise
203
+ # it is the lexeme.
204
+ def tok(s)
205
+ case s[0]
206
+ when ?{ then ['{', s[0,1], s[0,1]]
207
+ when ?} then ['}', s[0,1], s[0,1]]
208
+ when ?: then [':', s[0,1], s[0,1]]
209
+ when ?, then [',', s[0,1], s[0,1]]
210
+ when ?[ then ['[', s[0,1], s[0,1]]
211
+ when ?] then [']', s[0,1], s[0,1]]
212
+ when ?n then nulltok(s)
213
+ when ?t then truetok(s)
214
+ when ?f then falsetok(s)
215
+ when ?" then strtok(s)
216
+ when Spc then [:space, s[0,1], s[0,1]]
217
+ when ?\t then [:space, s[0,1], s[0,1]]
218
+ when ?\n then [:space, s[0,1], s[0,1]]
219
+ when ?\r then [:space, s[0,1], s[0,1]]
220
+ else numtok(s)
221
+ end
222
+ end
223
+
224
+
225
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
226
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
227
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
228
+
229
+
230
+ def numtok(s)
231
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
232
+ if m && m.begin(0) == 0
233
+ if m[3] && !m[2]
234
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
235
+ elsif m[2]
236
+ [:val, m[0], Float(m[0])]
237
+ else
238
+ [:val, m[0], Integer(m[0])]
239
+ end
240
+ else
241
+ []
242
+ end
243
+ end
244
+
245
+
246
+ def strtok(s)
247
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
248
+ if ! m
249
+ raise Error, "invalid string literal at #{abbrev(s)}"
250
+ end
251
+ [:str, m[0], unquote(m[0])]
252
+ end
253
+
254
+
255
+ def abbrev(s)
256
+ t = s[0,10]
257
+ p = t['`']
258
+ t = t[0,p] if p
259
+ t = t + '...' if t.length < s.length
260
+ '`' + t + '`'
261
+ end
262
+
263
+
264
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
265
+ # The rules are different than for Ruby, so we cannot use eval.
266
+ # Unquote will raise an error if q contains control characters.
267
+ def unquote(q)
268
+ q = q[1...-1]
269
+ a = q.dup # allocate a big enough string
270
+ rubydoesenc = false
271
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
272
+ if a.class.method_defined?(:force_encoding)
273
+ a.force_encoding('UTF-8')
274
+ rubydoesenc = true
275
+ end
276
+ r, w = 0, 0
277
+ while r < q.length
278
+ c = q[r]
279
+ case true
280
+ when c == ?\\
281
+ r += 1
282
+ if r >= q.length
283
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
284
+ end
285
+
286
+ case q[r]
287
+ when ?",?\\,?/,?'
288
+ a[w] = q[r]
289
+ r += 1
290
+ w += 1
291
+ when ?b,?f,?n,?r,?t
292
+ a[w] = Unesc[q[r]]
293
+ r += 1
294
+ w += 1
295
+ when ?u
296
+ r += 1
297
+ uchar = begin
298
+ hexdec4(q[r,4])
299
+ rescue RuntimeError => e
300
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
301
+ end
302
+ r += 4
303
+ if surrogate? uchar
304
+ if q.length >= r+6
305
+ uchar1 = hexdec4(q[r+2,4])
306
+ uchar = subst(uchar, uchar1)
307
+ if uchar != Ucharerr
308
+ # A valid pair; consume.
309
+ r += 6
310
+ end
311
+ end
312
+ end
313
+ if rubydoesenc
314
+ a[w] = '' << uchar
315
+ w += 1
316
+ else
317
+ w += ucharenc(a, w, uchar)
318
+ end
319
+ else
320
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
321
+ end
322
+ when c == ?", c < Spc
323
+ raise Error, "invalid character in string literal \"#{q}\""
324
+ else
325
+ # Copy anything else byte-for-byte.
326
+ # Valid UTF-8 will remain valid UTF-8.
327
+ # Invalid UTF-8 will remain invalid UTF-8.
328
+ # In ruby >= 1.9, c is a codepoint, not a byte,
329
+ # in which case this is still what we want.
330
+ a[w] = c
331
+ r += 1
332
+ w += 1
333
+ end
334
+ end
335
+ a[0,w]
336
+ end
337
+
338
+
339
+ # Encodes unicode character u as UTF-8
340
+ # bytes in string a at position i.
341
+ # Returns the number of bytes written.
342
+ def ucharenc(a, i, u)
343
+ case true
344
+ when u <= Uchar1max
345
+ a[i] = (u & 0xff).chr
346
+ 1
347
+ when u <= Uchar2max
348
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
349
+ a[i+1] = (Utagx | (u&Umaskx)).chr
350
+ 2
351
+ when u <= Uchar3max
352
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
353
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
354
+ a[i+2] = (Utagx | (u&Umaskx)).chr
355
+ 3
356
+ else
357
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
358
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
359
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
360
+ a[i+3] = (Utagx | (u&Umaskx)).chr
361
+ 4
362
+ end
363
+ end
364
+
365
+
366
+ def hexdec4(s)
367
+ if s.length != 4
368
+ raise Error, 'short'
369
+ end
370
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
371
+ end
372
+
373
+
374
+ def subst(u1, u2)
375
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
376
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
377
+ end
378
+ return Ucharerr
379
+ end
380
+
381
+
382
+ def surrogate?(u)
383
+ Usurr1 <= u && u < Usurr3
384
+ end
385
+
386
+
387
+ def nibble(c)
388
+ case true
389
+ when ?0 <= c && c <= ?9 then c.ord - ?0.ord
390
+ when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
391
+ when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
392
+ else
393
+ raise Error, "invalid hex code #{c}"
394
+ end
395
+ end
396
+
397
+
398
+ # Encodes x into a json text. It may contain only
399
+ # Array, Hash, String, Numeric, true, false, nil.
400
+ # (Note, this list excludes Symbol.)
401
+ # X itself must be an Array or a Hash.
402
+ # No other value can be encoded, and an error will
403
+ # be raised if x contains any other value, such as
404
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
405
+ # is not a String.
406
+ # Strings contained in x must be valid UTF-8.
407
+ def encode(x)
408
+ case x
409
+ when Hash then objenc(x)
410
+ when Array then arrenc(x)
411
+ else
412
+ raise Error, 'root value must be an Array or a Hash'
413
+ end
414
+ end
415
+
416
+
417
+ def valenc(x)
418
+ case x
419
+ when Hash then objenc(x)
420
+ when Array then arrenc(x)
421
+ when String then strenc(x)
422
+ when Numeric then numenc(x)
423
+ when true then "true"
424
+ when false then "false"
425
+ when nil then "null"
426
+ else
427
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
428
+ end
429
+ end
430
+
431
+
432
+ def objenc(x)
433
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
434
+ end
435
+
436
+
437
+ def arrenc(a)
438
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
439
+ end
440
+
441
+
442
+ def keyenc(k)
443
+ case k
444
+ when String then strenc(k)
445
+ else
446
+ raise Error, "Hash key is not a string: #{k.inspect}"
447
+ end
448
+ end
449
+
450
+
451
+ def strenc(s)
452
+ t = StringIO.new
453
+ t.putc(?")
454
+ r = 0
455
+
456
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
457
+ rubydoesenc = s.class.method_defined?(:encoding)
458
+
459
+ while r < s.length
460
+ case s[r]
461
+ when ?" then t.print('\\"')
462
+ when ?\\ then t.print('\\\\')
463
+ when ?\b then t.print('\\b')
464
+ when ?\f then t.print('\\f')
465
+ when ?\n then t.print('\\n')
466
+ when ?\r then t.print('\\r')
467
+ when ?\t then t.print('\\t')
468
+ else
469
+ c = s[r]
470
+ case true
471
+ when rubydoesenc
472
+ begin
473
+ c.ord # will raise an error if c is invalid UTF-8
474
+ t.write(c)
475
+ rescue
476
+ t.write(Ustrerr)
477
+ end
478
+ when Spc <= c && c <= ?~
479
+ t.putc(c)
480
+ else
481
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
482
+ r += n - 1 # r is incremented below
483
+ end
484
+ end
485
+ r += 1
486
+ end
487
+ t.putc(?")
488
+ t.string
489
+ end
490
+
491
+
492
+ def numenc(x)
493
+ if ((x.nan? || x.infinite?) rescue false)
494
+ raise Error, "Numeric cannot be represented: #{x}"
495
+ end
496
+ "#{x}"
497
+ end
498
+
499
+
500
+ # Copies the valid UTF-8 bytes of a single character
501
+ # from string s at position i to I/O object t, and
502
+ # returns the number of bytes copied.
503
+ # If no valid UTF-8 char exists at position i,
504
+ # ucharcopy writes Ustrerr and returns 1.
505
+ def ucharcopy(t, s, i)
506
+ n = s.length - i
507
+ raise Utf8Error if n < 1
508
+
509
+ c0 = s[i].ord
510
+
511
+ # 1-byte, 7-bit sequence?
512
+ if c0 < Utagx
513
+ t.putc(c0)
514
+ return 1
515
+ end
516
+
517
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
518
+
519
+ raise Utf8Error if n < 2 # need continuation byte
520
+ c1 = s[i+1].ord
521
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
522
+
523
+ # 2-byte, 11-bit sequence?
524
+ if c0 < Utag3
525
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
526
+ t.putc(c0)
527
+ t.putc(c1)
528
+ return 2
529
+ end
530
+
531
+ # need second continuation byte
532
+ raise Utf8Error if n < 3
533
+
534
+ c2 = s[i+2].ord
535
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
536
+
537
+ # 3-byte, 16-bit sequence?
538
+ if c0 < Utag4
539
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
540
+ raise Utf8Error if u <= Uchar2max
541
+ t.putc(c0)
542
+ t.putc(c1)
543
+ t.putc(c2)
544
+ return 3
545
+ end
546
+
547
+ # need third continuation byte
548
+ raise Utf8Error if n < 4
549
+ c3 = s[i+3].ord
550
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
551
+
552
+ # 4-byte, 21-bit sequence?
553
+ if c0 < Utag5
554
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
555
+ raise Utf8Error if u <= Uchar3max
556
+ t.putc(c0)
557
+ t.putc(c1)
558
+ t.putc(c2)
559
+ t.putc(c3)
560
+ return 4
561
+ end
562
+
563
+ raise Utf8Error
564
+ rescue Utf8Error
565
+ t.write(Ustrerr)
566
+ return 1
567
+ end
568
+
569
+
570
+ class Utf8Error < ::StandardError
571
+ end
572
+
573
+
574
+ class Error < ::StandardError
575
+ end
576
+
577
+
578
+ Utagx = 0x80 # 1000 0000
579
+ Utag2 = 0xc0 # 1100 0000
580
+ Utag3 = 0xe0 # 1110 0000
581
+ Utag4 = 0xf0 # 1111 0000
582
+ Utag5 = 0xF8 # 1111 1000
583
+ Umaskx = 0x3f # 0011 1111
584
+ Umask2 = 0x1f # 0001 1111
585
+ Umask3 = 0x0f # 0000 1111
586
+ Umask4 = 0x07 # 0000 0111
587
+ Uchar1max = (1<<7) - 1
588
+ Uchar2max = (1<<11) - 1
589
+ Uchar3max = (1<<16) - 1
590
+ Ucharerr = 0xFFFD # unicode "replacement char"
591
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
592
+ Usurrself = 0x10000
593
+ Usurr1 = 0xd800
594
+ Usurr2 = 0xdc00
595
+ Usurr3 = 0xe000
596
+
597
+ Spc = ' '[0]
598
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
599
+ end
@@ -1,8 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack"
3
- s.version = "1.5.1"
3
+ s.version = "1.5.2"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.summary = "a modular Ruby webserver interface"
6
+ s.license = "MIT"
6
7
 
7
8
  s.description = <<-EOF
8
9
  Rack provides a minimal, modular and adaptable interface for developing
@@ -364,7 +364,7 @@ Content-Type: image/jpeg\r
364
364
  end
365
365
 
366
366
  it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
367
- data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
367
+ data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |f| f.read }.gsub(/\n/, "\r\n")
368
368
  options = {
369
369
  "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
370
370
  "CONTENT_LENGTH" => data.length.to_s,
@@ -1010,6 +1010,30 @@ EOF
1010
1010
  res.body.should.equal '3.4.5.6'
1011
1011
  end
1012
1012
 
1013
+ should "regard local addresses as proxies" do
1014
+ req = Rack::Request.new(Rack::MockRequest.env_for("/"))
1015
+ req.trusted_proxy?('127.0.0.1').should.equal 0
1016
+ req.trusted_proxy?('10.0.0.1').should.equal 0
1017
+ req.trusted_proxy?('172.16.0.1').should.equal 0
1018
+ req.trusted_proxy?('172.20.0.1').should.equal 0
1019
+ req.trusted_proxy?('172.30.0.1').should.equal 0
1020
+ req.trusted_proxy?('172.31.0.1').should.equal 0
1021
+ req.trusted_proxy?('192.168.0.1').should.equal 0
1022
+ req.trusted_proxy?('::1').should.equal 0
1023
+ req.trusted_proxy?('fd00::').should.equal 0
1024
+ req.trusted_proxy?('localhost').should.equal 0
1025
+ req.trusted_proxy?('unix').should.equal 0
1026
+ req.trusted_proxy?('unix:/tmp/sock').should.equal 0
1027
+
1028
+ req.trusted_proxy?("unix.example.org").should.equal nil
1029
+ req.trusted_proxy?("example.org\n127.0.0.1").should.equal nil
1030
+ req.trusted_proxy?("127.0.0.1\nexample.org").should.equal nil
1031
+ req.trusted_proxy?("11.0.0.1").should.equal nil
1032
+ req.trusted_proxy?("172.15.0.1").should.equal nil
1033
+ req.trusted_proxy?("172.32.0.1").should.equal nil
1034
+ req.trusted_proxy?("2001:470:1f0b:18f8::1").should.equal nil
1035
+ end
1036
+
1013
1037
  class MyRequest < Rack::Request
1014
1038
  def params
1015
1039
  {:foo => "bar"}
@@ -35,7 +35,7 @@ describe Rack::Sendfile do
35
35
  unless method_defined?(:to_path)
36
36
  alias :to_path :path
37
37
  end
38
- end.open(path, 'w+')
38
+ end.open(path, 'wb+')
39
39
  end
40
40
 
41
41
  it "does nothing when no X-Sendfile-Type header present" do
@@ -110,6 +110,22 @@ describe Rack::Server do
110
110
  server.send(:pidfile_process_status).should.eql :not_owned
111
111
  end
112
112
 
113
+ should "not write pid file when it is created after check" do
114
+ pidfile = Tempfile.open('pidfile') { |f| break f }.path
115
+ ::File.delete(pidfile)
116
+ server = Rack::Server.new(:pid => pidfile)
117
+ ::File.open(pidfile, 'w') { |f| f.write(1) }
118
+ with_stderr do |err|
119
+ should.raise(SystemExit) do
120
+ server.send(:write_pid)
121
+ end
122
+ err.rewind
123
+ output = err.read
124
+ output.should.match(/already running/)
125
+ output.should.include? pidfile
126
+ end
127
+ end
128
+
113
129
  should "inform the user about existing pidfiles with running processes" do
114
130
  pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
115
131
  server = Rack::Server.new(:pid => pidfile)
@@ -100,6 +100,25 @@ describe Rack::Session::Cookie do
100
100
  coder.decode('lulz').should.equal nil
101
101
  end
102
102
  end
103
+
104
+ describe 'JSON' do
105
+ it 'marshals and base64 encodes' do
106
+ coder = Rack::Session::Cookie::Base64::JSON.new
107
+ obj = %w[fuuuuu]
108
+ coder.encode(obj).should.equal [::Rack::Utils::OkJson.encode(obj)].pack('m')
109
+ end
110
+
111
+ it 'marshals and base64 decodes' do
112
+ coder = Rack::Session::Cookie::Base64::JSON.new
113
+ str = [::Rack::Utils::OkJson.encode(%w[fuuuuu])].pack('m')
114
+ coder.decode(str).should.equal ::Rack::Utils::OkJson.decode(str.unpack('m').first)
115
+ end
116
+
117
+ it 'rescues failures on decode' do
118
+ coder = Rack::Session::Cookie::Base64::JSON.new
119
+ coder.decode('lulz').should.equal nil
120
+ end
121
+ end
103
122
  end
104
123
 
105
124
  it "warns if no secret is given" do
@@ -110,7 +110,7 @@ describe Rack::URLMap do
110
110
 
111
111
  res = Rack::MockRequest.new(map).get("http://foo.org/")
112
112
  res.should.be.ok
113
- res["X-Position"].should.equal "default.org"
113
+ res["X-Position"].should.equal "foo.org"
114
114
 
115
115
  res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org")
116
116
  res.should.be.ok
@@ -354,6 +354,11 @@ describe Rack::Utils do
354
354
  Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
355
355
  end
356
356
 
357
+ should "should perform constant time string comparison" do
358
+ Rack::Utils.secure_compare('a', 'a').should.equal true
359
+ Rack::Utils.secure_compare('a', 'b').should.equal false
360
+ end
361
+
357
362
  should "return status code for integer" do
358
363
  Rack::Utils.status_code(200).should.equal 200
359
364
  end
metadata CHANGED
@@ -1,70 +1,68 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rack
3
- version: !ruby/object:Gem::Version
4
- version: 1.5.1
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
5
  prerelease:
6
+ segments:
7
+ - 1
8
+ - 5
9
+ - 2
10
+ version: 1.5.2
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Christian Neukirchen
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-01-28 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2013-02-08 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: bacon
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :development
23
22
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: rake
32
- requirement: !ruby/object:Gem::Requirement
23
+ requirement: &id001 !ruby/object:Gem::Requirement
33
24
  none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: '0'
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
38
32
  type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
39
36
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
37
+ requirement: &id002 !ruby/object:Gem::Requirement
41
38
  none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
46
- description: ! 'Rack provides a minimal, modular and adaptable interface for developing
47
-
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ description: |
49
+ Rack provides a minimal, modular and adaptable interface for developing
48
50
  web applications in Ruby. By wrapping HTTP requests and responses in
49
-
50
51
  the simplest way possible, it unifies and distills the API for web
51
-
52
52
  servers, web frameworks, and software in between (the so-called
53
-
54
53
  middleware) into a single method call.
55
-
56
-
54
+
57
55
  Also see http://rack.github.com/.
58
56
 
59
- '
60
57
  email: chneukirchen@gmail.com
61
- executables:
58
+ executables:
62
59
  - rackup
63
60
  extensions: []
64
- extra_rdoc_files:
61
+
62
+ extra_rdoc_files:
65
63
  - README.rdoc
66
64
  - KNOWN-ISSUES
67
- files:
65
+ files:
68
66
  - bin/rackup
69
67
  - contrib/rack.png
70
68
  - contrib/rack.svg
@@ -135,6 +133,7 @@ files:
135
133
  - lib/rack/showstatus.rb
136
134
  - lib/rack/static.rb
137
135
  - lib/rack/urlmap.rb
136
+ - lib/rack/utils/okjson.rb
138
137
  - lib/rack/utils.rb
139
138
  - lib/rack.rb
140
139
  - test/builder/anything.rb
@@ -238,30 +237,39 @@ files:
238
237
  - README.rdoc
239
238
  - SPEC
240
239
  homepage: http://rack.github.com/
241
- licenses: []
240
+ licenses:
241
+ - MIT
242
242
  post_install_message:
243
243
  rdoc_options: []
244
- require_paths:
244
+
245
+ require_paths:
245
246
  - lib
246
- required_ruby_version: !ruby/object:Gem::Requirement
247
+ required_ruby_version: !ruby/object:Gem::Requirement
247
248
  none: false
248
- requirements:
249
- - - ! '>='
250
- - !ruby/object:Gem::Version
251
- version: '0'
252
- required_rubygems_version: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - ">="
251
+ - !ruby/object:Gem::Version
252
+ hash: 3
253
+ segments:
254
+ - 0
255
+ version: "0"
256
+ required_rubygems_version: !ruby/object:Gem::Requirement
253
257
  none: false
254
- requirements:
255
- - - ! '>='
256
- - !ruby/object:Gem::Version
257
- version: '0'
258
+ requirements:
259
+ - - ">="
260
+ - !ruby/object:Gem::Version
261
+ hash: 3
262
+ segments:
263
+ - 0
264
+ version: "0"
258
265
  requirements: []
266
+
259
267
  rubyforge_project: rack
260
- rubygems_version: 1.8.23
268
+ rubygems_version: 1.8.24
261
269
  signing_key:
262
270
  specification_version: 3
263
271
  summary: a modular Ruby webserver interface
264
- test_files:
272
+ test_files:
265
273
  - test/spec_auth_basic.rb
266
274
  - test/spec_auth_digest.rb
267
275
  - test/spec_body_proxy.rb
@@ -309,3 +317,4 @@ test_files:
309
317
  - test/spec_urlmap.rb
310
318
  - test/spec_utils.rb
311
319
  - test/spec_webrick.rb
320
+ has_rdoc: