roda 2.11.0 → 2.12.0

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.
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