escape_utils 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +47 -88
  4. data/benchmark/html_escape_once.rb +25 -0
  5. data/benchmark/javascript_escape.rb +1 -1
  6. data/benchmark/javascript_unescape.rb +1 -1
  7. data/benchmark/url_decode.rb +28 -0
  8. data/benchmark/url_encode.rb +37 -0
  9. data/benchmark/xml_escape.rb +7 -11
  10. data/ext/escape_utils/escape_utils.c +7 -115
  11. data/ext/escape_utils/houdini.h +3 -5
  12. data/ext/escape_utils/houdini_html_e.c +52 -24
  13. data/ext/escape_utils/houdini_uri_e.c +6 -17
  14. data/ext/escape_utils/houdini_uri_u.c +5 -15
  15. data/ext/escape_utils/houdini_xml_e.c +15 -1
  16. data/lib/escape_utils/html/cgi.rb +10 -8
  17. data/lib/escape_utils/html/erb.rb +1 -10
  18. data/lib/escape_utils/html/haml.rb +1 -7
  19. data/lib/escape_utils/html/rack.rb +3 -3
  20. data/lib/escape_utils/html_safety.rb +13 -0
  21. data/lib/escape_utils/url/cgi.rb +0 -8
  22. data/lib/escape_utils/url/erb.rb +1 -1
  23. data/lib/escape_utils/url/uri.rb +11 -7
  24. data/lib/escape_utils/version.rb +1 -1
  25. data/lib/escape_utils.rb +61 -9
  26. data/test/helper.rb +16 -3
  27. data/test/html/escape_test.rb +41 -39
  28. data/test/html/unescape_test.rb +0 -16
  29. data/test/html_safety_test.rb +1 -27
  30. data/test/javascript/escape_test.rb +0 -5
  31. data/test/query/escape_test.rb +0 -16
  32. data/test/query/unescape_test.rb +2 -18
  33. data/test/uri/unescape_test.rb +2 -2
  34. data/test/uri_component/unescape_test.rb +2 -2
  35. data/test/url/escape_test.rb +0 -16
  36. data/test/url/unescape_test.rb +2 -18
  37. metadata +6 -8
  38. data/benchmark/html_escape.rb +0 -68
  39. data/benchmark/html_unescape.rb +0 -35
  40. data/benchmark/url_escape.rb +0 -56
  41. data/benchmark/url_unescape.rb +0 -50
  42. data/ext/escape_utils/houdini_html_u.c +0 -122
@@ -18,8 +18,8 @@
18
18
  static const char HTML_ESCAPE_TABLE[] = {
19
19
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
20
20
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
21
- 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
22
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
21
+ 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0,
22
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 5, 0,
23
23
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24
24
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25
25
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -35,24 +35,64 @@ static const char HTML_ESCAPE_TABLE[] = {
35
35
  };
36
36
 
37
37
  static const char *HTML_ESCAPES[] = {
38
- "",
39
- """,
40
- "&",
41
- "'",
42
- "/",
43
- "<",
44
- ">"
38
+ "",
39
+ """,
40
+ "&",
41
+ "'",
42
+ "<",
43
+ ">"
45
44
  };
46
45
 
46
+ static const int HTML_ESCAPES_LENGTHS[] = {
47
+ 0,
48
+ 6,
49
+ 5,
50
+ 5,
51
+ 4,
52
+ 4
53
+ };
54
+
55
+ static int
56
+ is_entity(const uint8_t *src, size_t size)
57
+ {
58
+ size_t i = 0;
59
+
60
+ if (size == 0 || src[0] != '&')
61
+ return false;
62
+
63
+ if (size > 16)
64
+ size = 16;
65
+
66
+ if (size >= 4 && src[1] == '#') {
67
+ if (_isdigit(src[2])) {
68
+ for (i = 3; i < size && _isdigit(src[i]); ++i);
69
+ }
70
+ else if ((src[2] == 'x' || src[2] == 'X') && _isxdigit(src[3])) {
71
+ for (i = 4; i < size && _isxdigit(src[i]); ++i);
72
+ }
73
+ else return false;
74
+ }
75
+ else {
76
+ for (i = 1; i < size && _isasciialpha(src[i]); ++i);
77
+ if (i == 1) return false;
78
+ }
79
+
80
+ return i < size && src[i] == ';';
81
+ }
82
+
47
83
  int
48
- houdini_escape_html0(gh_buf *ob, const uint8_t *src, size_t size, int secure)
84
+ houdini_escape_html_once(gh_buf *ob, const uint8_t *src, size_t size)
49
85
  {
50
86
  size_t i = 0, org, esc = 0;
51
87
 
52
88
  while (i < size) {
53
89
  org = i;
54
- while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0)
90
+ while (i < size) {
91
+ esc = HTML_ESCAPE_TABLE[src[i]];
92
+ if (unlikely(esc != 0) && !is_entity(src + i, size - i))
93
+ break;
55
94
  i++;
95
+ }
56
96
 
57
97
  if (i > org) {
58
98
  if (unlikely(org == 0)) {
@@ -69,22 +109,10 @@ houdini_escape_html0(gh_buf *ob, const uint8_t *src, size_t size, int secure)
69
109
  if (unlikely(i >= size))
70
110
  break;
71
111
 
72
- /* The forward slash is only escaped in secure mode */
73
- if (src[i] == '/' && !secure) {
74
- gh_buf_putc(ob, '/');
75
- } else {
76
- gh_buf_puts(ob, HTML_ESCAPES[esc]);
77
- }
112
+ gh_buf_put(ob, HTML_ESCAPES[esc], HTML_ESCAPES_LENGTHS[esc]);
78
113
 
79
114
  i++;
80
115
  }
81
116
 
82
117
  return 1;
83
118
  }
84
-
85
- int
86
- houdini_escape_html(gh_buf *ob, const uint8_t *src, size_t size)
87
- {
88
- return houdini_escape_html0(ob, src, size, 1);
89
- }
90
-
@@ -44,7 +44,7 @@ static const char URI_SAFE[] = {
44
44
 
45
45
  static int
46
46
  escape(gh_buf *ob, const uint8_t *src, size_t size,
47
- const char *safe_table, bool escape_plus)
47
+ const char *safe_table)
48
48
  {
49
49
  static const uint8_t hex_chars[] = "0123456789ABCDEF";
50
50
 
@@ -73,13 +73,9 @@ escape(gh_buf *ob, const uint8_t *src, size_t size,
73
73
  if (i >= size)
74
74
  break;
75
75
 
76
- if (src[i] == ' ' && escape_plus) {
77
- gh_buf_putc(ob, '+');
78
- } else {
79
- hex_str[1] = hex_chars[(src[i] >> 4) & 0xF];
80
- hex_str[2] = hex_chars[src[i] & 0xF];
81
- gh_buf_put(ob, hex_str, 3);
82
- }
76
+ hex_str[1] = hex_chars[(src[i] >> 4) & 0xF];
77
+ hex_str[2] = hex_chars[src[i] & 0xF];
78
+ gh_buf_put(ob, hex_str, 3);
83
79
 
84
80
  i++;
85
81
  }
@@ -90,18 +86,11 @@ escape(gh_buf *ob, const uint8_t *src, size_t size,
90
86
  int
91
87
  houdini_escape_uri(gh_buf *ob, const uint8_t *src, size_t size)
92
88
  {
93
- return escape(ob, src, size, URI_SAFE, false);
89
+ return escape(ob, src, size, URI_SAFE);
94
90
  }
95
91
 
96
92
  int
97
93
  houdini_escape_uri_component(gh_buf *ob, const uint8_t *src, size_t size)
98
94
  {
99
- return escape(ob, src, size, URL_SAFE, false);
100
- }
101
-
102
- int
103
- houdini_escape_url(gh_buf *ob, const uint8_t *src, size_t size)
104
- {
105
- return escape(ob, src, size, URL_SAFE, true);
95
+ return escape(ob, src, size, URL_SAFE);
106
96
  }
107
-
@@ -7,13 +7,13 @@
7
7
  #define hex2c(c) ((c | 32) % 39 - 9)
8
8
 
9
9
  static int
10
- unescape(gh_buf *ob, const uint8_t *src, size_t size, bool unescape_plus)
10
+ unescape(gh_buf *ob, const uint8_t *src, size_t size)
11
11
  {
12
12
  size_t i = 0, org;
13
13
 
14
14
  while (i < size) {
15
15
  org = i;
16
- while (i < size && src[i] != '%' && src[i] != '+')
16
+ while (i < size && src[i] != '%')
17
17
  i++;
18
18
 
19
19
  if (likely(i > org)) {
@@ -31,11 +31,7 @@ unescape(gh_buf *ob, const uint8_t *src, size_t size, bool unescape_plus)
31
31
  if (i >= size)
32
32
  break;
33
33
 
34
- if (src[i++] == '+') {
35
- gh_buf_putc(ob, unescape_plus ? ' ' : '+');
36
- continue;
37
- }
38
-
34
+ i++;
39
35
  if (i + 1 < size && _isxdigit(src[i]) && _isxdigit(src[i + 1])) {
40
36
  unsigned char new_char = (hex2c(src[i]) << 4) + hex2c(src[i + 1]);
41
37
  gh_buf_putc(ob, new_char);
@@ -51,18 +47,12 @@ unescape(gh_buf *ob, const uint8_t *src, size_t size, bool unescape_plus)
51
47
  int
52
48
  houdini_unescape_uri(gh_buf *ob, const uint8_t *src, size_t size)
53
49
  {
54
- return unescape(ob, src, size, false);
50
+ return unescape(ob, src, size);
55
51
  }
56
52
 
57
53
  int
58
54
  houdini_unescape_uri_component(gh_buf *ob, const uint8_t *src, size_t size)
59
55
  {
60
- return unescape(ob, src, size, false);
61
- }
62
-
63
- int
64
- houdini_unescape_url(gh_buf *ob, const uint8_t *src, size_t size)
65
- {
66
- return unescape(ob, src, size, true);
56
+ return unescape(ob, src, size);
67
57
  }
68
58
 
@@ -25,6 +25,20 @@ static const char *LOOKUP_CODES[] = {
25
25
  "&gt;"
26
26
  };
27
27
 
28
+ static const int LOOKUP_CODES_LENGTHS[] = {
29
+ 0,
30
+ 0,
31
+ 0,
32
+ 0,
33
+ 0,
34
+ 1,
35
+ 6,
36
+ 5,
37
+ 6,
38
+ 4,
39
+ 4
40
+ };
41
+
28
42
  static const char CODE_INVALID = 5;
29
43
 
30
44
  static const char XML_LOOKUP_TABLE[] = {
@@ -129,7 +143,7 @@ houdini_escape_xml(gh_buf *ob, const uint8_t *src, size_t size)
129
143
  if (end >= size)
130
144
  break;
131
145
 
132
- gh_buf_puts(ob, LOOKUP_CODES[code]);
146
+ gh_buf_put(ob, LOOKUP_CODES[code], LOOKUP_CODES_LENGTHS[code]);
133
147
  }
134
148
 
135
149
  return 1;
@@ -1,11 +1,13 @@
1
- class CGI
2
- extend ::EscapeUtils::HtmlSafety
3
-
4
- class << self
5
- alias escapeHTML _escape_html
1
+ module EscapeUtils
2
+ module CGIHtmlSafety
3
+ def escapeHTML(html)
4
+ ::EscapeUtils::HtmlSafety.escape_once(html) { |s| super(s) }
5
+ end
6
6
 
7
- def unescapeHTML(s)
8
- EscapeUtils.unescape_html(s.to_s)
7
+ def unescapeHTML(html)
8
+ super(html.to_s)
9
9
  end
10
10
  end
11
- end
11
+ end
12
+
13
+ CGI.singleton_class.prepend(EscapeUtils::CGIHtmlSafety)
@@ -1,10 +1 @@
1
- class ERB
2
- module Util
3
- include ::EscapeUtils::HtmlSafety
4
-
5
- alias html_escape _escape_html
6
- alias h html_escape
7
- module_function :h
8
- module_function :html_escape
9
- end
10
- end
1
+ require 'escape_utils/html/cgi' # ERB delegates to EscapeUtils.escapeHTML
@@ -1,7 +1 @@
1
- module Haml
2
- module Helpers
3
- include ::EscapeUtils::HtmlSafety
4
-
5
- alias html_escape _escape_html
6
- end
7
- end
1
+ require 'escape_utils/html/cgi' # HAML delegates to EscapeUtils.escapeHTML
@@ -1,8 +1,8 @@
1
1
  module Rack
2
2
  module Utils
3
- include ::EscapeUtils::HtmlSafety
4
-
5
- alias escape_html _escape_html
3
+ def escape_html(html)
4
+ ::EscapeUtils::HtmlSafety.escape_once(html) { |s| CGI.escapeHTML(s) }
5
+ end
6
6
  module_function :escape_html
7
7
  end
8
8
  end
@@ -1,6 +1,15 @@
1
1
  module EscapeUtils
2
2
  module HtmlSafety
3
3
  if "".respond_to? :html_safe?
4
+ def self.escape_once(s)
5
+ s = s.to_s
6
+ if s.html_safe?
7
+ s.html_safe
8
+ else
9
+ yield(s).html_safe
10
+ end
11
+ end
12
+
4
13
  def _escape_html(s)
5
14
  if s.html_safe?
6
15
  s.to_s.html_safe
@@ -9,6 +18,10 @@ module EscapeUtils
9
18
  end
10
19
  end
11
20
  else
21
+ def self.escape_once(s)
22
+ yield s.to_s
23
+ end
24
+
12
25
  def _escape_html(s)
13
26
  EscapeUtils.escape_html(s.to_s)
14
27
  end
@@ -1,8 +0,0 @@
1
- class CGI
2
- def self.escape(s)
3
- EscapeUtils.escape_url(s.to_s)
4
- end
5
- def self.unescape(s)
6
- EscapeUtils.unescape_url(s.to_s)
7
- end
8
- end
@@ -1,7 +1,7 @@
1
1
  class ERB
2
2
  module Util
3
3
  def url_encode(s)
4
- EscapeUtils.escape_url(s.to_s)
4
+ EscapeUtils.escape_uri(s.to_s)
5
5
  end
6
6
  alias u url_encode
7
7
  module_function :u
@@ -1,8 +1,12 @@
1
- module URI
2
- def self.escape(s, unsafe=nil)
3
- EscapeUtils.escape_uri(s.to_s)
1
+ require 'uri'
2
+
3
+ if URI.respond_to?(:escape) # Was removed in Ruby 3.0. Let's not bring it back
4
+ module URI
5
+ def self.escape(s, unsafe=nil)
6
+ EscapeUtils.escape_uri(s.to_s)
7
+ end
8
+ def self.unescape(s)
9
+ EscapeUtils.unescape_uri(s.to_s)
10
+ end
4
11
  end
5
- def self.unescape(s)
6
- EscapeUtils.unescape_uri(s.to_s)
7
- end
8
- end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module EscapeUtils
2
- VERSION = "1.2.2"
2
+ VERSION = "1.3.0"
3
3
  end
data/lib/escape_utils.rb CHANGED
@@ -1,22 +1,74 @@
1
+ require 'cgi'
1
2
  require 'escape_utils/escape_utils'
2
3
  require 'escape_utils/version' unless defined? EscapeUtils::VERSION
3
4
 
4
5
  module EscapeUtils
5
6
  extend self
6
7
 
7
- # turn on/off the escaping of the '/' character during HTML escaping
8
- # Escaping '/' is recommended by the OWASP - http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content
9
- # This is because quotes around HTML attributes are optional in most/all modern browsers at the time of writing (10/15/2010)
10
- def self.html_secure
11
- @html_secure
8
+ def html_secure
9
+ warn "EscapeUtils.html_secure is deprecated"
10
+ false
11
+ end
12
+
13
+ def html_secure=(val)
14
+ warn "EscapeUtils.html_secure is deprecated"
12
15
  end
13
- self.html_secure = true
14
16
 
15
17
  # Default String class to return from HTML escaping
16
- def self.html_safe_string_class
17
- @html_safe_string_class
18
+ attr_reader :html_safe_string_class
19
+
20
+ def html_safe_string_class=(klass)
21
+ unless String >= klass
22
+ raise ArgumentError, "EscapeUtils.html_safe_string_class must inherit from ::String"
23
+ end
24
+ @html_safe_string_class = klass
18
25
  end
26
+
19
27
  self.html_safe_string_class = String
20
28
 
21
29
  autoload :HtmlSafety, 'escape_utils/html_safety'
22
- end
30
+
31
+ def self.escape_html_once_as_html_safe(html)
32
+ escaped = escape_html_once(html)
33
+ if String == @html_safe_string_class
34
+ escaped
35
+ else
36
+ escaped = @html_safe_string_class.new(escaped)
37
+ escaped.instance_variable_set(:@html_safe, true)
38
+ escaped
39
+ end
40
+ end
41
+
42
+ def self.escape_html(html, secure = false)
43
+ warn "EscapeUtils.escape_html is deprecated. Use GCI.escapeHTML instead, it's faster"
44
+ CGI.escapeHTML(html)
45
+ end
46
+
47
+ def self.escape_html_as_html_safe(html)
48
+ warn "EscapeUtils.escape_html_as_html_safe is deprecated. Use GCI.escapeHTML(str).html_safe instead, it's faster"
49
+
50
+ escaped = CGI.escapeHTML(html)
51
+ if String == @html_safe_string_class
52
+ escaped
53
+ else
54
+ escaped = @html_safe_string_class.new(escaped)
55
+ escaped.instance_variable_set(:@html_safe, true)
56
+ escaped
57
+ end
58
+ end
59
+
60
+ def self.unescape_html(html)
61
+ warn "EscapeUtils.unescape_html is deprecated. Use GCI.unescapeHTML instead, performance is similar"
62
+ CGI.unescapeHTML(html)
63
+ end
64
+
65
+ def self.escape_url(string)
66
+ warn "EscapeUtils.escape_url is deprecated. Use CGI.escape instead, performance is similar"
67
+ CGI.escape(string)
68
+ end
69
+
70
+ def self.unescape_url(string)
71
+ warn "EscapeUtils.unescape_url is deprecated. Use CGI.unescape instead, performance is similar"
72
+ CGI.unescape(string)
73
+ end
74
+ end
data/test/helper.rb CHANGED
@@ -1,11 +1,24 @@
1
1
  # Basic test environment.
2
2
 
3
- # blah fuck this
4
- require 'rubygems' if !defined?(Gem)
5
- require 'bundler/setup'
3
+ module HideOwnWarnings
4
+ def warn(message)
5
+ unless message.include?("EscapeUtils")
6
+ super
7
+ end
8
+ end
9
+ end
10
+ Warning.prepend(HideOwnWarnings)
6
11
 
12
+ require 'bundler/setup'
7
13
  require 'escape_utils'
8
14
 
15
+ require 'active_support'
16
+ require 'active_support/json'
17
+ require "active_support/core_ext/string/output_safety"
18
+
19
+ require 'action_view'
20
+ require 'action_view/helpers'
21
+
9
22
  # bring in minitest
10
23
  require 'minitest/autorun'
11
24
 
@@ -1,9 +1,18 @@
1
1
  require File.expand_path("../../helper", __FILE__)
2
2
 
3
- class MyCustomHtmlSafeString < String
4
- end
5
-
6
3
  class HtmlEscapeTest < Minitest::Test
4
+ MyCustomHtmlSafeString = Class.new(String)
5
+
6
+ def setup
7
+ @_previous_safe = EscapeUtils.html_secure
8
+ @_previous_class = EscapeUtils.html_safe_string_class
9
+ end
10
+
11
+ def teardown
12
+ EscapeUtils.html_secure = @_previous_safe
13
+ EscapeUtils.html_safe_string_class = @_previous_class
14
+ end
15
+
7
16
  def test_escape_source_encoding_is_maintained
8
17
  source = 'foobar'
9
18
  str = EscapeUtils.escape_html_as_html_safe(source)
@@ -29,38 +38,53 @@ class HtmlEscapeTest < Minitest::Test
29
38
  end
30
39
 
31
40
  def test_escape_basic_html_with_secure
32
- assert_equal "&lt;some_tag&#47;&gt;", EscapeUtils.escape_html("<some_tag/>")
41
+ assert_equal "&lt;some_tag/&gt;", EscapeUtils.escape_html("<some_tag/>")
33
42
 
34
- secure_before = EscapeUtils.html_secure
35
43
  EscapeUtils.html_secure = true
36
- assert_equal "&lt;some_tag&#47;&gt;", EscapeUtils.escape_html("<some_tag/>")
37
- EscapeUtils.html_secure = secure_before
44
+ assert_equal "&lt;some_tag/&gt;", EscapeUtils.escape_html("<some_tag/>")
38
45
  end
39
46
 
40
47
  def test_escape_basic_html_without_secure
41
48
  assert_equal "&lt;some_tag/&gt;", EscapeUtils.escape_html("<some_tag/>", false)
42
49
 
43
- secure_before = EscapeUtils.html_secure
44
50
  EscapeUtils.html_secure = false
45
51
  assert_equal "&lt;some_tag/&gt;", EscapeUtils.escape_html("<some_tag/>")
46
- EscapeUtils.html_secure = secure_before
47
52
  end
48
53
 
49
54
  def test_escape_double_quotes
50
- assert_equal "&lt;some_tag some_attr=&quot;some value&quot;&#47;&gt;", EscapeUtils.escape_html("<some_tag some_attr=\"some value\"/>")
55
+ assert_equal "&lt;some_tag some_attr=&quot;some value&quot;/&gt;", EscapeUtils.escape_html("<some_tag some_attr=\"some value\"/>")
51
56
  end
52
57
 
53
58
  def test_escape_single_quotes
54
- assert_equal "&lt;some_tag some_attr=&#39;some value&#39;&#47;&gt;", EscapeUtils.escape_html("<some_tag some_attr='some value'/>")
59
+ assert_equal "&lt;some_tag some_attr=&#39;some value&#39;/&gt;", EscapeUtils.escape_html("<some_tag some_attr='some value'/>")
55
60
  end
56
61
 
57
62
  def test_escape_ampersand
58
- assert_equal "&lt;b&gt;Bourbon &amp; Branch&lt;&#47;b&gt;", EscapeUtils.escape_html("<b>Bourbon & Branch</b>")
59
- end
60
-
61
- def test_returns_original_if_not_escaped
62
- str = 'foobar'
63
- assert_equal str.object_id, EscapeUtils.escape_html(str).object_id
63
+ assert_equal "&lt;b&gt;Bourbon &amp; Branch&lt;/b&gt;", EscapeUtils.escape_html("<b>Bourbon & Branch</b>")
64
+ end
65
+
66
+ def test_escape_html_once
67
+ {
68
+ '&<' => '&amp;&lt;',
69
+ '&amp;&lt;&x;' => '&amp;&lt;&x;',
70
+ '&amp' => '&amp;amp',
71
+ '&!;' => '&amp;!;',
72
+ '&#0;' => '&#0;',
73
+ '&#10;' => '&#10;',
74
+ '&#10' => '&amp;#10',
75
+ '&#10000000000;' => '&#10000000000;',
76
+ '&#x0;' => '&#x0;',
77
+ '&#xf0;' => '&#xf0;',
78
+ '&#xf0' => '&amp;#xf0',
79
+ '&#x;' => '&amp;#x;',
80
+ '&#xfoo;' => '&amp;#xfoo;',
81
+ '&#;' => '&amp;#;',
82
+ '&#foo;' => '&amp;#foo;',
83
+ 'foo&amp;bar' => 'foo&amp;bar',
84
+ }.each do |(input, output)|
85
+ assert_equal output, EscapeUtils.escape_html_once(input)
86
+ assert_equal output, EscapeUtils.escape_html_once_as_html_safe(input)
87
+ end
64
88
  end
65
89
 
66
90
  def test_html_safe_escape_default_works
@@ -69,27 +93,21 @@ class HtmlEscapeTest < Minitest::Test
69
93
  end
70
94
 
71
95
  def test_returns_custom_string_class
72
- klass_before = EscapeUtils.html_safe_string_class
73
96
  EscapeUtils.html_safe_string_class = MyCustomHtmlSafeString
74
97
 
75
98
  str = EscapeUtils.escape_html_as_html_safe('foobar')
76
99
  assert_equal 'foobar', str
77
100
  assert_equal MyCustomHtmlSafeString, str.class
78
101
  assert_equal true, str.instance_variable_get(:@html_safe)
79
- ensure
80
- EscapeUtils.html_safe_string_class = klass_before
81
102
  end
82
103
 
83
104
  def test_returns_custom_string_class_when_string_requires_escaping
84
- klass_before = EscapeUtils.html_safe_string_class
85
105
  EscapeUtils.html_safe_string_class = MyCustomHtmlSafeString
86
106
 
87
107
  str = EscapeUtils.escape_html_as_html_safe("<script>")
88
108
  assert_equal "&lt;script&gt;", str
89
109
  assert_equal MyCustomHtmlSafeString, str.class
90
110
  assert_equal true, str.instance_variable_get(:@html_safe)
91
- ensure
92
- EscapeUtils.html_safe_string_class = klass_before
93
111
  end
94
112
 
95
113
  def test_html_safe_string_class_descends_string
@@ -105,22 +123,6 @@ class HtmlEscapeTest < Minitest::Test
105
123
  end
106
124
  end
107
125
 
108
- def test_utf8_or_ascii_input_only
109
- str = "<b>Bourbon & Branch</b>"
110
-
111
- str.force_encoding 'ISO-8859-1'
112
- assert_raises Encoding::CompatibilityError do
113
- EscapeUtils.escape_html(str)
114
- end
115
-
116
- str.force_encoding 'UTF-8'
117
- begin
118
- EscapeUtils.escape_html(str)
119
- rescue Encoding::CompatibilityError => e
120
- assert_nil e, "#{e.class.name} raised, expected not to"
121
- end
122
- end
123
-
124
126
  def test_return_value_is_tagged_as_utf8
125
127
  str = "<b>Bourbon & Branch</b>".encode('utf-8')
126
128
  assert_equal Encoding.find('UTF-8'), EscapeUtils.escape_html(str).encoding
@@ -23,22 +23,6 @@ class HtmlUnescapeTest < Minitest::Test
23
23
  assert_equal "&lt", EscapeUtils.unescape_html("&lt")
24
24
  end
25
25
 
26
- def test_input_must_be_utf8_or_ascii
27
- escaped = EscapeUtils.escape_html("<b>Bourbon & Branch</b>")
28
-
29
- escaped.force_encoding 'ISO-8859-1'
30
- assert_raises Encoding::CompatibilityError do
31
- EscapeUtils.unescape_html(escaped)
32
- end
33
-
34
- escaped.force_encoding 'UTF-8'
35
- begin
36
- EscapeUtils.unescape_html(escaped)
37
- rescue Encoding::CompatibilityError => e
38
- assert_nil e, "#{e.class.name} raised, expected not to"
39
- end
40
- end
41
-
42
26
  def test_return_value_is_tagged_as_utf8
43
27
  escaped = EscapeUtils.escape_html("<b>Bourbon & Branch</b>")
44
28
  assert_equal Encoding.find('UTF-8'), EscapeUtils.unescape_html(escaped).encoding
@@ -1,37 +1,11 @@
1
1
  require File.expand_path("../helper", __FILE__)
2
2
 
3
- class Object
4
- def html_safe?
5
- false
6
- end
7
- end
8
-
9
- class TestSafeBuffer < String
10
- def html_safe?
11
- true
12
- end
13
-
14
- def html_safe
15
- self
16
- end
17
-
18
- def to_s
19
- self
20
- end
21
- end
22
-
23
- class String
24
- def html_safe
25
- TestSafeBuffer.new(self)
26
- end
27
- end
28
-
29
3
  class HtmlEscapeTest < Minitest::Test
30
4
  include EscapeUtils::HtmlSafety
31
5
 
32
6
  def test_marks_escaped_strings_safe
33
7
  escaped = _escape_html("<strong>unsafe</strong>")
34
- assert_equal "&lt;strong&gt;unsafe&lt;&#47;strong&gt;", escaped
8
+ assert_equal "&lt;strong&gt;unsafe&lt;/strong&gt;", escaped
35
9
  assert escaped.html_safe?
36
10
  end
37
11