Class: Rhales::SafeInjectionValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/rhales/safe_injection_validator.rb

Overview

Validates whether a hydration injection point is safe within HTML context Prevents injection inside script tags, style tags, comments, or other unsafe locations

Constant Summary collapse

UNSAFE_CONTEXTS =
[
  { start: /<script\b[^>]*>/i, end: /<\/script>/i },
  { start: /<style\b[^>]*>/i, end: /<\/style>/i },
  { start: /<!--/, end: /-->/ },
  { start: /<!\[CDATA\[/, end: /\]\]>/ }
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(html) ⇒ SafeInjectionValidator

Returns a new instance of SafeInjectionValidator.



14
15
16
17
# File 'lib/rhales/safe_injection_validator.rb', line 14

def initialize(html)
  @html = html
  @unsafe_ranges = calculate_unsafe_ranges
end

Instance Method Details

#at_tag_boundary?(position) ⇒ Boolean (private)

Check if position is at a tag boundary (before < or after >)

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/rhales/safe_injection_validator.rb', line 77

def at_tag_boundary?(position)
  return true if position == 0 || position == @html.length

  char_before = position > 0 ? @html[position - 1] : nil
  char_at = @html[position]

  # Safe positions:
  # - Right after a closing >
  # - Right before an opening <
  # - At whitespace boundaries between tags
  char_before == '>' || char_at == '<' || (char_at&.match?(/\s/) && next_non_whitespace_is_tag?(position))
end

#calculate_unsafe_rangesObject (private)



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rhales/safe_injection_validator.rb', line 51

def calculate_unsafe_ranges
  ranges = []
  scanner = StringScanner.new(@html)

  UNSAFE_CONTEXTS.each do |context|
    scanner.pos = 0

    while scanner.scan_until(context[:start])
      start_pos = scanner.pos - scanner.matched.length

      # Find the corresponding end tag
      if scanner.scan_until(context[:end])
        end_pos = scanner.pos
        ranges << (start_pos...end_pos)
      else
        # If no closing tag found, consider rest of document unsafe
        ranges << (start_pos...@html.length)
        break
      end
    end
  end

  ranges
end

#nearest_safe_point_after(position) ⇒ Object

Find the nearest safe injection point after the given position



39
40
41
42
43
44
45
46
47
# File 'lib/rhales/safe_injection_validator.rb', line 39

def nearest_safe_point_after(position)
  # Work forwards from position to find a safe point
  (position...@html.length).each do |pos|
    return pos if safe_injection_point?(pos) && at_tag_boundary?(pos)
  end

  # If no safe point found after, return nil
  nil
end

#nearest_safe_point_before(position) ⇒ Object

Find the nearest safe injection point before the given position



28
29
30
31
32
33
34
35
36
# File 'lib/rhales/safe_injection_validator.rb', line 28

def nearest_safe_point_before(position)
  # Work backwards from position to find a safe point
  (0...position).reverse_each do |pos|
    return pos if safe_injection_point?(pos) && at_tag_boundary?(pos)
  end

  # If no safe point found before, return nil
  nil
end

#next_non_whitespace_is_tag?(position) ⇒ Boolean (private)

Returns:

  • (Boolean)


90
91
92
93
94
95
96
97
# File 'lib/rhales/safe_injection_validator.rb', line 90

def next_non_whitespace_is_tag?(position)
  pos = position
  while pos < @html.length && @html[pos].match?(/\s/)
    pos += 1
  end

  pos < @html.length && @html[pos] == '<'
end

#safe_injection_point?(position) ⇒ Boolean

Check if the given position is safe for injection

Returns:

  • (Boolean)


20
21
22
23
24
25
# File 'lib/rhales/safe_injection_validator.rb', line 20

def safe_injection_point?(position)
  return false if position < 0 || position > @html.length

  # Check if position falls within any unsafe range
  @unsafe_ranges.none? { |range| range.cover?(position) }
end