contextual 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +0 -0
- data/Gemfile +4 -0
- data/README.md +45 -0
- data/Rakefile +11 -0
- data/contextual.gemspec +23 -0
- data/lib/contextual.rb +3 -0
- data/lib/contextual/contextual.rb +95 -0
- data/lib/contextual/rails_erubis.rb +94 -0
- data/lib/contextual/version.rb +3 -0
- data/lib/ext/autoesc.jar +0 -0
- data/lib/ext/guava.jar +0 -0
- data/spec/contextual_spec.rb +70 -0
- metadata +71 -0
data/.gitignore
ADDED
data/.rspec
ADDED
File without changes
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Runtime Contextual Autoescaper
|
2
|
+
|
3
|
+
A JRuby wrapper for [Mike Samuel's contextual HTML autoescaper](https://github.com/mikesamuel/html-contextual-autoescaper-java). Same one as [used by Google's Closure Templates](http://code.google.com/closure/templates/docs/security.html).
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
First, let's define an Erb template:
|
8
|
+
|
9
|
+
```erb
|
10
|
+
<% def helper(obj); "Hello, #{obj['world']}"; end %>
|
11
|
+
|
12
|
+
<div style="color: <%= object['color'] %>">
|
13
|
+
<a href="/<%= object['color'] %>?q=<%= object['world'] %>" onclick="alert('<%= helper(object) %>');return false"><%= helper(object) %></a>
|
14
|
+
<script>(function () { // Sleepy developers put sensitive info in comments.
|
15
|
+
var o = <%= object %>,
|
16
|
+
w = "<%= object['world'] %>";
|
17
|
+
})();</script>
|
18
|
+
</div>
|
19
|
+
```
|
20
|
+
|
21
|
+
Let's load the template and execute it:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
template = Erubis::ContextualEruby.new(template_string)
|
25
|
+
|
26
|
+
object = {"world" => "<Cincinnati>", "color" => "blue"}
|
27
|
+
puts template.result(binding())
|
28
|
+
```
|
29
|
+
|
30
|
+
Output:
|
31
|
+
|
32
|
+
```html
|
33
|
+
<div style="color: blue">
|
34
|
+
<a href="/blue?q=%3cCincinnati%3e" onclick="alert('Hello, \x3cCincinnati\x3e');return false">Hello, <Cincinnati></a>
|
35
|
+
<script>(function () {
|
36
|
+
var o = {'world':'\x3cCincinnati\x3e','color':'blue'},
|
37
|
+
w = "\x3cCincinnati\x3e";
|
38
|
+
})();</script>
|
39
|
+
</div>
|
40
|
+
```
|
41
|
+
|
42
|
+
The safe parts are treated as literal chunks of HTML/CSS/JS, the query string parameters are auto URI encoded, same data is also auto escaped within the JS block, and the rendered object within the javascript block is automatically converted to JSON! Additionally, extra comments are removed, data is properly HTML escaped, and so forth.
|
43
|
+
|
44
|
+
Contextual will also automatically strip variety of injection cases for JS, CSS, and HTML, and give you a [dozen other features](https://github.com/mikesamuel/html-contextual-autoescaper-java/tree/master/src/tests/com/google/autoesc) for free.
|
45
|
+
|
data/Rakefile
ADDED
data/contextual.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "contextual/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "contextual"
|
7
|
+
s.version = Contextual::VERSION
|
8
|
+
s.authors = ["Ilya Grigorik"]
|
9
|
+
s.email = ["ilya@igvita.com"]
|
10
|
+
s.homepage = "https://github.com/igrigorik/contextual"
|
11
|
+
s.summary = "Runtime contextual autoescaper"
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "contextual"
|
15
|
+
s.platform = "java"
|
16
|
+
|
17
|
+
s.add_development_dependency "rspec"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
end
|
data/lib/contextual.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "java"
|
2
|
+
require "ext/guava"
|
3
|
+
require "ext/autoesc"
|
4
|
+
|
5
|
+
java_import com.google.autoesc.HTMLEscapingWriter
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "erubis"
|
9
|
+
|
10
|
+
module Erubis
|
11
|
+
module ContextualEscapeEnhancer
|
12
|
+
|
13
|
+
def self.desc # :nodoc:
|
14
|
+
"switch '<%= %>' to escaped and '<%== %>' to unescaped"
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_expr(src, code, indicator)
|
18
|
+
case indicator
|
19
|
+
when '='
|
20
|
+
@escape ? add_expr_literal(src, code) : add_expr_escaped(src, code)
|
21
|
+
when '=='
|
22
|
+
@escape ? add_expr_escaped(src, code) : add_expr_literal(src, code)
|
23
|
+
when '==='
|
24
|
+
add_expr_debug(src, code)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_text(src, text)
|
29
|
+
if !text.empty?
|
30
|
+
src << " #{@bufvar}.writeSafe '" << text.to_s.gsub("'", "\\\\'") << "';"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_stmt(src, code)
|
35
|
+
src << code
|
36
|
+
src << ';' unless code[-1] == ?\n
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_expr_literal(src, code)
|
40
|
+
src << " #{@bufvar}.writeSafe(" << code << ').to_s;'
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_expr_escaped(src, code)
|
44
|
+
src << " #{@bufvar}.write(" << code << ').to_s;'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ContextualBuffer
|
49
|
+
def initialize
|
50
|
+
@writer = java.io.StringWriter.new
|
51
|
+
@buf = HTMLEscapingWriter.new(@writer)
|
52
|
+
end
|
53
|
+
|
54
|
+
def writeSafe(code)
|
55
|
+
@buf.writeSafe(code)
|
56
|
+
end
|
57
|
+
alias :writeSafe= :writeSafe
|
58
|
+
alias :append= :writeSafe
|
59
|
+
alias :concat :writeSafe
|
60
|
+
alias :<< :writeSafe
|
61
|
+
|
62
|
+
def write(code)
|
63
|
+
@buf.write(code)
|
64
|
+
end
|
65
|
+
alias :write= :write
|
66
|
+
alias :safe_append= :write
|
67
|
+
alias :safe_concat :write
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
@writer.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
@writer.close
|
75
|
+
end
|
76
|
+
|
77
|
+
def presence
|
78
|
+
@writer.to_s.html_safe
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ContextualEruby < Eruby
|
83
|
+
include ContextualEscapeEnhancer
|
84
|
+
|
85
|
+
def add_preamble(src)
|
86
|
+
src << "#{@bufvar} = Erubis::ContextualBuffer.new; "
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_postamble(src)
|
90
|
+
src << "\n" unless src[-1] == ?\n
|
91
|
+
src << "#{@bufvar}.close\n"
|
92
|
+
src << "#{@bufvar}.to_s\n"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ActionView
|
2
|
+
class Template
|
3
|
+
module Handlers
|
4
|
+
|
5
|
+
# class Erubis < ::Erubis::Eruby
|
6
|
+
# def add_preamble(src)
|
7
|
+
# src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# def add_text(src, text)
|
11
|
+
# return if text.empty?
|
12
|
+
# p [:add_text, :safe_concat, text]
|
13
|
+
# src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
17
|
+
#
|
18
|
+
# def add_expr_literal(src, code)
|
19
|
+
# if code =~ BLOCK_EXPR
|
20
|
+
# p [:add_expr_literal, :block_append=, code]
|
21
|
+
#
|
22
|
+
# src << '@output_buffer.append= ' << code
|
23
|
+
# else
|
24
|
+
# p [:add_expr_literal, :append=, code]
|
25
|
+
#
|
26
|
+
# src << '@output_buffer.append= (' << code << ');'
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def add_expr_escaped(src, code)
|
31
|
+
# if code =~ BLOCK_EXPR
|
32
|
+
# p [:add_expr_escaped, :safe_append=, code]
|
33
|
+
#
|
34
|
+
# src << "@output_buffer.safe_append= " << code
|
35
|
+
# else
|
36
|
+
# p [:add_expr_escaped, :safe_concat, code]
|
37
|
+
# src << "@output_buffer.safe_concat((" << code << ").to_s);"
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def add_postamble(src)
|
42
|
+
# src << '@output_buffer.to_s'
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
|
46
|
+
class SafeErubis < ::Erubis::Eruby
|
47
|
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
48
|
+
|
49
|
+
def add_preamble(src)
|
50
|
+
src << "@output_buffer = output_buffer || Erubis::ContextualBuffer.new; "
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_text(src, text)
|
54
|
+
if !text.empty?
|
55
|
+
src << "@output_buffer.concat('" << text.to_s.gsub("'", "\\\\'") << "');"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_expr_literal(src, code)
|
60
|
+
if code =~ BLOCK_EXPR
|
61
|
+
src << '@output_buffer.append= ' << code
|
62
|
+
else
|
63
|
+
src << <<-SRC
|
64
|
+
val = (#{code.to_s});
|
65
|
+
if (val.html_safe?);
|
66
|
+
@output_buffer.append=(val);
|
67
|
+
else;
|
68
|
+
@output_buffer.safe_append=(val);
|
69
|
+
end;
|
70
|
+
SRC
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_expr_escaped(src, code)
|
75
|
+
if code =~ BLOCK_EXPR
|
76
|
+
src << "@output_buffer.append= " << code
|
77
|
+
else
|
78
|
+
src << "@output_buffer.append(" << code << ");"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_postamble(src)
|
83
|
+
src << "@output_buffer.close \n"
|
84
|
+
# src << "p [:CONTEXTUAL,@output_buffer, @output_buffer.to_s, @output_buffer.to_s.html_safe.html_safe?]\n"
|
85
|
+
src << "@output_buffer.to_s.html_safe"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
ERB.erb_implementation = SafeErubis
|
90
|
+
ActionView::OutputBuffer = ::Erubis::ContextualBuffer
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/ext/autoesc.jar
ADDED
Binary file
|
data/lib/ext/guava.jar
ADDED
Binary file
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "contextual"
|
2
|
+
|
3
|
+
TEMPLATE = <<-eos
|
4
|
+
<% def helper(obj); "Hello, \#{obj['world']}"; end %>
|
5
|
+
|
6
|
+
<div style="color: <%= object['color'] %>">
|
7
|
+
<a href="/<%= object['color'] %>?q=<%= object['world'] %>" onclick="alert('<%= helper(object) %>');return false"><%= helper(object) %></a>
|
8
|
+
<script>(function () { // Sleepy developers put sensitive info in comments.
|
9
|
+
var o = <%= object %>,
|
10
|
+
w = "<%= object['world'] %>";
|
11
|
+
})();</script>
|
12
|
+
</div>
|
13
|
+
eos
|
14
|
+
|
15
|
+
EXPECTED = <<-eos
|
16
|
+
|
17
|
+
<div style="color: blue">
|
18
|
+
<a href="/blue?q=%3cCincinnati%3e" onclick="alert('Hello, \\x3cCincinnati\\x3e');return false">Hello, <Cincinnati></a>
|
19
|
+
<script>(function () {
|
20
|
+
var o = {'world':'\\x3cCincinnati\\x3e','color':'blue'},
|
21
|
+
w = "\\x3cCincinnati\\x3e";
|
22
|
+
})();</script>
|
23
|
+
</div>
|
24
|
+
eos
|
25
|
+
|
26
|
+
describe Contextual do
|
27
|
+
|
28
|
+
it "should escape unsafe content" do
|
29
|
+
t = Erubis::ContextualEruby.new(" \
|
30
|
+
<% elements.each do |e| %> \
|
31
|
+
<%= e %> \
|
32
|
+
<% end %> \
|
33
|
+
")
|
34
|
+
|
35
|
+
elements = ['<script>', '&bar', 'style="test"']
|
36
|
+
res = t.result(binding())
|
37
|
+
|
38
|
+
res.should match('<script>')
|
39
|
+
res.should match('&bar')
|
40
|
+
res.should match('style="test"')
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should preserve safe content" do
|
44
|
+
t = Erubis::ContextualEruby.new("<ul><%= '<script>' %></ul>")
|
45
|
+
t.result.should match('<ul><script></ul>')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should allow explicit safe content" do
|
49
|
+
t = Erubis::ContextualEruby.new("<ul><%== '<script>' %></ul>")
|
50
|
+
t.result.should match('<ul><script></ul>')
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should skip comments" do
|
54
|
+
t = Erubis::ContextualEruby.new("<%# some comment %>")
|
55
|
+
t.result.should be_empty
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should render contextual template" do
|
59
|
+
object = {"world" => "<Cincinnati>", "color" => "blue"}
|
60
|
+
template = Erubis::ContextualEruby.new(TEMPLATE)
|
61
|
+
res = template.result(binding())
|
62
|
+
|
63
|
+
# don't worry about trailing whitespace
|
64
|
+
res = res.split("\n").map {|r| r.strip}
|
65
|
+
exp = EXPECTED.split("\n").map {|r| r.strip}
|
66
|
+
|
67
|
+
res.should == exp
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contextual
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: java
|
7
|
+
authors:
|
8
|
+
- Ilya Grigorik
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2152973440 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2152973440
|
25
|
+
description: Runtime contextual autoescaper
|
26
|
+
email:
|
27
|
+
- ilya@igvita.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- .rspec
|
34
|
+
- Gemfile
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- contextual.gemspec
|
38
|
+
- lib/contextual.rb
|
39
|
+
- lib/contextual/contextual.rb
|
40
|
+
- lib/contextual/rails_erubis.rb
|
41
|
+
- lib/contextual/version.rb
|
42
|
+
- lib/ext/autoesc.jar
|
43
|
+
- lib/ext/guava.jar
|
44
|
+
- spec/contextual_spec.rb
|
45
|
+
homepage: https://github.com/igrigorik/contextual
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project: contextual
|
65
|
+
rubygems_version: 1.8.10
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Runtime contextual autoescaper
|
69
|
+
test_files:
|
70
|
+
- spec/contextual_spec.rb
|
71
|
+
has_rdoc:
|