mustache 1.1.1 → 1.1.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.
- checksums.yaml +4 -4
- data/README.md +6 -5
- data/Rakefile +1 -1
- data/bin/mustache +6 -1
- data/lib/mustache/context.rb +21 -0
- data/lib/mustache/context_miss.rb +41 -0
- data/lib/mustache/generator.rb +2 -2
- data/lib/mustache/parser.rb +1 -1
- data/lib/mustache/settings.rb +59 -22
- data/lib/mustache/utils.rb +1 -1
- data/lib/mustache/version.rb +1 -1
- data/lib/mustache.rb +4 -7
- data/man/mustache.5 +301 -224
- data/man/mustache.5.html +306 -30
- data/man/mustache.5.ron +277 -19
- data/test/autoloading_test.rb +5 -0
- data/test/fixtures/override/passenger.conf +6 -0
- data/test/mustache_test.rb +20 -2
- data/test/path_test.rb +49 -0
- metadata +6 -105
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf066480926aaca95b8ebbb4e45de15a11cae753e407be03a8059bee4deb9837
|
|
4
|
+
data.tar.gz: a334278f764909eb095bed8bf31132af6c61d6dffadbf5688cd745e85517a7a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 571c5ff585457d1073fa4de0d506073b55a7c3409967d39a488b71fe38b03e7074312ad5e9e379d0f654043dc8be7ffdba4e1cb3b6df4ba6e884920885064ecd
|
|
7
|
+
data.tar.gz: cb3b39d80301b99e13e4cf412e5da3065ecf5215d7b7bece535c032e6cec5c452a0d44f43363d4d35a06a2267e67945325c177ae8631f8b19bac8f344fad2768
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Mustache
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/mustache)
|
|
4
|
-
[](https://github.com/mustache/mustache/actions/workflows/test.yml)
|
|
5
5
|
|
|
6
6
|
Inspired by [ctemplate][1] and [et][2], Mustache is a
|
|
7
7
|
framework-agnostic way to render logic-free views.
|
|
@@ -166,8 +166,9 @@ follows the classic Ruby naming convention.
|
|
|
166
166
|
|
|
167
167
|
TemplatePartial => ./template_partial.mustache
|
|
168
168
|
|
|
169
|
-
You can set the search path using `Mustache.template_path`.
|
|
170
|
-
|
|
169
|
+
You can set the search path using `Mustache.template_path`. Search multiple
|
|
170
|
+
paths by delimiting them with your platform's 'path separator' (colon on Unix).
|
|
171
|
+
It can be set on a class by class basis:
|
|
171
172
|
|
|
172
173
|
```ruby
|
|
173
174
|
class Simple < Mustache
|
|
@@ -381,9 +382,9 @@ Once you've made your great commits:
|
|
|
381
382
|
|
|
382
383
|
~~To join the list simply send an email to <mustache@librelist.com>. This
|
|
383
384
|
will subscribe you and send you information about your subscription,
|
|
384
|
-
including unsubscribe information
|
|
385
|
+
including unsubscribe information.~~
|
|
385
386
|
|
|
386
|
-
The archive can be found at <http://librelist.com/browser/mustache/>.~~
|
|
387
|
+
~~The archive can be found at <http://librelist.com/browser/mustache/>.~~
|
|
387
388
|
|
|
388
389
|
The mailing list hasn't been updated in quite a while, please join us on Gitter
|
|
389
390
|
or IRC:
|
data/Rakefile
CHANGED
data/bin/mustache
CHANGED
|
@@ -11,7 +11,7 @@ class Mustache
|
|
|
11
11
|
# Return a structure describing the options.
|
|
12
12
|
def self.parse_options(args)
|
|
13
13
|
opts = OptionParser.new do |opts|
|
|
14
|
-
opts.banner = "Usage: mustache [-c] [-t] [-e] [-r library] FILE ..."
|
|
14
|
+
opts.banner = "Usage: mustache [-c] [-t] [-l level] [-e] [-r library] FILE ..."
|
|
15
15
|
|
|
16
16
|
opts.separator " "
|
|
17
17
|
|
|
@@ -42,6 +42,11 @@ class Mustache
|
|
|
42
42
|
exit
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
opts.on("-l", "--level",
|
|
46
|
+
"Provide a context access security level.") do |level|
|
|
47
|
+
Mustache.context_access_security_level = level.to_i
|
|
48
|
+
end
|
|
49
|
+
|
|
45
50
|
opts.on("-e", "--error",
|
|
46
51
|
"Raise an error on context misses.") do |e|
|
|
47
52
|
Mustache.raise_on_context_miss = true
|
data/lib/mustache/context.rb
CHANGED
|
@@ -136,14 +136,35 @@ class Mustache
|
|
|
136
136
|
# @return [Object] The value of key in object if it is found, and default otherwise.
|
|
137
137
|
#
|
|
138
138
|
def find(obj, key, default = nil)
|
|
139
|
+
|
|
140
|
+
if mustache_in_stack.context_access_security_level == 5 and !obj.instance_of?(Hash)
|
|
141
|
+
# on level 5, object must be exactly Hash
|
|
142
|
+
return context_level_violation("Detected key access attempt for a non-Hash object using '#{key}'", default)
|
|
143
|
+
end
|
|
144
|
+
|
|
139
145
|
return find_in_hash(obj.to_hash, key, default) if obj.respond_to?(:to_hash)
|
|
140
146
|
|
|
147
|
+
if mustache_in_stack.context_access_security_level >= 4
|
|
148
|
+
# on levels 4 and 5, method access is not allowed
|
|
149
|
+
return context_level_violation("Detected method access attempt using '#{key}'", default)
|
|
150
|
+
end
|
|
151
|
+
|
|
141
152
|
unless obj.respond_to?(key)
|
|
142
153
|
# no match for the key, but it may include a hyphen, so try again replacing hyphens with underscores.
|
|
143
154
|
key = key.to_s.tr('-', '_')
|
|
144
155
|
return default unless obj.respond_to?(key)
|
|
145
156
|
end
|
|
146
157
|
|
|
158
|
+
if mustache_in_stack.context_access_security_level >= 3 && !obj.class.instance_methods(false).include?(key.to_sym)
|
|
159
|
+
# on level 3, inherited methods are blocked
|
|
160
|
+
return context_level_violation("Detected inherited method '#{key}'", default)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if mustache_in_stack.context_access_security_level >= 2 && MethodBlacklist.include?(key.to_s)
|
|
164
|
+
# on levels 2 and 3, reflection-based methods are blacklisted
|
|
165
|
+
return context_level_violation("Detected blacklisted reflection-related key '#{key}'", default)
|
|
166
|
+
end
|
|
167
|
+
|
|
147
168
|
meth = obj.method(key) rescue proc { obj.send(key) }
|
|
148
169
|
meth.arity == 1 ? meth.to_proc : meth.call
|
|
149
170
|
end
|
|
@@ -11,4 +11,45 @@ class Mustache
|
|
|
11
11
|
#
|
|
12
12
|
class ContextMiss < RuntimeError; end
|
|
13
13
|
|
|
14
|
+
# A ContextLevelViolation is raised whenever accessing a key in the context is
|
|
15
|
+
# not allowed by the selected context access security level
|
|
16
|
+
#
|
|
17
|
+
# For example, if your context access security level is set to 3 but
|
|
18
|
+
# your template tries to access an inherited method this exception will be raised.
|
|
19
|
+
#
|
|
20
|
+
# By default it is only raised for blacklisted keys in objects.
|
|
21
|
+
# See Mustache.context_access_security_level.
|
|
22
|
+
#
|
|
23
|
+
class ContextLevelViolation < ContextMiss; end
|
|
24
|
+
|
|
25
|
+
class Context
|
|
26
|
+
# Raises ContextLevelViolation or returns default depending on raise_on_context_miss
|
|
27
|
+
#
|
|
28
|
+
# @param [String] message The error message if raise_on_context_miss is set
|
|
29
|
+
# @param [Object] default An optional default value, to return if the key is not found.
|
|
30
|
+
#
|
|
31
|
+
# @return [Object] The default value if errors are not raised
|
|
32
|
+
#
|
|
33
|
+
def context_level_violation(message, default = nil)
|
|
34
|
+
return default unless mustache_in_stack.raise_on_context_miss?
|
|
35
|
+
raise ContextLevelViolation.new(message)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
MethodBlacklist = ["allocate", "attached_object", "superclass", "subclasses", "new", "autoload?", "autoload",
|
|
41
|
+
"included_modules", "include?", "set_temporary_name", "ancestors", "attr", "attr_reader", "attr_writer",
|
|
42
|
+
"attr_accessor", "public_instance_method", "instance_methods", "public_instance_methods",
|
|
43
|
+
"protected_instance_methods", "private_instance_methods", "undefined_instance_methods", "freeze", "const_get",
|
|
44
|
+
"constants", "const_missing", "const_defined?", "const_set", "const_source_location", "remove_class_variable",
|
|
45
|
+
"class_variable_get", "class_variables", "private_constant", "class_variable_set", "class_variable_defined?",
|
|
46
|
+
"public_constant", "include", "deprecate_constant", "singleton_class?", "prepend", "refinements", "define_method",
|
|
47
|
+
"module_exec", "class_exec", "module_eval", "class_eval", "remove_method", "undef_method", "alias_method",
|
|
48
|
+
"method_defined?", "public_method_defined?", "private_method_defined?", "protected_method_defined?",
|
|
49
|
+
"public_class_method", "private_class_method", "instance_method", "singleton_class", "dup", "itself", "methods",
|
|
50
|
+
"singleton_methods", "protected_methods", "private_methods", "public_methods", "instance_variables",
|
|
51
|
+
"instance_variable_get", "instance_variable_set", "instance_variable_defined?", "remove_instance_variable",
|
|
52
|
+
"instance_of?", "kind_of?", "is_a?", "display", "public_send", "extend", "clone", "class", "frozen?", "tap",
|
|
53
|
+
"then", "yield_self", "respond_to?", "method", "public_method", "singleton_method", "define_singleton_method",
|
|
54
|
+
"hash", "object_id", "send", "enum_for", "__send__", "instance_eval", "instance_exec", "__id__"]
|
|
14
55
|
end
|
data/lib/mustache/generator.rb
CHANGED
|
@@ -88,7 +88,7 @@ class Mustache
|
|
|
88
88
|
def compile!(exp)
|
|
89
89
|
case exp.first
|
|
90
90
|
when :multi
|
|
91
|
-
exp[1..-1].reduce("") { |sum, e| sum << compile!(e) }
|
|
91
|
+
exp[1..-1].reduce("".dup) { |sum, e| sum << compile!(e) }
|
|
92
92
|
when :static
|
|
93
93
|
str(exp[1])
|
|
94
94
|
when :mustache
|
|
@@ -151,7 +151,7 @@ class Mustache
|
|
|
151
151
|
# what's inside.
|
|
152
152
|
ev(<<-compiled)
|
|
153
153
|
v = #{compile!(name)}
|
|
154
|
-
if v.nil? || v == false || v.respond_to?(:empty?) && v.empty?
|
|
154
|
+
if v.nil? || v == false || (v.class != String && v.respond_to?(:empty?) && v.empty?)
|
|
155
155
|
#{code}
|
|
156
156
|
end
|
|
157
157
|
compiled
|
data/lib/mustache/parser.rb
CHANGED
|
@@ -78,7 +78,7 @@ EOF
|
|
|
78
78
|
SKIP_WHITESPACE = [ '#', '^', '/', '<', '>', '=', '!' ].map(&:freeze)
|
|
79
79
|
|
|
80
80
|
# The content allowed in a tag name.
|
|
81
|
-
ALLOWED_CONTENT = /(\w|[
|
|
81
|
+
ALLOWED_CONTENT = /(\w|[?!\/.@-])*/
|
|
82
82
|
|
|
83
83
|
# These types of tags allow any content,
|
|
84
84
|
# the rest only allow ALLOWED_CONTENT.
|
data/lib/mustache/settings.rb
CHANGED
|
@@ -9,6 +9,7 @@ class Mustache
|
|
|
9
9
|
@template_name = nil
|
|
10
10
|
@template_file = nil
|
|
11
11
|
@raise_on_context_miss = nil
|
|
12
|
+
@context_access_security_level = 2
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def self.initialize_settings
|
|
@@ -18,6 +19,7 @@ class Mustache
|
|
|
18
19
|
@template_name = nil
|
|
19
20
|
@template_file = nil
|
|
20
21
|
@raise_on_context_miss = nil
|
|
22
|
+
@context_access_security_level = 2
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
initialize_settings
|
|
@@ -31,38 +33,43 @@ class Mustache
|
|
|
31
33
|
#
|
|
32
34
|
|
|
33
35
|
# The template path informs your Mustache view where to look for its
|
|
34
|
-
# corresponding template.
|
|
36
|
+
# corresponding template. It is an array of paths, by default containing
|
|
37
|
+
# one entry: the current directory (".")
|
|
38
|
+
# When setting up from a string, use path delimiters to create a search path
|
|
39
|
+
# When the template_file is requested, a search is done to find the file in the
|
|
40
|
+
# path, this is then stored as the name.
|
|
35
41
|
#
|
|
36
42
|
# A class named Stat with a template_path of "app/templates" will look
|
|
37
43
|
# for "app/templates/stat.mustache"
|
|
38
44
|
|
|
45
|
+
def self.setup_path path
|
|
46
|
+
path = path.split(File::PATH_SEPARATOR) if path.is_a? String
|
|
47
|
+
path.map{|p| File.expand_path(p)}
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
def self.template_path
|
|
40
|
-
@template_path ||= inheritable_config_for
|
|
51
|
+
@template_path ||= setup_path(inheritable_config_for(:template_path, '.'))
|
|
41
52
|
end
|
|
42
53
|
|
|
43
54
|
def self.template_path=(path)
|
|
44
|
-
@template_path =
|
|
55
|
+
@template_path = setup_path(path)
|
|
45
56
|
@template = nil
|
|
46
57
|
end
|
|
47
58
|
|
|
48
59
|
def template_path
|
|
49
60
|
@template_path ||= self.class.template_path
|
|
50
61
|
end
|
|
62
|
+
|
|
51
63
|
alias_method :path, :template_path
|
|
52
64
|
|
|
53
65
|
def template_path=(path)
|
|
54
|
-
@template_path =
|
|
66
|
+
@template_path = self.class.setup_path(path)
|
|
55
67
|
@template = nil
|
|
56
68
|
end
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
template_path
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Alias for `template_path`
|
|
64
|
-
def self.path=(path)
|
|
65
|
-
self.template_path = path
|
|
70
|
+
class << self
|
|
71
|
+
alias_method :path, :template_path
|
|
72
|
+
alias_method :path=, :template_path=
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
|
|
@@ -128,26 +135,25 @@ class Mustache
|
|
|
128
135
|
# Template File
|
|
129
136
|
#
|
|
130
137
|
|
|
131
|
-
# The template file is the absolute path of the file Mustache will
|
|
132
|
-
#
|
|
138
|
+
# The template file is the absolute path of the file Mustache will use as its template.
|
|
139
|
+
# By default it's ./class_name.mustache
|
|
140
|
+
#
|
|
133
141
|
|
|
134
142
|
def self.template_file
|
|
135
|
-
@template_file || "#{
|
|
143
|
+
@template_file || path.map{|p| "#{p}/#{template_name}.#{template_extension}" }.find{|tf| File.readable? tf}
|
|
136
144
|
end
|
|
137
145
|
|
|
138
|
-
def self.template_file=(
|
|
139
|
-
@template_file =
|
|
146
|
+
def self.template_file=(tf)
|
|
147
|
+
@template_file = tf
|
|
140
148
|
@template = nil
|
|
141
149
|
end
|
|
142
150
|
|
|
143
|
-
# The template file is the absolute path of the file Mustache will
|
|
144
|
-
# use as its template. By default it's ./class_name.mustache
|
|
145
151
|
def template_file
|
|
146
|
-
@template_file || "#{
|
|
152
|
+
@template_file || path.map{|p| "#{p}/#{template_name}.#{template_extension}" }.find{|tf| File.readable? tf}
|
|
147
153
|
end
|
|
148
154
|
|
|
149
|
-
def template_file=(
|
|
150
|
-
@template_file =
|
|
155
|
+
def template_file=(tf)
|
|
156
|
+
@template_file = tf
|
|
151
157
|
@template = nil
|
|
152
158
|
end
|
|
153
159
|
|
|
@@ -214,6 +220,37 @@ class Mustache
|
|
|
214
220
|
@raise_on_context_miss = boolean
|
|
215
221
|
end
|
|
216
222
|
|
|
223
|
+
#
|
|
224
|
+
# Context access security level
|
|
225
|
+
#
|
|
226
|
+
|
|
227
|
+
# What object methods could be accessed from the template?
|
|
228
|
+
# This level determines the security restrictions for method access to protect against unsafe reflection
|
|
229
|
+
# Possible values are integers from 1 to 5:
|
|
230
|
+
#
|
|
231
|
+
# 1 - All methods allowed (use only with trusted templates)
|
|
232
|
+
# 2 - Reflection-related methods are blacklisted (default)
|
|
233
|
+
# 3 - All inherited or reflection-related methods are prohibited
|
|
234
|
+
# 4 - Access allowed only for Hash elements
|
|
235
|
+
# 5 - Access allowed only for Hash elements, only numbers, strings, hashes and functions allowed
|
|
236
|
+
|
|
237
|
+
def self.context_access_security_level
|
|
238
|
+
@context_access_security_level
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def self.context_access_security_level=(integer)
|
|
242
|
+
@context_access_security_level = integer
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Instance level version of `Mustache.context_access_security_level`
|
|
246
|
+
def context_access_security_level
|
|
247
|
+
self.class.context_access_security_level || @context_access_security_level
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def context_access_security_level=(integer)
|
|
251
|
+
@context_access_security_level = integer
|
|
252
|
+
end
|
|
253
|
+
|
|
217
254
|
|
|
218
255
|
#
|
|
219
256
|
# View Namespace
|
data/lib/mustache/utils.rb
CHANGED
data/lib/mustache/version.rb
CHANGED
data/lib/mustache.rb
CHANGED
|
@@ -187,14 +187,11 @@ class Mustache
|
|
|
187
187
|
# reading templates from a database. It will be rendered by the
|
|
188
188
|
# context, so all you need to do is return a string.
|
|
189
189
|
def partial(name)
|
|
190
|
-
|
|
190
|
+
partialpath = template_path.map{|p| "#{p}/#{name}.#{template_extension}" }.find{|pf| File.readable? pf}
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
raise if raise_on_context_miss?
|
|
196
|
-
""
|
|
197
|
-
end
|
|
192
|
+
raise RuntimeError.new("Can't find partial #{name}") if not partialpath and raise_on_context_miss?
|
|
193
|
+
|
|
194
|
+
partialpath ? File.read(partialpath) : ""
|
|
198
195
|
end
|
|
199
196
|
|
|
200
197
|
# Override this to provide custom escaping.
|