Skip to content

Commit

Permalink
Auto escape views: implicit and concrete methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
jodosha committed Feb 9, 2015
1 parent d8d35c0 commit a34aa65
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 8 deletions.
2 changes: 2 additions & 0 deletions lib/lotus/view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require 'lotus/view/configuration'
require 'lotus/view/inheritable'
require 'lotus/view/rendering'
require 'lotus/view/escape'
require 'lotus/view/dsl'
require 'lotus/layout'
require 'lotus/presenter'
Expand Down Expand Up @@ -270,6 +271,7 @@ def self.included(base)
extend Inheritable.dup
extend Dsl.dup
extend Rendering.dup
extend Escape.dup

include Utils::ClassAttribute
class_attribute :configuration
Expand Down
44 changes: 44 additions & 0 deletions lib/lotus/view/escape.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require 'lotus/utils/escape'

module Lotus
module View
module Escape
def self.html(input)
case input
when String
Utils::Escape.html(input)
else
input
end
end

def self.extended(base)
base.class_eval do
include Lotus::Utils::ClassAttribute
include InstanceMethods

class_attribute :autoescape_methods
self.autoescape_methods = {}
end
end

def method_added(method_name)
unless autoescape_methods[method_name]
prepend Module.new {
module_eval %{
def #{ method_name }(*args, &blk); ::Lotus::View::Escape.html super; end
}
}

autoescape_methods[method_name] = true
end
end

module InstanceMethods
def raw(input)
Lotus::Utils::Escape::SafeString.new(input)
end
end
end
end
end
17 changes: 10 additions & 7 deletions lib/lotus/view/rendering/scope.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'lotus/utils/escape'
require 'lotus/view/rendering/layout_scope'
require 'lotus/view/rendering/template'
require 'lotus/view/rendering/partial'
Expand Down Expand Up @@ -59,13 +60,15 @@ def respond_to_missing?(m, include_all)

protected
def method_missing(m, *args, &block)
if @view.respond_to?(m)
@view.__send__ m, *args, &block
elsif @locals.key?(m)
@locals[m]
else
super
end
::Lotus::View::Escape.html(
if @view.respond_to?(m)
@view.__send__ m, *args, &block
elsif @locals.key?(m)
@locals[m]
else
super
end
)
end
end
end
Expand Down
32 changes: 32 additions & 0 deletions test/escape_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'test_helper'

describe 'Escape' do
before do
path = Pathname.new(__dir__ + '/fixtures/templates/users/show.html.erb')
template = Lotus::View::Template.new(path)

@user = User.new(%(<script>alert('username')</script>))
@view = Users::Show.new(template, user: @user, code: %(<script>alert('code')</script>))
end

it 'escapes concrete method' do
@view.custom.must_equal %(&lt;script&gt;alert(&apos;custom&apos;)&lt;&#x2F;script&gt;)
end

it 'escapes concrete methods with user input' do
@view.username.must_equal %(&lt;script&gt;alert(&apos;username&apos;)&lt;&#x2F;script&gt;)
end

it 'escapes implicit methods' do
@view.code.must_equal %(&lt;script&gt;alert(&apos;code&apos;)&lt;&#x2F;script&gt;)
end

it "doesn't escape concrete raw methods" do
@view.raw_username.must_equal %(<script>alert('username')</script>)
end

it "doesn't interfer with other views" do
Users::Show.autoescape_methods.must_equal({custom: true, username: true, raw_username: true})
Users::Extra.autoescape_methods.must_equal({username: true})
end
end
39 changes: 38 additions & 1 deletion test/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class Show
format :html

def render
SongWidget.new(song).render
raw SongWidget.new(song).render
end
end
end
Expand Down Expand Up @@ -311,3 +311,40 @@ class JsonIndex < Index
end

Store::View.load!

User = Struct.new(:username)

class UserLayout
include Lotus::Layout

def page_title(username)
"User: #{ username }"
end
end

module Users
class Show
include Lotus::View
layout :user

def custom
%(<script>alert('custom')</script>)
end

def username
user.username
end

def raw_username
raw user.username
end
end

class Extra
include Lotus::View

def username
user.username
end
end
end
15 changes: 15 additions & 0 deletions test/fixtures/templates/user.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype HTML>
<html>
<head>
<title><%= page_title username %></title>
</head>
<body>
<div id="content">
<%= yield %>
</div>

<aside id="sidebar">
<span class="username"><%= username %></span>
</aside>
</body>
</html>
5 changes: 5 additions & 0 deletions test/fixtures/templates/users/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%= custom %>
<%= username %>
<%= code %>
<%= raw_username %>
32 changes: 32 additions & 0 deletions test/integration/automatic_escape_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require 'test_helper'

describe 'Automatic escape' do
before do
@user = User.new(%(<script>alert('username')</script>))
@rendered = Users::Show.render(format: :html, user: @user, code: %(<script>alert('code')</script>))
end

it 'escapes concrete methods' do
@rendered.must_match %(&lt;script&gt;alert(&apos;custom&apos;)&lt;&#x2F;script&gt;)
end

it 'escapes concrete methods with user input' do
@rendered.must_match %(&lt;script&gt;alert(&apos;username&apos;)&lt;&#x2F;script&gt;)
end

it 'escapes implicit methods' do
@rendered.must_match %(&lt;script&gt;alert(&apos;code&apos;)&lt;&#x2F;script&gt;)
end

it "doesn't escape concrete raw methods" do
@rendered.must_match %(<script>alert('username')</script>)
end

it 'escapes concrete methods in layout' do
@rendered.must_match %(<span class="username">&lt;script&gt;alert(&apos;username&apos;)&lt;&#x2F;script&gt;</span>)
end

it 'escapes concrete helpers in layout' do
@rendered.must_match %(<title>User: &lt;script&gt;alert(&apos;username&apos;)&lt;&#x2F;script&gt;</title>)
end
end

0 comments on commit a34aa65

Please sign in to comment.