escape_utils 1.2.2 → 1.3.0

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.
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