Module: Cybertrain::Html

Defined in:
cybertrain/html.rb

Overview

HTML escaping for controllers and models. Templates escape <%= %> output themselves; these are for building HTML in Ruby, e.g. for render html:.

Class Method Summary collapse

Class Method Details

.escape(value) ⇒ String

Escapes the five characters that matter for HTML text/attribute contexts. Scans bytes (the five are ASCII, so multibyte UTF-8 sequences are never split) and returns the input untouched when nothing needs escaping, since most interpolated values are plain text. Measured 2.7x faster than an each_char loop under Spinel; this runs for every <%= %>.

&, <, >, " and ' become entities.

Parameters:

  • value (String)

Returns:

  • (String)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'cybertrain/html.rb', line 19

def self.escape(value)
  # The parameter is polymorphic under Spinel (callers hand in Strings of
  # more than one static type), which made every getbyte and comparison
  # below a dynamic call: most of this method's time. value.to_s has one
  # static type (every to_s in the program returns a String; SafeString's
  # included, see SafeString#initialize), so the loops compile to plain
  # C, and for a String it is the String itself, not a copy. It is also
  # what comes back when nothing needs escaping, so callers get a typed
  # String rather than the polymorphic parameter. The case keeps value
  # polymorphic: used only through to_s, Spinel narrows it to whatever
  # one caller passes (it once inferred an Integer and turned Strings
  # into "0"). Anything but a String is converted anyway, so the
  # interpolation costs nothing extra.
  str = case value
        when String then value.to_s
        else "#{value.to_s}"
        end
  n = str.bytesize
  i = 0
  # Most values need nothing: find the first byte that does before
  # allocating a buffer at all.
  while i < n
    b = str.getbyte(i)
    break if b == 38 || b == 60 || b == 62 || b == 34 || b == 39
    i += 1
  end
  return str if i == n

  buf = +""
  dirty = false
  start = 0
  while i < n
    b = str.getbyte(i)
    if b == 38 || b == 60 || b == 62 || b == 34 || b == 39
      buf << str.byteslice(start, i - start) if i > start
      case b
      when 38 then buf << "&amp;"
      when 60 then buf << "&lt;"
      when 62 then buf << "&gt;"
      when 34 then buf << "&quot;"
      else buf << "&#39;"
      end
      start = i + 1
      dirty = true
    end
    i += 1
  end
  return str unless dirty

  buf << str.byteslice(start, n - start) if n > start
  buf
end

.safe(str) ⇒ SafeString

Marks trusted HTML as safe, so it is output as it is.

Examples:

render html: Cybertrain::Html.safe("<p>#{Cybertrain::Html.escape(@article.title)}</p>")

Parameters:

  • str (String)

Returns:



78
79
80
# File 'cybertrain/html.rb', line 78

def self.safe(str)
  SafeString.of(str)
end