gin 1.0.2 → 1.0.3

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 (5) hide show
  1. data/History.rdoc +11 -0
  2. data/lib/gin.rb +1 -4
  3. data/lib/gin/app.rb +84 -31
  4. data/test/test_app.rb +60 -27
  5. metadata +3 -3
@@ -1,3 +1,14 @@
1
+ === 1.0.2 / 2013-03-12
2
+
3
+ * Minor Enhancements
4
+ * Routes have priority over static assets
5
+ * Rack::Session and Rack::Protection are off by default
6
+ * When running as middleware, don't run internal middleware
7
+ if going to next rack app
8
+
9
+ * Bugfixes
10
+ * Block on reloading to avoid multi-threading issues
11
+
1
12
  === 1.0.2 / 2013-03-08
2
13
 
3
14
  * Minor Enhancements
data/lib/gin.rb CHANGED
@@ -1,11 +1,8 @@
1
1
  require 'logger'
2
-
3
2
  require 'rack'
4
- require 'rack-protection'
5
-
6
3
 
7
4
  class Gin
8
- VERSION = '1.0.2'
5
+ VERSION = '1.0.3'
9
6
 
10
7
  LIB_DIR = File.expand_path("..", __FILE__) #:nodoc:
11
8
  PUBLIC_DIR = File.expand_path("../../public/", __FILE__) #:nodoc:
@@ -18,7 +18,11 @@ class Gin::App
18
18
 
19
19
  RACK_KEYS = { #:nodoc:
20
20
  :stack => 'gin.stack'.freeze,
21
+ :http_route => 'gin.http_route'.freeze,
21
22
  :path_params => 'gin.path_query_hash'.freeze,
23
+ :controller => 'gin.controller'.freeze,
24
+ :action => 'gin.action'.freeze,
25
+ :static => 'gin.static'.freeze,
22
26
  :reloaded => 'gin.reloaded'.freeze,
23
27
  :errors => 'gin.errors'.freeze
24
28
  }.freeze
@@ -312,11 +316,11 @@ class Gin::App
312
316
 
313
317
  ##
314
318
  # Use rack sessions or not. Supports assigning
315
- # hash for options. Defaults to true.
319
+ # hash for options. Defaults to false.
316
320
 
317
321
  def self.sessions opts=nil
318
322
  @session = opts unless opts.nil?
319
- @session = true if @session.nil?
323
+ @session = false if @session.nil?
320
324
  @session
321
325
  end
322
326
 
@@ -333,11 +337,11 @@ class Gin::App
333
337
 
334
338
  ##
335
339
  # Use rack-protection or not. Supports assigning
336
- # hash for options. Defaults to true.
340
+ # hash for options. Defaults to false.
337
341
 
338
342
  def self.protection opts=nil
339
343
  @protection = opts unless opts.nil?
340
- @protection = true if @protection.nil?
344
+ @protection = false if @protection.nil?
341
345
  @protection
342
346
  end
343
347
 
@@ -431,13 +435,17 @@ class Gin::App
431
435
 
432
436
  def reload!
433
437
  return unless autoreload
434
- self.class.erase! [self.class.source_file],
435
- [self.class.name.split("::").last],
436
- self.class.namespace
438
+ @mutex ||= Mutex.new
437
439
 
438
- self.class.erase_dependencies!
439
- Object.send(:require, self.class.source_file)
440
- @app = self.class.source_class.new @rack_app, @logger
440
+ @mutex.synchronize do
441
+ self.class.erase! [self.class.source_file],
442
+ [self.class.name.split("::").last],
443
+ self.class.namespace
444
+
445
+ self.class.erase_dependencies!
446
+ Object.send(:require, self.class.source_file)
447
+ @app = self.class.source_class.new @rack_app, @logger
448
+ end
441
449
  end
442
450
 
443
451
 
@@ -445,18 +453,40 @@ class Gin::App
445
453
  # Default Rack call method.
446
454
 
447
455
  def call env
448
- if filename = static?(env)
449
- return error_delegate.exec(self, env){ send_file filename }
456
+ try_autoreload(env)
457
+
458
+ if @app.route!(env)
459
+ @app.call!(env)
460
+
461
+ elsif @app.static!(env)
462
+ @app.call_static(env)
463
+
464
+ elsif @rack_app
465
+ @rack_app.call(env)
466
+
467
+ else
468
+ @app.call!(env)
450
469
  end
470
+ end
451
471
 
452
- if autoreload && !env[RACK_KEYS[:reloaded]]
453
- env[RACK_KEYS[:reloaded]] = true
454
- reload!
455
- @app.call env
456
472
 
457
- elsif env[RACK_KEYS[:stack]]
473
+ ##
474
+ # Check if autoreload is needed and reload.
475
+
476
+ def try_autoreload env
477
+ return if env[RACK_KEYS[:reloaded]]
478
+ env[RACK_KEYS[:reloaded]] = true
479
+ reload!
480
+ end
481
+
482
+
483
+ ##
484
+ # Call App instance stack without static file lookup or reloading.
485
+
486
+ def call! env
487
+ if env[RACK_KEYS[:stack]]
458
488
  env.delete RACK_KEYS[:stack]
459
- @app.call! env
489
+ dispatch env, env[RACK_KEYS[:controller]], env[RACK_KEYS[:action]]
460
490
 
461
491
  else
462
492
  env[RACK_KEYS[:stack]] = true
@@ -466,29 +496,53 @@ class Gin::App
466
496
 
467
497
 
468
498
  ##
469
- # Call App instance without internal middleware or reloading.
470
-
471
- def call! env
472
- ctrl, action, env[RACK_KEYS[:path_params]] =
473
- router.resources_for env['REQUEST_METHOD'], env['PATH_INFO']
499
+ # Returns a static file Rack response Array from the given gin.static
500
+ # env filename.
474
501
 
475
- dispatch env, ctrl, action
502
+ def call_static env
503
+ error_delegate.exec(self, env){ send_file env[RACK_KEYS[:static]] }
476
504
  end
477
505
 
478
506
 
479
- STATIC_PATH_CLEANER = %r{\.+/|/\.+} #:nodoc:
507
+ ##
508
+ # Check if the request is for a static file and set the gin.static env
509
+ # variable to the filepath.
510
+
511
+ def static! env
512
+ filepath = %w{GET HEAD}.include?(env['REQUEST_METHOD']) &&
513
+ asset(env['PATH_INFO'])
514
+
515
+ filepath ? (env[RACK_KEYS[:static]] = filepath) :
516
+ env.delete(RACK_KEYS[:static])
517
+
518
+ !!env[RACK_KEYS[:static]]
519
+ end
520
+
480
521
 
481
522
  ##
482
- # Check if the request is for a static file.
523
+ # Check if the request routes to a controller and action and set
524
+ # gin.controller, gin.action, gin.path_query_hash,
525
+ # and gin.http_route env variables.
483
526
 
484
- def static? env
485
- %w{GET HEAD}.include?(env['REQUEST_METHOD']) && asset(env['PATH_INFO'])
527
+ def route! env
528
+ http_route = "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
529
+ return true if env[RACK_KEYS[:http_route]] == http_route
530
+
531
+ env[RACK_KEYS[:controller]], env[RACK_KEYS[:action]], env[RACK_KEYS[:path_params]] =
532
+ router.resources_for env['REQUEST_METHOD'], env['PATH_INFO']
533
+
534
+ env[RACK_KEYS[:http_route]] = http_route
535
+
536
+ !!(env[RACK_KEYS[:controller]] && env[RACK_KEYS[:action]])
486
537
  end
487
538
 
488
539
 
540
+ STATIC_PATH_CLEANER = %r{\.+/|/\.+} #:nodoc:
541
+
489
542
  ##
490
543
  # Check if an asset exists.
491
544
  # Returns the full path to the asset if found, otherwise nil.
545
+ # Does not support ./ or ../ for security reasons.
492
546
 
493
547
  def asset path
494
548
  path = path.gsub STATIC_PATH_CLEANER, ""
@@ -511,9 +565,6 @@ class Gin::App
511
565
 
512
566
  ctrl.new(self, env).call_action action
513
567
 
514
- rescue Gin::NotFound => err
515
- @rack_app ? @rack_app.call(env) : handle_error(err, env)
516
-
517
568
  rescue ::Exception => err
518
569
  handle_error(err, env)
519
570
  end
@@ -564,6 +615,8 @@ class Gin::App
564
615
 
565
616
  def setup_protection builder
566
617
  return unless protection
618
+ require 'rack-protection' unless defined?(Rack::Protection)
619
+
567
620
  options = Hash === protection ? protection.dup : {}
568
621
  options[:except] = Array options[:except]
569
622
  options[:except] += [:session_hijacking, :remote_token] unless sessions
@@ -134,7 +134,7 @@ class AppTest < Test::Unit::TestCase
134
134
 
135
135
 
136
136
  def test_protection
137
- assert_equal true, FooApp.protection
137
+ assert_equal false, FooApp.protection
138
138
 
139
139
  FooApp.protection(test: "thing")
140
140
  assert_equal({test:"thing"}, FooApp.protection)
@@ -171,7 +171,7 @@ class AppTest < Test::Unit::TestCase
171
171
 
172
172
 
173
173
  def test_sessions
174
- assert_equal true, FooApp.sessions
174
+ assert_equal false, FooApp.sessions
175
175
 
176
176
  FooApp.sessions(test: "thing")
177
177
  assert_equal({test:"thing"}, FooApp.sessions)
@@ -247,7 +247,7 @@ class AppTest < Test::Unit::TestCase
247
247
  assert FooMiddleware.called?
248
248
 
249
249
  FooMiddleware.reset!
250
- myapp.call!({'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'})
250
+ myapp.dispatch({'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'}, FooController, :index)
251
251
  assert !FooMiddleware.called?
252
252
  end
253
253
 
@@ -273,6 +273,18 @@ class AppTest < Test::Unit::TestCase
273
273
  end
274
274
 
275
275
 
276
+ def test_call_rack_app
277
+ env = {'rack.input' => "", 'PATH_INFO' => '/bad', 'REQUEST_METHOD' => 'GET'}
278
+ expected = [200, {'Content-Length'=>"5"}, "AHOY!"]
279
+ myapp = lambda{|env| expected }
280
+ @app = FooApp.new myapp
281
+
282
+ resp = @app.call env
283
+ assert_equal expected, resp
284
+ end
285
+
286
+
287
+
276
288
  def test_call!
277
289
  resp = @app.call! 'rack.input' => "",
278
290
  'PATH_INFO' => '/foo',
@@ -324,17 +336,6 @@ class AppTest < Test::Unit::TestCase
324
336
  end
325
337
 
326
338
 
327
- def test_dispatch_rack_app
328
- env = {'rack.input' => "", 'PATH_INFO' => '/bad', 'REQUEST_METHOD' => 'GET'}
329
- expected = [200, {'Content-Length'=>"5"}, "AHOY!"]
330
- myapp = lambda{|env| expected }
331
- @app = FooApp.new myapp
332
-
333
- resp = @app.dispatch env, nil, nil
334
- assert_equal expected, resp
335
- end
336
-
337
-
338
339
  def test_dispatch_error
339
340
  FooApp.environment 'test'
340
341
  env = {'rack.input' => "", 'PATH_INFO' => '/bad', 'REQUEST_METHOD' => 'GET'}
@@ -457,33 +458,65 @@ class AppTest < Test::Unit::TestCase
457
458
  FooApp.public_dir "./test/mock_config"
458
459
  assert @app.asset("backend.yml") =~ %r{/gin/test/mock_config/backend\.yml$}
459
460
  assert @app.asset("500.html") =~ %r{/gin/public/500\.html$}
461
+
462
+ assert !@app.asset("foo/../../mock_config/backend.yml")
463
+ assert !@app.asset("foo/../../public/500.html")
464
+ end
465
+
466
+
467
+ def test_bad_asset
468
+ FooApp.public_dir "./test/mock_config"
460
469
  assert_nil @app.asset("bad_file")
461
470
  assert_nil @app.asset("../../History.rdoc")
471
+
472
+ path = File.join(FooApp.public_dir, "../.././test/mock_config/../../History.rdoc")
473
+ assert File.file?(path)
474
+ assert_nil @app.asset("../.././test/mock_config/../../History.rdoc")
462
475
  end
463
476
 
464
477
 
465
- def test_static
478
+ def test_static_no_file
466
479
  env = {'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'}
467
- assert !@app.static?(env)
480
+ assert !@app.static!(env)
481
+ end
482
+
468
483
 
484
+ def test_static
485
+ env = {'rack.input' => "", 'REQUEST_METHOD' => 'GET'}
469
486
  env['PATH_INFO'] = '/500.html'
470
- assert @app.static?(env) =~ %r{/gin/public/500\.html$}
487
+ assert @app.static!(env)
488
+ assert env['gin.static'] =~ %r{/gin/public/500\.html$}
489
+ end
471
490
 
472
- env['PATH_INFO'] = '../../../500.html'
473
- assert @app.static?(env) =~ %r{/gin/public/500\.html$}
474
491
 
492
+ def test_static_updir
493
+ env = {'rack.input' => "", 'REQUEST_METHOD' => 'GET'}
494
+ env['PATH_INFO'] = '../../gin/public/500.html'
495
+ assert !@app.static!(env)
496
+ assert !env['gin.static']
497
+ end
498
+
499
+
500
+ def test_static_head
501
+ env = {'rack.input' => "", 'REQUEST_METHOD' => 'GET'}
475
502
  env['REQUEST_METHOD'] = 'HEAD'
476
- assert @app.static?(env) =~ %r{/gin/public/500\.html$}
503
+ env['PATH_INFO'] = '/500.html'
504
+ assert @app.static!(env)
505
+ assert env['gin.static'] =~ %r{/gin/public/500\.html$}
506
+ end
477
507
 
508
+
509
+ def test_non_static_verbs
510
+ env = {'rack.input' => "", 'REQUEST_METHOD' => 'GET'}
478
511
  env['PATH_INFO'] = '/backend.yml'
479
- assert !@app.static?(env)
480
512
 
481
513
  FooApp.public_dir "./test/mock_config"
482
- assert @app.static?(env) =~ %r{/gin/test/mock_config/backend\.yml$}
514
+ assert @app.static!(env)
515
+ assert env['gin.static'] =~ %r{/gin/test/mock_config/backend\.yml$}
483
516
 
484
517
  %w{POST PUT DELETE TRACE OPTIONS}.each do |verb|
485
518
  env['REQUEST_METHOD'] = verb
486
- assert !@app.static?(env), "#{verb} should not be a static request"
519
+ assert !@app.static!(env), "#{verb} should not be a static request"
487
520
  end
488
521
  end
489
522
 
@@ -568,7 +601,10 @@ class AppTest < Test::Unit::TestCase
568
601
  end
569
602
 
570
603
 
571
- def test_build_default_middleware
604
+ def test_build_w_middleware
605
+ FooApp.sessions true
606
+ FooApp.protection true
607
+ @app = FooApp.new
572
608
  stack = @app.instance_variable_get("@stack")
573
609
  assert Rack::Session::Cookie === stack
574
610
  assert Rack::Protection::FrameOptions === stack.instance_variable_get("@app")
@@ -583,9 +619,6 @@ class AppTest < Test::Unit::TestCase
583
619
 
584
620
 
585
621
  def test_build_no_middleware
586
- FooApp.sessions false
587
- FooApp.protection false
588
- @app = FooApp.new
589
622
  stack = @app.instance_variable_get("@stack")
590
623
  assert_equal @app, stack
591
624
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-08 00:00:00.000000000 Z
12
+ date: 2013-03-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -150,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
150
  version: '0'
151
151
  segments:
152
152
  - 0
153
- hash: -3176863341583704067
153
+ hash: 3408999320760955315
154
154
  required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  none: false
156
156
  requirements: