xss_shield 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Novell, 2007 Trampoline Systems
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,103 @@
1
+ = XSS Shield
2
+
3
+ This Rails plugin provides automatic cross site scripting
4
+ ({XSS}[http://en.wikipedia.org/wiki/Cross-site_scripting]) protection for your
5
+ views. Once installed, you no longer have to manually and painstakingly sanitize
6
+ all your views with HTML escaping (eg. <tt><%= h(foo) %></tt>). Currently only
7
+ {ERB}[http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html] templates are
8
+ supported.
9
+
10
+ For example with XSS Shield:
11
+ <%= link_to "A & B", "/foo" %>
12
+ will return a +SafeString+:
13
+ <a href="/foo">A &amp; B</a>
14
+ and not a plain, unsafe +String+:
15
+ <a href="/foo">A & B</a>
16
+
17
+ This version has been tested to work with <b><i>Rails 2.1.2</i></b>. Your milage
18
+ may vary.
19
+
20
+ DISCLAIMER: Note that while no effort is spared to ensure that this plugin works as
21
+ advertised, we cannot guarantee that all your views are 100% XSS safe. Use it at
22
+ your own risk, but remember that {bug
23
+ reports}[http://github.com/jamestyj/xss_shield/issues] and patches are welcomed.
24
+
25
+ == How it works
26
+
27
+ It works by subclassing +String+ into +SafeString+. When the ERB engine sees a
28
+ <tt><%= foo %></tt> fragment, it checks if the result of executing +foo+ is a
29
+ +SafeString+. If so, it just uses it. Otherwise the string is HTML escaped
30
+ first.
31
+
32
+ The use of +SafeString+ avoids potential double-escaping. For example, with XSS
33
+ Shield, <tt><%= @foo %></tt> is the same as <tt><%= h(@foo) %></tt>.
34
+
35
+ If your string contains HTML that you don't want to escape (and you trust it),
36
+ just append <tt>.xss_safe</tt>:
37
+ <%= "<b>foobar</b>".xss_safe %>
38
+
39
+ It would be cumbersome to require xss_safe every time you use some helper like
40
+ <tt>render(:partial)</tt> or +link_to+, so some helpers are modified to return
41
+ +SafeString+.
42
+
43
+ If you trust your helpers, you can mark them as XSS safe:
44
+
45
+ module Some::Module
46
+ mark_methods_as_xss_safe :text_field, :check_box
47
+ end
48
+
49
+ You may need to manually tweak your helpers, views and layouts to avoid
50
+ unnecessary escaping.
51
+
52
+ == Other template engines
53
+
54
+ Currently only ERB templates is supported, but support for other templating
55
+ engines should be relatively straightforward. It's mostly a matter of changing
56
+ to_s to to_xss_safe in a few places in their source.
57
+
58
+ Patches that add support for other templating engines (along with supporting
59
+ tests) are welcomed.
60
+
61
+ == Running tests
62
+
63
+ This plugin monkey patches ERB in order to do its magic, so it's a good idea to
64
+ at least run the included tests to verify that things work in your environment.
65
+
66
+ You can run the XSS Shield tests by simply running:
67
+
68
+ rake
69
+
70
+ which should generate output looking like this:
71
+
72
+ (in /xss_shield)
73
+ /usr/bin/ruby -I"lib:lib" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" ...
74
+ Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
75
+ Started
76
+ ..........................................................................................
77
+ Finished in 0.163422 seconds.
78
+
79
+ 90 tests, 135 assertions, 0 failures, 0 errors
80
+
81
+ If you place this plugin inside the vendor/plugin directory of your Rails
82
+ application, the test suite will load your application environment by requiring
83
+ RAILS_ROOT/test/test_helper.rb.
84
+
85
+ Of course, you should also verify that your existing application tests still
86
+ pass with XSS Shield enabled.
87
+
88
+ == Bugs and feedback
89
+
90
+ Please report bugs and feature requests
91
+ {here}[http://github.com/jamestyj/xss_shield/issues]. Patches and suggestions
92
+ are welcomed too.
93
+
94
+ == Authors
95
+
96
+ - Updated to support Rails 2.1 and maintained by {James
97
+ Tan}[http://github.com/jamestyj], Novell.
98
+ - {Original code}[http://code.google.com/p/xss-shield/] written by {Tomasz
99
+ Wegrzanowski}[http://code.google.com/u/Tomasz.Wegrzanowski/], Trampoline Systems.
100
+
101
+ == License
102
+
103
+ Copyright (c) 2009 Novell. See MIT-LICENSE in this directory.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the xss-shield plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ begin
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gemspec|
18
+ gemspec.name = 'xss_shield'
19
+ gemspec.summary = 'Protect your Rails site from XSS attacks.'
20
+ gemspec.description = 'This Rails plugin provides automatic cross site ' +
21
+ 'scripting (XSS) protection for your views. Once installed, you no ' +
22
+ 'longer have to manually and painstakingly sanitize all your views ' +
23
+ 'with HTML escaping.'
24
+ gemspec.email = 'jamestyj@gmail.com'
25
+ gemspec.homepage = 'http://github.com/jamestyj/xss_shield'
26
+ gemspec.authors = [ 'James Tan' ]
27
+ end
28
+ Jeweler::GemcutterTasks.new
29
+ rescue LoadError
30
+ puts 'Jeweler (or a dependency) not available. ' +
31
+ 'Install it with: sudo gem install jeweler.'
32
+ end
33
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/init.rb ADDED
@@ -0,0 +1,15 @@
1
+ unless ENV['DISABLE_XSS_SHIELD']
2
+ require 'xss_shield'
3
+ else
4
+ class ::String
5
+ def xss_safe
6
+ self
7
+ end
8
+ end
9
+
10
+ class ::NilClass
11
+ def xss_safe
12
+ self
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,101 @@
1
+ # Create our own ERB compiler to handle <%= %> differently.
2
+ # See /usr/lib/ruby/1.8/erb.erb.
3
+ class XSSProtectedERB < ERB
4
+ class Compiler < ::ERB::Compiler
5
+ def compile(s)
6
+ out = Buffer.new(self)
7
+
8
+ content = ''
9
+ scanner = make_scanner(s)
10
+ scanner.scan do |token|
11
+ if scanner.stag.nil?
12
+ case token
13
+ when PercentLine
14
+ out.push("#{@put_cmd} #{content.dump}") if content.size > 0
15
+ content = ''
16
+ out.push(token.to_s)
17
+ out.cr
18
+ when :cr
19
+ out.cr
20
+ when '<%', '<%=', '<%#'
21
+ scanner.stag = token
22
+ out.push("#{@put_cmd} #{content.dump}") if content.size > 0
23
+ content = ''
24
+ when "\n"
25
+ content << "\n"
26
+ out.push("#{@put_cmd} #{content.dump}")
27
+ out.cr
28
+ content = ''
29
+ when '<%%'
30
+ content << '<%'
31
+ else
32
+ content << token
33
+ end
34
+ else
35
+ case token
36
+ when '%>'
37
+ case scanner.stag
38
+ when '<%'
39
+ if content[-1] == ?\n
40
+ content.chop!
41
+ out.push(content)
42
+ out.cr
43
+ else
44
+ out.push(content)
45
+ end
46
+ when '<%='
47
+ # NOTE: Changed lines
48
+
49
+ # Don't escape yield statements (they should already be safe)
50
+ if content =~ /^[ \t]*yield[ |\(]/
51
+ to_string = 'to_s'
52
+ else
53
+ to_string = 'to_xss_safe'
54
+ end
55
+ out.push("#{@insert_cmd}((#{content}).#{to_string})")
56
+
57
+ # NOTE: End changed lines
58
+ when '<%#'
59
+ # out.push("# #{content.dump}")
60
+ end
61
+ scanner.stag = nil
62
+ content = ''
63
+ when '%%>'
64
+ content << '%>'
65
+ else
66
+ content << token
67
+ end
68
+ end
69
+ end
70
+ out.push("#{@put_cmd} #{content.dump}") if content.size > 0
71
+ out.close
72
+ out.script
73
+ end
74
+
75
+ end
76
+
77
+ def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
78
+ @safe_level = safe_level
79
+ # NOTE: Changed lines
80
+
81
+ compiler = XSSProtectedERB::Compiler.new(trim_mode)
82
+
83
+ # NOTE: End changed lines
84
+ set_eoutvar(compiler, eoutvar)
85
+ @src = compiler.compile(str)
86
+ @filename = nil
87
+ end
88
+ end
89
+
90
+ # Use our own ERB handler.
91
+ # See /usr/lib/ruby/gems/1.8/gems/actionpack-2.1.0/lib/action_view/template_handlers/erb.rb.
92
+ module ActionView
93
+ module TemplateHandlers
94
+ class ERB < TemplateHandler
95
+ def compile(template)
96
+ ::XSSProtectedERB.new(template.source, nil, @view.erb_trim_mode).src
97
+ end
98
+ end
99
+ end
100
+ end
101
+
@@ -0,0 +1,42 @@
1
+ class SafeString < String
2
+ def to_s
3
+ self
4
+ end
5
+ def to_xss_safe
6
+ self
7
+ end
8
+ end
9
+
10
+ class String
11
+ def xss_safe
12
+ SafeString.new(self)
13
+ end
14
+ end
15
+
16
+ class NilClass
17
+ def xss_safe
18
+ self
19
+ end
20
+ end
21
+
22
+ # ERB::Util.h and (include ERB::Util; h) are different methods
23
+ module ERB::Util
24
+ class <<self
25
+ def h_with_xss_protection(*args)
26
+ h_without_xss_protection(*args).xss_safe
27
+ end
28
+ alias_method_chain :h, :xss_protection
29
+ end
30
+
31
+ def h_with_xss_protection(*args)
32
+ h_without_xss_protection(*args).xss_safe
33
+ end
34
+ alias_method_chain :h, :xss_protection
35
+ end
36
+
37
+ class Object
38
+ def to_xss_safe
39
+ ERB::Util.h(to_s).xss_safe
40
+ end
41
+ end
42
+
@@ -0,0 +1,118 @@
1
+ class Module
2
+ def mark_methods_as_xss_safe(*ms)
3
+ ms.each do |m|
4
+ begin
5
+ instance_method("#{m}_with_xss_protection")
6
+ rescue NameError
7
+ define_method :"#{m}_with_xss_protection" do |*args|
8
+ send(:"#{m}_without_xss_protection", *args).xss_safe
9
+ end
10
+ alias_method_chain m, :xss_protection
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ # Mark known helpers as xss_safe only if their arguments are guaranteed to be
17
+ # safe. Don't mark methods that take a block as xss_safe.
18
+ class ActionView::Base
19
+ # ActionView::Helpers::AssetTagHelper
20
+ mark_methods_as_xss_safe :auto_discovery_link_tag,
21
+ :javascript_include_tag,
22
+ :stylesheet_link_tag,
23
+ :image_tag
24
+
25
+ # ActionView::Helpers::JavaScriptHelper
26
+ mark_methods_as_xss_safe :link_to_function,
27
+ :button_to_function,
28
+ :javascript_tag
29
+
30
+ # ActionView::Helpers::FormHelper
31
+ mark_methods_as_xss_safe :check_box,
32
+ :file_field,
33
+ :hidden_field,
34
+ :label,
35
+ :password_field,
36
+ :radio_button,
37
+ :text_area,
38
+ :text_field
39
+
40
+ # ActionView::Helpers::FormTagHelper
41
+ mark_methods_as_xss_safe :check_box_tag,
42
+ :file_field_tag,
43
+ :form_tag_html,
44
+ :hidden_field_tag,
45
+ :image_submit_tag,
46
+ :label_tag,
47
+ :password_field_tag,
48
+ :radio_button_tag,
49
+ :select_tag,
50
+ :submit_tag,
51
+ :text_area_tag,
52
+ :text_field_tag
53
+
54
+ # ActionView::Helpers::FormOptionsHelper
55
+ mark_methods_as_xss_safe :select,
56
+ :options_for_select,
57
+ :collection_select,
58
+ :country_select,
59
+ :time_zone_select,
60
+ :options_from_collection_for_select,
61
+ :option_groups_from_collection_for_select,
62
+ :country_options_for_select,
63
+ :time_zone_options_for_select
64
+
65
+ # ActionView::Helpers::PrototypeHelper
66
+ mark_methods_as_xss_safe :submit_to_remote
67
+
68
+ # ActionView::Helpers::ActiveRecordHelper
69
+ mark_methods_as_xss_safe :error_message_on,
70
+ :error_messages_for,
71
+ :input
72
+
73
+ # ActionView::Helpers::DateHelper
74
+ mark_methods_as_xss_safe :date_select,
75
+ :datetime_select,
76
+ :select_date,
77
+ :select_datetime,
78
+ :select_time,
79
+ :select_month,
80
+ :select_minute,
81
+ :select_hour,
82
+ :select_day,
83
+ :select_year,
84
+ :select_second,
85
+ :time_select
86
+
87
+ # ActionView::Helpers::UrlHelper
88
+ mark_methods_as_xss_safe :mail_to
89
+
90
+ # General
91
+ mark_methods_as_xss_safe :render
92
+
93
+ def link_to_with_xss_protection(text, *args)
94
+ link_to_without_xss_protection(text.to_xss_safe, *args).xss_safe
95
+ end
96
+ alias_method_chain :link_to, :xss_protection
97
+
98
+ def button_to_with_xss_protection(text, *args)
99
+ button_to_without_xss_protection(text.to_xss_safe, *args).xss_safe
100
+ end
101
+ alias_method_chain :button_to, :xss_protection
102
+ end
103
+
104
+ # AssetPackager plugin.
105
+ if defined? Synthesis
106
+ module Synthesis::AssetPackageHelper
107
+ mark_methods_as_xss_safe :stylesheet_link_merged,
108
+ :javascript_include_merged
109
+ end
110
+ end
111
+
112
+ # WillPaginate plugin.
113
+ if defined? WillPaginate
114
+ module WillPaginate::ViewHelpers
115
+ mark_methods_as_xss_safe :will_paginate
116
+ end
117
+ end
118
+
data/lib/xss_shield.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'xss_shield/safe_string'
2
+ require 'xss_shield/erb_hacks'
3
+ require 'xss_shield/secure_helpers'
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from ActionView::Helpers::ActiveRecordHelper are properly
4
+ # escaped.
5
+ class ActiveRecordHelper < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @errors = mock()
9
+ foobar = mock()
10
+ foobar.stubs(:collect).returns(['foo&name'])
11
+ Object.stubs(:content_columns).returns(foobar)
12
+ @foo = Object.new
13
+ @foo.stubs(:errors).returns(@errors)
14
+ @options = { :locals => { :@foo => @foo } }
15
+ end
16
+
17
+ def test_error_message_on
18
+ @errors.stubs(:on).with(:bar).returns('foo&bar')
19
+ assert_render({
20
+ %(<%= error_message_on :foo, :bar %>) => %(
21
+ <div class="formError">foo&bar</div>)
22
+ }, @options)
23
+ end
24
+
25
+ def test_error_messages_for
26
+ @errors.stubs(:count).returns(1)
27
+ @errors.stubs(:full_messages).returns('foo&bar')
28
+ assert_render({
29
+ %(<%= error_messages_for :foo %>) => %(
30
+ <div class="errorExplanation" id="errorExplanation"><h2>1 error \
31
+ prohibited this foo from being saved</h2><p>There were problems with the \
32
+ following fields:</p><ul><li>foo&bar</li></ul></div>)
33
+ }, @options)
34
+ end
35
+
36
+ def test_form
37
+ @foo.stubs(:new_record?).returns(true)
38
+ assert_render({
39
+ %(<%= form :foo %>) => %(
40
+ <form action="/test/foobar" method="post">foo&name<input name="commit" \
41
+ type="submit" value="Create"#{XHTML_TAGS}></form>)
42
+ }, @options)
43
+ end
44
+
45
+ def test_input
46
+ @foo.stubs(:column_for_attribute).returns(stub(:type => :string))
47
+ @foo.stubs(:bar).returns('foo&bar&val')
48
+ assert_render({
49
+ %(<%= input :foo, :bar %>) => %(
50
+ <input name="foo[bar]" size="30" type="text" id="foo_bar" value="\
51
+ foo&amp;bar&amp;val" />)
52
+ }, @options)
53
+ end
54
+
55
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from Synthesis::AssetPackagerHelper are properly escaped.
4
+ class AssetPackagerTest < Test::Unit::TestCase
5
+
6
+ $asset_packages_yml = {
7
+ "javascripts" => [{ "base" => [ "foobar" ] }],
8
+ "stylesheets" => [{ "base" => [ "foobar" ] }]
9
+ }
10
+ include Synthesis::AssetPackageHelper
11
+
12
+ rescue NameError
13
+ puts "AssetPackager plugin not found, skipping related tests"
14
+
15
+ else
16
+
17
+ def test_stylesheet_link_merged
18
+ assert_render(
19
+ %(<%= stylesheet_link_merged :base %>) => %(
20
+ <link href="/stylesheets/foobar.css" rel="stylesheet" media="screen"\
21
+ type="text/css"#{XHTML_TAGS}>)
22
+ )
23
+ end
24
+
25
+ def test_javascript_include_merged
26
+ assert_render(
27
+ %(<%= javascript_include_merged :base %>) => %(
28
+ <script type="text/javascript" src="/javascripts/foobar.js"></script>)
29
+ )
30
+ end
31
+
32
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from ActionView::Helpers::AssetTagHelper are properly
4
+ # escaped.
5
+ class AssetTagHelper < Test::Unit::TestCase
6
+
7
+ def test_auto_discovery_link_tag
8
+ assert_render(
9
+ %(<%= auto_discovery_link_tag "foo&bar" %>) => %(
10
+ <link href="/test/foobar" title="FOO&amp;BAR" rel="alternate" type="\
11
+ foo&amp;bar"#{XHTML_TAGS}>))
12
+ end
13
+
14
+ def test_image_path
15
+ assert_render(
16
+ %(<%= image_path "foo&bar" %>) => %(/images/foo&amp;bar))
17
+ end
18
+
19
+ def test_image_tag
20
+ assert_render(
21
+ %(<%= image_tag "foo&bar" %>) => %(
22
+ <img src="/images/foo&amp;bar" alt="Foo&amp;bar"#{XHTML_TAGS}>))
23
+ end
24
+
25
+ def test_javascript_include_tag
26
+ assert_render(
27
+ %(<%= javascript_include_tag "foo&bar" %>) => %(
28
+ <script type="text/javascript" src="/javascripts/foo&amp;bar.js"></script>))
29
+ end
30
+
31
+ def test_javascript_path
32
+ assert_render(
33
+ %(<%= javascript_path "foo&bar" %>) => %(/javascripts/foo&amp;bar.js))
34
+ end
35
+
36
+ # Alias for image_path.
37
+ def test_path_to_image
38
+ assert_render(
39
+ %(<%= path_to_image "foo&bar" %>) => %(/images/foo&amp;bar))
40
+ end
41
+
42
+ # Alias for javascript_path.
43
+ def test_path_to_javascript
44
+ assert_render(
45
+ %(<%= path_to_javascript "foo&bar" %>) => %(/javascripts/foo&amp;bar.js))
46
+ end
47
+
48
+ # Alias for stylesheet_path.
49
+ def test_path_to_stylesheet
50
+ assert_render(
51
+ %(<%= path_to_stylesheet "foo&bar" %>) => %(/stylesheets/foo&amp;bar.css))
52
+ end
53
+
54
+ def test_stylesheet_link_tag
55
+ assert_render(
56
+ %(<%= stylesheet_link_tag "foo&bar" %>) => %(
57
+ <link href="/stylesheets/foo&amp;bar.css" rel="stylesheet" type=\
58
+ "text/css" media="screen"#{XHTML_TAGS}>))
59
+ end
60
+
61
+ def test_stylesheet_path
62
+ assert_render(
63
+ %(<%= stylesheet_path "foo&bar" %>) => %(/stylesheets/foo&amp;bar.css))
64
+ end
65
+
66
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/../test/test_helper'
2
+
3
+ # Test that helpers from ActionView::Helpers::DateHelper are properly
4
+ # escaped.
5
+ class DateHelperTest < Test::Unit::TestCase
6
+
7
+ def test_date_select
8
+ assert_render_has_no_escaped_chars %(<%= date_select :foo, :created_on %>)
9
+ end
10
+
11
+ def test_datetime_select
12
+ assert_render_has_no_escaped_chars(
13
+ %(<%= datetime_select :foo, :created_on %>))
14
+ end
15
+
16
+ def test_distance_of_time_in_words
17
+ assert_render({
18
+ %(<%= distance_of_time_in_words time, time+10 %>) => %(less than a minute)
19
+ }, { :locals => { :time => Time.now } })
20
+ end
21
+
22
+ def test_distance_of_time_in_words_to_now
23
+ assert_render({
24
+ %(<%= distance_of_time_in_words 10 %>) => %(less than a minute)
25
+ })
26
+ end
27
+
28
+ def test_select_date
29
+ assert_render_has_no_escaped_chars %(<%= select_date %>)
30
+ end
31
+
32
+ def test_select_datetime
33
+ assert_render_has_no_escaped_chars %(<%= select_datetime %>)
34
+ end
35
+
36
+ def test_select_day
37
+ assert_render_has_no_escaped_chars %(<%= select_day 1 %>)
38
+ end
39
+
40
+ def test_select_hour
41
+ assert_render_has_no_escaped_chars %(<%= select_hour 1 %>)
42
+ end
43
+
44
+ def test_select_minute
45
+ assert_render_has_no_escaped_chars %(<%= select_minute 1 %>)
46
+ end
47
+
48
+ def test_select_month
49
+ assert_render_has_no_escaped_chars %(<%= select_month 1 %>)
50
+ end
51
+
52
+ def test_select_second
53
+ assert_render_has_no_escaped_chars %(<%= select_second 1 %>)
54
+ end
55
+
56
+ def test_select_time
57
+ assert_render_has_no_escaped_chars %(<%= select_time Time.now %>)
58
+ end
59
+
60
+ def test_select_year
61
+ assert_render_has_no_escaped_chars %(<%= select_year 2000 %>)
62
+ end
63
+
64
+ def test_time_ago_in_words
65
+ assert_render_has_no_escaped_chars %(<%= time_ago_in_words Time.now %>)
66
+ end
67
+
68
+ def test_time_select
69
+ assert_render_has_no_escaped_chars %(<%= time_select :foo, :bar %>)
70
+ end
71
+ end