Skip to content
This repository
Browse code

Merge branch 'master' of github.com:rack/rack

  • Loading branch information...
commit d084a14fc5be6587fb1720a4f126bae2c7e74d10 2 parents 1038c7e + b83d7be
Konstantin Haase authored August 29, 2012
2  COPYING
... ...
@@ -1,4 +1,4 @@
1  
-Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
  1
+Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012 Christian Neukirchen <purl.org/net/chneukirchen>
2 2
 
3 3
 Permission is hereby granted, free of charge, to any person obtaining a copy
4 4
 of this software and associated documentation files (the "Software"), to
2  lib/rack/auth/digest/nonce.rb
@@ -38,7 +38,7 @@ def valid?
38 38
         end
39 39
 
40 40
         def stale?
41  
-          !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit
  41
+          !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
42 42
         end
43 43
 
44 44
         def fresh?
41  lib/rack/backports/uri/common_192.rb
@@ -17,21 +17,18 @@
17 17
 require 'uri/common'
18 18
 
19 19
 module URI
20  
-  256.times do |i|
21  
-    TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
22  
-  end
23  
-  TBLENCWWWCOMP_[' '] = '+'
24  
-  TBLENCWWWCOMP_.freeze
25  
-
26  
-  256.times do |i|
27  
-    h, l = i>>4, i&15
28  
-    TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
29  
-    TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
30  
-    TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
31  
-    TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
  20
+  TBLDECWWWCOMP_ = {} unless const_defined?(:TBLDECWWWCOMP_)  #:nodoc:
  21
+  if TBLDECWWWCOMP_.empty?
  22
+    256.times do |i|
  23
+      h, l = i>>4, i&15
  24
+      TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
  25
+      TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
  26
+      TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
  27
+      TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
  28
+    end
  29
+    TBLDECWWWCOMP_['+'] = ' '
  30
+    TBLDECWWWCOMP_.freeze
32 31
   end
33  
-  TBLDECWWWCOMP_['+'] = ' '
34  
-  TBLDECWWWCOMP_.freeze
35 32
 
36 33
   def self.decode_www_form(str, enc=Encoding::UTF_8)
37 34
     return [] if str.empty?
@@ -46,22 +43,6 @@ def self.decode_www_form(str, enc=Encoding::UTF_8)
46 43
   end
47 44
 
48 45
   def self.decode_www_form_component(str, enc=Encoding::UTF_8)
49  
-    if TBLDECWWWCOMP_.empty?
50  
-      tbl = {}
51  
-      256.times do |i|
52  
-        h, l = i>>4, i&15
53  
-        tbl['%%%X%X' % [h, l]] = i.chr
54  
-        tbl['%%%x%X' % [h, l]] = i.chr
55  
-        tbl['%%%X%x' % [h, l]] = i.chr
56  
-        tbl['%%%x%x' % [h, l]] = i.chr
57  
-      end
58  
-      tbl['+'] = ' '
59  
-      begin
60  
-        TBLDECWWWCOMP_.replace(tbl)
61  
-        TBLDECWWWCOMP_.freeze
62  
-      rescue
63  
-      end
64  
-    end
65 46
     raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
66 47
     str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
67 48
   end
2  lib/rack/builder.rb
@@ -38,7 +38,7 @@ def self.parse_file(config, opts = Server::Options.new)
38 38
         end
39 39
         cfgfile.sub!(/^__END__\n.*\Z/m, '')
40 40
         app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
41  
-          TOPLEVEL_BINDING, config
  41
+          TOPLEVEL_BINDING, config, 0
42 42
       else
43 43
         require config
44 44
         app = Object.const_get(::File.basename(config, '.rb').capitalize)
2  lib/rack/directory.rb
@@ -80,7 +80,7 @@ def list_directory
80 80
       @files = [['../','Parent Directory','','','']]
81 81
       glob = F.join(@path, '*')
82 82
 
83  
-      url_head = ([@script_name] + @path_info.split('/')).map do |part|
  83
+      url_head = (@script_name.split('/') + @path_info.split('/')).map do |part|
84 84
         Rack::Utils.escape part
85 85
       end
86 86
 
7  lib/rack/etag.rb
@@ -28,8 +28,11 @@ def call(env)
28 28
       end
29 29
 
30 30
       unless headers['Cache-Control']
31  
-        headers['Cache-Control'] =
32  
-          (digest ? @cache_control : @no_cache_control) || ""
  31
+        if digest
  32
+          headers['Cache-Control'] = @cache_control if @cache_control
  33
+        else
  34
+          headers['Cache-Control'] = @no_cache_control if @no_cache_control
  35
+        end
33 36
       end
34 37
 
35 38
       [status, headers, body]
15  lib/rack/file.rb
@@ -21,9 +21,16 @@ class File
21 21
 
22 22
     alias :to_path :path
23 23
 
24  
-    def initialize(root, cache_control = nil)
  24
+    def initialize(root, headers={})
25 25
       @root = root
26  
-      @cache_control = cache_control
  26
+      # Allow a cache_control string for backwards compatibility
  27
+      if headers.instance_of? String
  28
+        warn \
  29
+          "Rack::File headers parameter replaces cache_control after Rack 1.5."
  30
+        @headers = { 'Cache-Control' => headers }
  31
+      else
  32
+        @headers = headers
  33
+      end
27 34
     end
28 35
 
29 36
     def call(env)
@@ -78,7 +85,9 @@ def serving(env)
78 85
         },
79 86
         env["REQUEST_METHOD"] == "HEAD" ? [] : self
80 87
       ]
81  
-      response[1]['Cache-Control'] = @cache_control if @cache_control
  88
+
  89
+      # Set custom headers
  90
+      @headers.each { |field, content| response[1][field] = content } if @headers
82 91
 
83 92
       # NOTE:
84 93
       #   We check via File::size? whether this file provides size info
28  lib/rack/server.rb
@@ -247,11 +247,14 @@ def start &blk
247 247
         pp app
248 248
       end
249 249
 
  250
+      check_pid! if options[:pid]
  251
+
250 252
       # Touch the wrapped app, so that the config.ru is loaded before
251 253
       # daemonization (i.e. before chdir, etc).
252 254
       wrapped_app
253 255
 
254 256
       daemonize_app if options[:daemonize]
  257
+
255 258
       write_pid if options[:pid]
256 259
 
257 260
       trap(:INT) do
@@ -274,7 +277,7 @@ def parse_options(args)
274 277
         options = default_options
275 278
 
276 279
         # Don't evaluate CGI ISINDEX parameters.
277  
-        # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
  280
+        # http://www.meb.uni-bonn.de/docs/cgi/cl.html
278 281
         args.clear if ENV.include?("REQUEST_METHOD")
279 282
 
280 283
         options.merge! opt_parser.parse!(args)
@@ -319,5 +322,28 @@ def write_pid
319 322
         ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
320 323
         at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
321 324
       end
  325
+
  326
+      def check_pid!
  327
+        case pidfile_process_status
  328
+        when :running, :not_owned
  329
+          $stderr.puts "A server is already running. Check #{options[:pid]}."
  330
+          exit(1)
  331
+        when :dead
  332
+          ::File.delete(options[:pid])
  333
+        end
  334
+      end
  335
+
  336
+      def pidfile_process_status
  337
+        return :exited unless ::File.exist?(options[:pid])
  338
+
  339
+        pid = ::File.read(options[:pid]).to_i
  340
+        Process.kill(0, pid)
  341
+        :running
  342
+      rescue Errno::ESRCH
  343
+        :dead
  344
+      rescue Errno::EPERM
  345
+        :not_owned
  346
+      end
  347
+
322 348
   end
323 349
 end
5  lib/rack/session/abstract/id.rb
@@ -120,6 +120,11 @@ def empty?
120 120
           super
121 121
         end
122 122
 
  123
+        def merge!(hash)
  124
+          load_for_write!
  125
+          super
  126
+        end
  127
+
123 128
       private
124 129
 
125 130
         def load_for_read!
17  lib/rack/static.rb
@@ -32,6 +32,16 @@ module Rack
32 32
   #
33 33
   #     use Rack::Static, :root => 'public', :cache_control => 'public'
34 34
   #
  35
+  # Set custom HTTP Headers for all served files:
  36
+  #
  37
+  #     use Rack::Static, :root => 'public', :headers =>
  38
+  #         {'Cache-Control' => 'public, max-age=31536000',
  39
+  #         'Access-Control-Allow-Origin' => '*'}
  40
+  #
  41
+  #     Note: If both :headers => {'Cache-Control' => 'public, max-age=42'}
  42
+  #           and :cache_control => 'public, max-age=38' are being provided
  43
+  #           the :headers setting takes precedence
  44
+  #
35 45
 
36 46
   class Static
37 47
 
@@ -40,8 +50,11 @@ def initialize(app, options={})
40 50
       @urls = options[:urls] || ["/favicon.ico"]
41 51
       @index = options[:index]
42 52
       root = options[:root] || Dir.pwd
43  
-      cache_control = options[:cache_control]
44  
-      @file_server = Rack::File.new(root, cache_control)
  53
+      headers = options[:headers] || {}
  54
+      # Allow for legacy :cache_control option
  55
+      # while prioritizing :headers => {'Cache-Control' => ''} settings
  56
+      headers['Cache-Control'] ||= options[:cache_control] if options[:cache_control]
  57
+      @file_server = Rack::File.new(root, headers)
45 58
     end
46 59
 
47 60
     def overwrite_file_path(path)
2  lib/rack/utils.rb
@@ -9,7 +9,7 @@
9 9
 
10 10
 if major == 1 && minor < 9
11 11
   require 'rack/backports/uri/common_18'
12  
-elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL < 318 && RUBY_ENGINE != 'jruby'
  12
+elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 320 && RUBY_ENGINE != 'jruby'
13 13
   require 'rack/backports/uri/common_192'
14 14
 elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
15 15
   require 'rack/backports/uri/common_193'
1  test/builder/line.ru
... ...
@@ -0,0 +1 @@
  1
+run lambda{ |env| [200, {'Content-Type' => 'text/plain'}, [__LINE__.to_s]] }
14  test/spec_auth_digest.rb
@@ -153,6 +153,20 @@ def assert_bad_request(response)
153 153
     end
154 154
   end
155 155
 
  156
+  should 'not rechallenge if nonce is not stale' do
  157
+    begin
  158
+      Rack::Auth::Digest::Nonce.time_limit = 10
  159
+
  160
+      request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 1 do |response|
  161
+        response.status.should.equal 200
  162
+        response.body.to_s.should.equal 'Hi Alice'
  163
+        response.headers['WWW-Authenticate'].should.not =~ /\bstale=true\b/
  164
+      end
  165
+    ensure
  166
+      Rack::Auth::Digest::Nonce.time_limit = nil
  167
+    end
  168
+  end
  169
+
156 170
   should 'rechallenge with stale parameter if nonce is stale' do
157 171
     begin
158 172
       Rack::Auth::Digest::Nonce.time_limit = 1
6  test/spec_builder.rb
@@ -197,5 +197,11 @@ def config_file(name)
197 197
       Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
198 198
       $:.pop
199 199
     end
  200
+
  201
+    it "sets __LINE__ correctly" do
  202
+      app, options = Rack::Builder.parse_file config_file('line.ru')
  203
+      options = nil # ignored, prevents warning
  204
+      Rack::MockRequest.new(app).get("/").body.to_s.should.equal '1'
  205
+    end
200 206
   end
201 207
 end
18  test/spec_directory.rb
@@ -67,4 +67,22 @@
67 67
     res = mr.get("/cgi/test%2bdirectory/test%2bfile")
68 68
     res.should.be.ok
69 69
   end
  70
+
  71
+  should "correctly escape script name" do
  72
+    app2 = Rack::Builder.new do
  73
+      map '/script-path' do
  74
+        run app
  75
+      end
  76
+    end
  77
+
  78
+    mr = Rack::MockRequest.new(Rack::Lint.new(app2))
  79
+
  80
+    res = mr.get("/script-path/cgi/test%2bdirectory")
  81
+
  82
+    res.should.be.ok
  83
+    res.body.should =~ %r[/script-path/cgi/test%2Bdirectory/test%2Bfile]
  84
+
  85
+    res = mr.get("/script-path/cgi/test%2bdirectory/test%2bfile")
  86
+    res.should.be.ok
  87
+  end
70 88
 end
6  test/spec_etag.rb
@@ -54,6 +54,12 @@ def res.to_path ; "/tmp/hello.txt" ; end
54 54
     response[1]['Cache-Control'].should.equal 'public'
55 55
   end
56 56
 
  57
+  should "not set Cache-Control if directive isn't present" do
  58
+    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
  59
+    response = etag(app, nil, nil).call(request)
  60
+    response[1]['Cache-Control'].should.equal nil
  61
+  end
  62
+
57 63
   should "not change ETag if it is already set" do
58 64
     app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
59 65
     response = etag(app).call(request)
27  test/spec_file.rb
@@ -65,13 +65,13 @@ def file(*args)
65 65
   should "not allow unsafe directory traversal" do
66 66
     req = Rack::MockRequest.new(file(DOCROOT))
67 67
 
68  
-    res = req.get("/../README")
  68
+    res = req.get("/../README.rdoc")
69 69
     res.should.be.client_error
70 70
 
71  
-    res = req.get("../test")
  71
+    res = req.get("../test/spec_file.rb")
72 72
     res.should.be.client_error
73 73
 
74  
-    res = req.get("..")
  74
+    res = req.get("../README.rdoc")
75 75
     res.should.be.client_error
76 76
 
77 77
     res.should.be.not_found
@@ -145,7 +145,7 @@ def file(*args)
145 145
     res["Content-Range"].should.equal "bytes */193"
146 146
   end
147 147
 
148  
-  should "support cache control options" do
  148
+  should "support legacy cache control options provided as string" do
149 149
     env = Rack::MockRequest.env_for("/cgi/test")
150 150
     status, heads, _ = file(DOCROOT, 'public, max-age=38').call(env)
151 151
 
@@ -153,6 +153,25 @@ def file(*args)
153 153
     heads['Cache-Control'].should.equal 'public, max-age=38'
154 154
   end
155 155
 
  156
+  should "support custom http headers" do
  157
+    env = Rack::MockRequest.env_for("/cgi/test")
  158
+    status, heads, _ = file(DOCROOT, 'Cache-Control' => 'public, max-age=38',
  159
+     'Access-Control-Allow-Origin' => '*').call(env)
  160
+
  161
+    status.should.equal 200
  162
+    heads['Cache-Control'].should.equal 'public, max-age=38'
  163
+    heads['Access-Control-Allow-Origin'].should.equal '*'
  164
+  end
  165
+
  166
+  should "support not add custom http headers if none are supplied" do
  167
+    env = Rack::MockRequest.env_for("/cgi/test")
  168
+    status, heads, _ = file(DOCROOT).call(env)
  169
+
  170
+    status.should.equal 200
  171
+    heads['Cache-Control'].should.equal nil
  172
+    heads['Access-Control-Allow-Origin'].should.equal nil
  173
+  end
  174
+
156 175
   should "only support GET and HEAD requests" do
157 176
     req = Rack::MockRequest.new(file(DOCROOT))
158 177
 
47  test/spec_server.rb
@@ -10,6 +10,13 @@ def app
10 10
     lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }
11 11
   end
12 12
 
  13
+  def with_stderr
  14
+    old, $stderr = $stderr, StringIO.new
  15
+    yield $stderr
  16
+  ensure
  17
+    $stderr = old
  18
+  end
  19
+
13 20
   it "overrides :config if :app is passed in" do
14 21
     server = Rack::Server.new(:app => "FOO")
15 22
     server.app.should == "FOO"
@@ -71,4 +78,44 @@ def app
71 78
     open(pidfile) { |f| f.read.should.eql $$.to_s }
72 79
   end
73 80
 
  81
+  should "check pid file presence and running process" do
  82
+    pidfile = Tempfile.open('pidfile') { |f| f.write($$); break f }.path
  83
+    server = Rack::Server.new(:pid => pidfile)
  84
+    server.send(:pidfile_process_status).should.eql :running
  85
+  end
  86
+
  87
+  should "check pid file presence and dead process" do
  88
+    dead_pid = `echo $$`.to_i
  89
+    pidfile = Tempfile.open('pidfile') { |f| f.write(dead_pid); break f }.path
  90
+    server = Rack::Server.new(:pid => pidfile)
  91
+    server.send(:pidfile_process_status).should.eql :dead
  92
+  end
  93
+
  94
+  should "check pid file presence and exited process" do
  95
+    pidfile = Tempfile.open('pidfile') { |f| break f }.path
  96
+    ::File.delete(pidfile)
  97
+    server = Rack::Server.new(:pid => pidfile)
  98
+    server.send(:pidfile_process_status).should.eql :exited
  99
+  end
  100
+
  101
+  should "check pid file presence and not owned process" do
  102
+    pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
  103
+    server = Rack::Server.new(:pid => pidfile)
  104
+    server.send(:pidfile_process_status).should.eql :not_owned
  105
+  end
  106
+
  107
+  should "inform the user about existing pidfiles with running processes" do
  108
+    pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
  109
+    server = Rack::Server.new(:pid => pidfile)
  110
+    with_stderr do |err|
  111
+      should.raise(SystemExit) do
  112
+        server.start
  113
+      end
  114
+      err.rewind
  115
+      output = err.read
  116
+      output.should.match(/already running/)
  117
+      output.should.include? pidfile
  118
+    end
  119
+  end
  120
+
74 121
 end
7  test/spec_session_cookie.rb
@@ -338,4 +338,11 @@ def call(env)
21  test/spec_static.rb
@@ -12,7 +12,7 @@ def call(env)
12 12
   def static(app, *args)
13 13
     Rack::Lint.new Rack::Static.new(app, *args)
14 14
   end
15  
-  
  15
+
16 16
   root = File.expand_path(File.dirname(__FILE__))
17 17
 
18 18
   OPTIONS = {:urls => ["/cgi"], :root => root}
@@ -78,4 +78,23 @@ def static(app, *args)
78 78
     res.headers['Cache-Control'].should == 'public'
79 79
   end
80 80
 
  81
+  it "supports serving custom http headers" do
  82
+    opts = OPTIONS.merge(:headers => {'Cache-Control' => 'public, max-age=42',
  83
+      'Access-Control-Allow-Origin' => '*'})
  84
+    request = Rack::MockRequest.new(static(DummyApp.new, opts))
  85
+    res = request.get("/cgi/test")
  86
+    res.should.be.ok
  87
+    res.headers['Cache-Control'].should == 'public, max-age=42'
  88
+    res.headers['Access-Control-Allow-Origin'].should == '*'
  89
+  end
  90
+
  91
+  it "allows headers hash to take priority over fixed cache-control" do
  92
+    opts = OPTIONS.merge(:cache_control => 'public, max-age=38',
  93
+      :headers => {'Cache-Control' => 'public, max-age=42'})
  94
+    request = Rack::MockRequest.new(static(DummyApp.new, opts))
  95
+    res = request.get("/cgi/test")
  96
+    res.should.be.ok
  97
+    res.headers['Cache-Control'].should == 'public, max-age=42'
  98
+  end
  99
+
81 100
 end
2  test/spec_utils.rb
@@ -197,7 +197,7 @@ def kcodeu
197 197
 
198 198
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
199 199
       should.raise(TypeError).
200  
-      message.should.match /expected Array \(got [^)]*\) for param `x'/
  200
+      message.should.match(/expected Array \(got [^)]*\) for param `x'/)
201 201
 
202 202
     lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
203 203
       should.raise(TypeError).

0 notes on commit d084a14

Please sign in to comment.
Something went wrong with that request. Please try again.