roda 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8838cf12ea385cc60fd7a700596ea8e5b7d6f048
4
- data.tar.gz: 17e8862408a8cfec0f55a4ff6bc845bef02df74b
3
+ metadata.gz: cfcafebba5d38f3d4346b485721c5625aa0df988
4
+ data.tar.gz: c3e1918167a29a21c0a0936cf5296778dba0f052
5
5
  SHA512:
6
- metadata.gz: 3dc1d418979beb8c3410d4eb15c4e5c8355c501dc39ec091c80409f6c30e1a4e3d5dfbf41263965a11174915753ed66c9385a55549d6a97603e96f1eb46ae41a
7
- data.tar.gz: fdf05f0931a18f622c0c75e0bc596cb7421a584b0e67ac9d381bdb5932f78658ee3caf38d5ca7eba827893e21dcc2efd935b12b4c1ce9504082bf6927c1f0979
6
+ metadata.gz: a9c3d4d68c0f1e45f1c16edc923eced0070f4e1be5f9fa27465e0e8f398e1b49b0ee8e4dc5daade672f01530487846217b59b7e157f2e3c1f4c7a4fc4955072d
7
+ data.tar.gz: ee4d4ea602738536680bc27eec01502105dcb7e6bbccd7aa22cfc305eadf701b0980b2ee43bc37fe3a645434595091c408d99cb1e747675980fc5c5d83dc834f
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ = 2.12.0 (2016-03-15)
2
+
3
+ * Allow error handler access to the request's remaining_path (jeremyevans)
4
+
5
+ * Add optimized_string_matchers plugin, containing optimized matchers for single string arguments (jeremyevans)
6
+
7
+ * Optimize string matching code for strings without placeholders for up to a 60% performance increase (jeremyevans)
8
+
9
+ * Optimize symbol matching code for up to a 60% performance increase (jeremyevans)
10
+
1
11
  = 2.11.0 (2016-02-16)
2
12
 
3
13
  * Support :scope option in render plugin, for specifying object in which to evaluate the template (jeremyevans)
@@ -782,6 +782,38 @@ Example:
782
782
  'X-XSS-Protection'=>'1; mode=block'
783
783
  end
784
784
 
785
+ === Rendering Templates Derived From User Input
786
+
787
+ Roda's rendering plugin assumes that template paths given to it are trusted. If you provide a path
788
+ to the +render+/+view+ methods that is derived from user input, you are opening yourself
789
+ for people rendering arbitrary files on the system that that have a file name ending in the
790
+ default template extension. For example, if you do:
791
+
792
+ class App < Roda
793
+ plugin :render
794
+ route do |r|
795
+ view(r['page'])
796
+ end
797
+ end
798
+
799
+ Then attackers can submit a <tt>page</tt> parameter such as <tt>'../../../../tmp/upload'</tt>
800
+ to render the <tt>/tmp/upload.erb</tt> file. If you have another part of your system that
801
+ allows users to create files with arbitrary extensions (even temporary files), then it may
802
+ be possible to combine these two issues into a remote code execution exploit.
803
+
804
+ If you do want to allow users to choose which template to use, you should use a whitelist:
805
+
806
+ class App < Roda
807
+ plugin :render
808
+ ALLOWED_PAGES = %w'page1 page2 page3'
809
+ route do |r|
810
+ page = r['page']
811
+ if ALLOWED_PAGES.include?(page)
812
+ view(page)
813
+ end
814
+ end
815
+ end
816
+
785
817
  == Reloading
786
818
 
787
819
  Most rack-based reloaders will work with Roda, including:
data/Rakefile CHANGED
@@ -58,7 +58,7 @@ end
58
58
  desc "Make local version of website, with rdoc"
59
59
  task :website => [:website_base, :website_rdoc]
60
60
 
61
- desc "Make local version of website"
61
+ desc "Serve local version of website via rackup"
62
62
  task :serve => :website do
63
63
  sh %{#{FileUtils::RUBY} -C www -S rackup}
64
64
  end
@@ -0,0 +1,40 @@
1
+ = New Features
2
+
3
+ * An optimized_string_matchers plugin has been added, which contains
4
+ optimized matchers for single strings. r.on_branch is an optimized
5
+ version of r.on and r.is_exactly is optimized version of r.is:
6
+
7
+ plugin :optimized_string_matchers
8
+
9
+ route do |r|
10
+ r.on_branch "x" do
11
+ # matches /x and paths starting with /x/
12
+ r.is_exactly "y" do
13
+ # matches /x/y
14
+ end
15
+ end
16
+ end
17
+
18
+ Both of these methods will work even if the strings have placeholders,
19
+ but no captures will be yielded to the blocks.
20
+
21
+ * The error_handler plugin now has access to the request's
22
+ remaining_path when handling an error. You can then compare the
23
+ remaining_path to path_info to see how much of request was already
24
+ routed, which can be useful when reporting errors.
25
+
26
+ = Other Improvements
27
+
28
+ * String matching for strings without placeholders is now 60% faster
29
+ as it uses optimized string operations instead of a regexp match.
30
+
31
+ * Symbol matching is now 60% faster as it uses optimized string
32
+ operations instead of a regexp match.
33
+
34
+ = Backwards Compatibility
35
+
36
+ * The match methods no longer automatically reset the remaining_path
37
+ via ensure. This means that using non-local jumps out of the code
38
+ such as begin/rescue and throw/catch will not reset remaining_path
39
+ automatically. Users that want to reset remaining path in
40
+ such cases should use their own ensure blocks.
@@ -326,6 +326,7 @@ class Roda
326
326
  REQUEST_METHOD = "REQUEST_METHOD".freeze
327
327
  EMPTY_STRING = "".freeze
328
328
  SLASH = "/".freeze
329
+ COLON = ":".freeze
329
330
  SEGMENT = "([^\\/]+)".freeze
330
331
  TERM_INSPECT = "TERM".freeze
331
332
  GET_REQUEST_METHOD = 'GET'.freeze
@@ -676,12 +677,43 @@ class Roda
676
677
  # string so that regexp metacharacters are not matched, and recognizes
677
678
  # colon tokens for placeholders.
678
679
  def _match_string(str)
679
- consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
680
+ if str.index(COLON)
681
+ consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
682
+ else
683
+ rp = @remaining_path
684
+ if rp.start_with?("/#{str}")
685
+ last = str.length + 1
686
+ case rp[last]
687
+ when SLASH
688
+ @remaining_path = rp[last, rp.length]
689
+ when nil
690
+ @remaining_path = EMPTY_STRING
691
+ when Integer
692
+ # :nocov:
693
+ # Ruby 1.8 support
694
+ if rp[last].chr == SLASH
695
+ @remaining_path = rp[last, rp.length]
696
+ end
697
+ # :nocov:
698
+ end
699
+ end
700
+ end
680
701
  end
681
702
 
682
703
  # Match the given symbol if any segment matches.
683
704
  def _match_symbol(sym)
684
- consume(self.class.cached_matcher(sym){_match_symbol_regexp(sym)})
705
+ rp = @remaining_path
706
+ if rp[0, 1] == SLASH
707
+ if last = rp.index('/', 1)
708
+ if last > 1
709
+ @captures << rp[1, last-1]
710
+ @remaining_path = rp[last, rp.length]
711
+ end
712
+ elsif rp.length > 1
713
+ @captures << rp[1,rp.length]
714
+ @remaining_path = EMPTY_STRING
715
+ end
716
+ end
685
717
  end
686
718
 
687
719
  # The regular expression to use for matching symbols. By default, any non-empty
@@ -760,11 +792,13 @@ class Roda
760
792
  # nesting matchers won't mess with each other's captures.
761
793
  @captures.clear
762
794
 
763
- return unless match_all(args)
764
- block_result(yield(*captures))
765
- throw :halt, response.finish
766
- ensure
767
- @remaining_path = path
795
+ if match_all(args)
796
+ block_result(yield(*captures))
797
+ throw :halt, response.finish
798
+ else
799
+ @remaining_path = path
800
+ false
801
+ end
768
802
  end
769
803
 
770
804
  # Attempt to match the argument to the given request, handling
@@ -773,12 +807,12 @@ class Roda
773
807
  case matcher
774
808
  when String
775
809
  _match_string(matcher)
776
- when Regexp
777
- _match_regexp(matcher)
778
810
  when Symbol
779
811
  _match_symbol(matcher)
780
812
  when TERM
781
813
  empty_path?
814
+ when Regexp
815
+ _match_regexp(matcher)
782
816
  when Hash
783
817
  _match_hash(matcher)
784
818
  when Array
@@ -9,13 +9,16 @@ class Roda
9
9
  #
10
10
  # This uses the render plugin for rendering the assets, and the render
11
11
  # plugin uses tilt internally, so you can use any template engine
12
- # supported by tilt for you assets. Tilt ships with support for
12
+ # supported by tilt for your assets. Tilt ships with support for
13
13
  # the following asset template engines, assuming the necessary libaries
14
14
  # are installed:
15
15
  #
16
16
  # css :: Less, Sass, Scss
17
17
  # js :: CoffeeScript
18
18
  #
19
+ # You can also use opal as a javascript template engine, assuming it is
20
+ # installed.
21
+ #
19
22
  # == Usage
20
23
  #
21
24
  # When loading the plugin, use the :css and :js options
@@ -28,12 +31,13 @@ class Roda
28
31
  # assets/css/some_file.scss
29
32
  # assets/js/some_file.coffee
30
33
  #
31
- # If you want to change the paths where asset files are stored, see the
34
+ # The values for the :css and :js options can be arrays to load multiple
35
+ # files. If you want to change the paths where asset files are stored, see the
32
36
  # Options section below.
33
37
  #
34
38
  # === Serving
35
39
  #
36
- # In your routes, call the r.assets method to add a route to your assets,
40
+ # In your routes, call the +r.assets+ method to add a route to your assets,
37
41
  # which will make your app serve the rendered assets:
38
42
  #
39
43
  # route do |r|
@@ -126,9 +130,12 @@ class Roda
126
130
  # === Asset Compression
127
131
  #
128
132
  # If you have the yuicompressor gem installed and working, it will be used
129
- # automatically to compress your javascript and css assets. Otherwise,
130
- # the assets will just be concatenated together and not compressed during
131
- # compilation.
133
+ # automatically to compress your javascript and css assets. For javascript
134
+ # assets, if yuicompressor is not available, the plugin will check for
135
+ # closure_compiler, uglifier, and minjs and use the first one that works.
136
+ # If no compressors are available, the assets will just be concatenated
137
+ # together and not compressed during compilation. You can use the
138
+ # :css_compressor and :js_compressor options to specify the compressor to use.
132
139
  #
133
140
  # === With Asset Groups
134
141
  #
@@ -143,7 +150,7 @@ class Roda
143
150
  #
144
151
  # === Serving
145
152
  #
146
- # If you call +r.assets+ when compiling assets, will serve the compiled asset
153
+ # When compiling assets, +r.assets+ will serve the compiled asset
147
154
  # files. However, it is recommended to have the main webserver (e.g. nginx)
148
155
  # serve the compiled files, instead of relying on the application.
149
156
  #
@@ -165,7 +172,7 @@ class Roda
165
172
  # when loading the plugin. The value of this option should be the filename
166
173
  # where the compiled asset metadata is stored.
167
174
  #
168
- # If the compiled assset metadata file does not exist when the assets plugin
175
+ # If the compiled asset metadata file does not exist when the assets plugin
169
176
  # is loaded, the plugin will run in non-compiled mode. However, when you call
170
177
  # compile_assets, it will write the compiled asset metadata file after
171
178
  # compiling the assets.
@@ -79,6 +79,8 @@ class Roda
79
79
  # If the normal routing tree doesn't handle an action, try each class level route
80
80
  # to see if it matches.
81
81
  def call
82
+ req = @_request
83
+ rp = req.remaining_path
82
84
  result = super
83
85
 
84
86
  if result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
@@ -87,6 +89,7 @@ class Roda
87
89
  @_response = self.class::RodaResponse.new
88
90
  super do |r|
89
91
  opts[:class_level_routes].each do |meth, args, blk|
92
+ req.instance_variable_set(:@remaining_path, rp)
90
93
  r.send(meth, *args) do |*a|
91
94
  instance_exec(*a, &blk)
92
95
  end
@@ -44,6 +44,15 @@ class Roda
44
44
  /\A#{roda_class.opts[:match_prefix] || PREFIX}(?:#{pattern})#{roda_class.opts[:match_suffix] || SUFFIX}/
45
45
  end
46
46
  end
47
+
48
+ module RequestMethods
49
+ private
50
+
51
+ # Use regexps for all string matches, so that the prefix and suffix matches work.
52
+ def _match_string(str)
53
+ consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
54
+ end
55
+ end
47
56
  end
48
57
 
49
58
  register_plugin(:match_affix, MatchAffix)
@@ -0,0 +1,51 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The optimized_string_matchers plugin adds two optimized matcher methods,
7
+ # +r.on_branch+ and +r.is_exactly+. +r.on_branch+ is an optimized version of
8
+ # +r.on+ that only accepts a single string, and +r.is_exactly+ is an
9
+ # optimized version of +r.is+ that only accepts a single string.
10
+ #
11
+ # plugin :optimized_string_matchers
12
+ #
13
+ # route do |r|
14
+ # r.on_branch "x" do
15
+ # # matches /x and paths starting with /x/
16
+ # r.is_exactly "y" do
17
+ # # matches /x/y
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # Note that both of these methods only work with plain strings, not
23
+ # with strings with embedded colons for capturing. Matching will work
24
+ # correctly in such cases, but the captures will not be yielded to the
25
+ # match blocks.
26
+ module OptimizedStringMatchers
27
+ EMPTY_STRING = ''.freeze
28
+
29
+ module RequestMethods
30
+ # Optimized version of +on+ that only supports a single string.
31
+ def on_branch(s)
32
+ always{yield} if _match_string(s)
33
+ end
34
+
35
+ # Optimized version of +is+ that only supports a single string.
36
+ def is_exactly(s)
37
+ rp = @remaining_path
38
+ if _match_string(s)
39
+ if @remaining_path == EMPTY_STRING
40
+ always{yield}
41
+ else
42
+ @remaining_path = rp
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ register_plugin(:optimized_string_matchers, OptimizedStringMatchers)
50
+ end
51
+ end
@@ -10,7 +10,7 @@ class Roda
10
10
  #
11
11
  # route do |r|
12
12
  # r.on "foo/:bar" do |bar|
13
- # pass if bar == 'baz'
13
+ # r.pass if bar == 'baz'
14
14
  # "/foo/#{bar} (not baz)"
15
15
  # end
16
16
  #
@@ -34,7 +34,10 @@ class Roda
34
34
 
35
35
  # Handle passing inside the match block.
36
36
  def if_match(_)
37
- catch(:pass){super}
37
+ rp = @remaining_path
38
+ ret = catch(:pass){super}
39
+ @remaining_path = rp
40
+ ret
38
41
  end
39
42
  end
40
43
  end
@@ -44,6 +44,9 @@ class Roda
44
44
  # r.is "album:opt" do |id| end
45
45
  # # matches /album (yielding nil) and /album/foo (yielding "foo")
46
46
  # # does not match /album/ or /album/foo/bar
47
+ #
48
+ # If using this plugin with the params_capturing plugin, this plugin should
49
+ # be loaded first.
47
50
  module SymbolMatchers
48
51
  def self.configure(app)
49
52
  app.symbol_matcher(:d, /(\d+)/)
@@ -64,8 +67,21 @@ class Roda
64
67
  module RequestMethods
65
68
  private
66
69
 
67
- # Allow for symbol specific regexps, by using match_symbol_#{s} if
68
- # defined. If not defined, calls super for the default behavior.
70
+ # Use regular expressions to the symbol-specific regular expression
71
+ # if the symbol is registered. Otherwise, call super for the default
72
+ # behavior.
73
+ def _match_symbol(s)
74
+ meth = :"match_symbol_#{s}"
75
+ if respond_to?(meth)
76
+ re = send(meth)
77
+ consume(self.class.cached_matcher(re){re})
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ # Return the symbol-specific regular expression if one is registered.
84
+ # Otherwise, call super for the default behavior.
69
85
  def _match_symbol_regexp(s)
70
86
  meth = :"match_symbol_#{s}"
71
87
  if respond_to?(meth)
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 11
7
+ RodaMinorVersion = 12
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -118,4 +118,25 @@ describe "error_handler plugin" do
118
118
 
119
119
  proc{req}.must_raise(ArgumentError)
120
120
  end
121
+
122
+ it "has access to current remaining_path" do
123
+ app(:bare) do
124
+ plugin :error_handler do |e|
125
+ request.remaining_path
126
+ end
127
+
128
+ route do |r|
129
+ r.on('a') do
130
+ raise ArgumentError, "bad idea"
131
+ end
132
+
133
+ raise ArgumentError, "bad idea"
134
+ end
135
+ end
136
+
137
+ body.must_equal '/'
138
+ body('/b').must_equal '/b'
139
+ body('/a').must_equal ''
140
+ body('/a/c').must_equal '/c'
141
+ end
121
142
  end
@@ -0,0 +1,40 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "optimized_string_matchers plugin" do
4
+ it "should support on_branch and is_exactly match methods" do
5
+ app(:optimized_string_matchers) do |r|
6
+ r.on_branch "e" do
7
+ r.is_exactly "f" do
8
+ "ef"
9
+ end
10
+
11
+ "ee"
12
+ end
13
+
14
+ r.on_branch "a/:b" do |*b|
15
+ r.is_exactly ":c" do |*c|
16
+ "c-#{c.length}"
17
+ end
18
+
19
+ "a-#{b.length}"
20
+ end
21
+
22
+ "cc"
23
+ end
24
+
25
+ body.must_equal 'cc'
26
+ body('/a').must_equal 'cc'
27
+ body('/a/').must_equal 'cc'
28
+ body('/a/b/').must_equal 'a-0'
29
+ body('/a/b/').must_equal 'a-0'
30
+ body('/a/b/c').must_equal 'c-0'
31
+ body('/a/b/c/').must_equal 'a-0'
32
+ body('/a/b/c/d').must_equal 'a-0'
33
+ body('/e').must_equal 'ee'
34
+ body('/eb').must_equal 'cc'
35
+ body('/e/').must_equal 'ee'
36
+ body('/e/f').must_equal 'ef'
37
+ body('/e/f/').must_equal 'ee'
38
+ body('/e/fe').must_equal 'ee'
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-16 00:00:00.000000000 Z
11
+ date: 2016-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -177,6 +177,7 @@ extra_rdoc_files:
177
177
  - doc/release_notes/2.9.0.txt
178
178
  - doc/release_notes/2.10.0.txt
179
179
  - doc/release_notes/2.11.0.txt
180
+ - doc/release_notes/2.12.0.txt
180
181
  files:
181
182
  - CHANGELOG
182
183
  - MIT-LICENSE
@@ -191,6 +192,7 @@ files:
191
192
  - doc/release_notes/2.1.0.txt
192
193
  - doc/release_notes/2.10.0.txt
193
194
  - doc/release_notes/2.11.0.txt
195
+ - doc/release_notes/2.12.0.txt
194
196
  - doc/release_notes/2.2.0.txt
195
197
  - doc/release_notes/2.3.0.txt
196
198
  - doc/release_notes/2.4.0.txt
@@ -242,6 +244,7 @@ files:
242
244
  - lib/roda/plugins/named_templates.rb
243
245
  - lib/roda/plugins/not_allowed.rb
244
246
  - lib/roda/plugins/not_found.rb
247
+ - lib/roda/plugins/optimized_string_matchers.rb
245
248
  - lib/roda/plugins/padrino_render.rb
246
249
  - lib/roda/plugins/param_matchers.rb
247
250
  - lib/roda/plugins/params_capturing.rb
@@ -320,6 +323,7 @@ files:
320
323
  - spec/plugin/named_templates_spec.rb
321
324
  - spec/plugin/not_allowed_spec.rb
322
325
  - spec/plugin/not_found_spec.rb
326
+ - spec/plugin/optimized_string_matchers_spec.rb
323
327
  - spec/plugin/padrino_render_spec.rb
324
328
  - spec/plugin/param_matchers_spec.rb
325
329
  - spec/plugin/params_capturing_spec.rb