Class: Rhales::SafeInjectionValidator
- Inherits:
-
Object
- Object
- Rhales::SafeInjectionValidator
- Defined in:
- lib/rhales/hydration/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
-
#at_tag_boundary?(position) ⇒ Boolean
private
Check if position is at a tag boundary (before < or after >).
-
#build_byte_to_char_map(str) ⇒ Hash<Integer, Integer>
private
Builds a mapping from byte positions to character positions for efficient conversion when processing UTF-8 strings with StringScanner.
-
#calculate_unsafe_ranges ⇒ Object
private
-
#initialize(html) ⇒ SafeInjectionValidator
constructor
A new instance of SafeInjectionValidator.
-
#nearest_safe_point_after(position) ⇒ Object
Find the nearest safe injection point after the given position.
-
#nearest_safe_point_before(position) ⇒ Object
Find the nearest safe injection point before the given position.
-
#next_non_whitespace_is_tag?(position) ⇒ Boolean
private
-
#safe_injection_point?(position) ⇒ Boolean
Check if the given position is safe for injection.
Constructor Details
#initialize(html) ⇒ SafeInjectionValidator
Returns a new instance of SafeInjectionValidator.
18 19 20 21 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 18 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 >)
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 86 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 |
#build_byte_to_char_map(str) ⇒ Hash<Integer, Integer> (private)
Builds a mapping from byte positions to character positions for efficient conversion when processing UTF-8 strings with StringScanner.
This method creates a hash where keys are byte positions and values are the corresponding character positions. For multibyte UTF-8 characters, only the starting byte position has an entry in the map.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 126 def build_byte_to_char_map(str) map = {} char_pos = 0 byte_pos = 0 # Iterate through each character (not byte) in the string str.each_char do |char| # Map the starting byte position of this character map[byte_pos] = char_pos # Advance byte position by the byte size of this character byte_pos += char.bytesize char_pos += 1 end # Add final mapping for the end of the string map[byte_pos] = char_pos map end |
#calculate_unsafe_ranges ⇒ Object (private)
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 55 def calculate_unsafe_ranges ranges = [] scanner = StringScanner.new(@html) byte_to_char_map = build_byte_to_char_map(@html) UNSAFE_CONTEXTS.each do |context| scanner.pos = 0 while scanner.scan_until(context[:start]) # Convert byte position to character position using pre-built map byte_start_pos = scanner.pos - scanner.matched.length start_pos = byte_to_char_map[byte_start_pos] # Find the corresponding end tag if scanner.scan_until(context[:end]) # Convert byte position to character position using pre-built map byte_end_pos = scanner.pos end_pos = byte_to_char_map[byte_end_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
43 44 45 46 47 48 49 50 51 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 43 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
32 33 34 35 36 37 38 39 40 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 32 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)
99 100 101 102 103 104 105 106 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 99 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
24 25 26 27 28 29 |
# File 'lib/rhales/hydration/safe_injection_validator.rb', line 24 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 |