Module: Ruby::Rego::Builtins::Regex
- Extended by:
- RegistryHelpers
- Defined in:
- lib/ruby/rego/builtins/regex.rb,
lib/ruby/rego/builtins/regex/replace.rb,
lib/ruby/rego/builtins/regex/matching.rb,
lib/ruby/rego/builtins/regex/template.rb,
lib/ruby/rego/builtins/regex/compilation.rb,
lib/ruby/rego/builtins/regex/go_template.rb,
lib/ruby/rego/builtins/regex/glob_intersection.rb,
lib/ruby/rego/builtins/regex/glob_intersection/trim.rb,
lib/ruby/rego/builtins/regex/glob_intersection/tokenizer.rb,
lib/ruby/rego/builtins/regex/glob_intersection/intersector.rb
Overview
Built-in regex helpers (regex.globs_match).
Defined Under Namespace
Modules: GlobIntersection Classes: GoTemplate
Constant Summary collapse
- REGEX_FUNCTIONS =
{ "regex.match" => { arity: 2, handler: :match }, "regex.is_valid" => { arity: 1, handler: :is_valid }, "regex.split" => { arity: 2, handler: :split }, "regex.find_n" => { arity: 3, handler: :find_n }, "regex.find_all_string_submatch_n" => { arity: 3, handler: :find_all_string_submatch_n }, "regex.template_match" => { arity: 4, handler: :template_match }, "regex.globs_match" => { arity: 2, handler: :globs_match }, "regex.replace" => { arity: 3, handler: :replace } }.freeze
- REGEX_TIMEOUT_SECONDS =
Wall-clock budget for one regex builtin operation. OPA’s RE2 engine is linear-time and immune to catastrophic backtracking; Ruby’s Onigmo engine is not, so a hostile pattern or input could otherwise hang the evaluator. Applied two ways: as the per-match engine timeout (
Regexp.new(timeout:), interrupts a single catastrophic search) and as an aggregate deadline across the match loop (a cheap-per-match pattern over a long subject is O(matches) engine scans, which the per-match timeout — reset each search — does not bound). Either path yields undefined. Override with RUBY_REGO_REGEX_TIMEOUT (seconds). ENV.fetch("RUBY_REGO_REGEX_TIMEOUT", "1.0").to_f
- MAX_REPLACE_OUTPUT =
Upper bound on
regex.replaceoutput. The per-match engine timeout does not cover template expansion in the gsub block, so many cheap matches with a large replacement template could otherwise expand output super-linearly (matches x template length). Exceeding this yields undefined — a deliberate anti-DoS divergence from OPA, in the same spirit as the regex timeout above. 32_000_000- MAX_REPLACE_WORK =
Upper bound on total template-segment expansions (matches x template segments). Output size alone is insufficient: references that resolve to empty (an out-of-range
$9or unknown${name}) do CPU work per segment while emitting nothing, so a large template over many matches would otherwise run unbounded under the output cap. Exceeding this yields undefined. 32_000_000- MAX_REGEX_SOURCE =
Maximum byte length of a pattern or replacement template. Both are split into a character array (
String#chars) up front — an uninterruptible O(n) C call that no cooperative deadline can bound (the same category as compile cost) — before the per-element processing runs. An oversized source is rejected here by an O(1) bytesize check rather than materialized; the resulting char array then bounds the downstream scan/parse/expand loops, which are all O(source). ~1000x any real pattern or template; exceeding it yields undefined. 1_000_000- GROUP_NAME_CHAR =
A single RE2 group-name character (
[A-Za-z0-9_]). Used to scan a(?P<name>/(?<name>header forward one identifier char at a time, which bounds the scan to the name’s length and stops at the first non-identifier — keeping pattern preprocessing linear even for adversarial input like(?P<repeated with no>. /[A-Za-z0-9_]/- GLOBS_MATCH_CONTEXT =
"regex.globs_match"
Class Method Summary collapse
-
.find_all_string_submatch_n(pattern_value, string_value, number_value) ⇒ Ruby::Rego::ArrayValue
:reek:TooManyStatements.
-
.find_n(pattern_value, string_value, number_value) ⇒ Ruby::Rego::ArrayValue
-
.globs_match(glob1_value, glob2_value) ⇒ Ruby::Rego::BooleanValue
Whether the intersection of two glob patterns matches a non-empty string.
-
.is_valid(pattern_value) ⇒ Ruby::Rego::BooleanValue
-
.match(pattern_value, string_value) ⇒ Ruby::Rego::BooleanValue
-
.register! ⇒ Ruby::Rego::Builtins::BuiltinRegistry
-
.replace(string_value, pattern_value, replacement_value) ⇒ Ruby::Rego::StringValue
Replaces every match of
patterninstringwithreplacement, expanding Go’s Expand template syntax in the replacement ($1/${name}/$$), matching OPA’sregex.replace. -
.split(pattern_value, string_value) ⇒ Ruby::Rego::ArrayValue
Splits a string on a pattern, matching Go’s regexp.Split (n = -1): leading/trailing empty segments are kept, a zero-width match does not produce a trailing empty segment, and empty input yields [””].
-
.template_match(template_value, string_value, delim_start_value, delim_end_value) ⇒ Ruby::Rego::BooleanValue
regex.template_match(template, string, delim_start, delim_end)— the template is literal text with delimited regex sections ({...}by default).
Methods included from RegistryHelpers
Class Method Details
.find_all_string_submatch_n(pattern_value, string_value, number_value) ⇒ Ruby::Rego::ArrayValue
:reek:TooManyStatements
161 162 163 164 165 166 167 |
# File 'lib/ruby/rego/builtins/regex.rb', line 161 def self.find_all_string_submatch_n(pattern_value, string_value, number_value) context = "regex.find_all_string_submatch_n" rows = submatches_for(pattern_value, string_value, context) limit = NumericHelpers.integer_value(number_value, context: context) selected = limit.negative? ? rows : rows.first(limit) ArrayValue.new(selected.map { |row| string_array(row) }) end |
.find_n(pattern_value, string_value, number_value) ⇒ Ruby::Rego::ArrayValue
150 151 152 153 154 |
# File 'lib/ruby/rego/builtins/regex.rb', line 150 def self.find_n(pattern_value, string_value, number_value) matches = matches_for(pattern_value, string_value, "regex.find_n") limit = NumericHelpers.integer_value(number_value, context: "regex.find_n") string_array(limit.negative? ? matches : matches.first(limit)) end |
.globs_match(glob1_value, glob2_value) ⇒ Ruby::Rego::BooleanValue
Whether the intersection of two glob patterns matches a non-empty string. An invalid glob (or a DoS-bound breach) yields undefined, matching OPA.
18 19 20 21 22 23 24 |
# File 'lib/ruby/rego/builtins/regex/glob_intersection.rb', line 18 def self.globs_match(glob1_value, glob2_value) lhs = string_arg(glob1_value, GLOBS_MATCH_CONTEXT) rhs = string_arg(glob2_value, GLOBS_MATCH_CONTEXT) BooleanValue.new(GlobIntersection.non_empty?(lhs, rhs)) rescue GlobIntersection::GlobError => e raise_invalid_glob(e.) end |
.is_valid(pattern_value) ⇒ Ruby::Rego::BooleanValue
108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/ruby/rego/builtins/regex.rb', line 108 def self.is_valid(pattern_value) # OPA's regex.is_valid is total over runtime values: a non-string argument is # `false`, not undefined (unlike the other regex built-ins, which type-error to # undefined). An over-length pattern is also rejected as not-valid rather than # processed (anti-DoS cap; OPA, having no cap, reports a large valid pattern true). return BooleanValue.new(false) unless pattern_value.is_a?(StringValue) pattern = pattern_value.value valid = pattern.valid_encoding? && !source_too_long?(pattern) && compilable?(pattern) BooleanValue.new(valid) end |
.match(pattern_value, string_value) ⇒ Ruby::Rego::BooleanValue
100 101 102 103 104 |
# File 'lib/ruby/rego/builtins/regex.rb', line 100 def self.match(pattern_value, string_value) regexp = compile(pattern_value, "regex.match") string = string_arg(string_value, "regex.match") guarded("regex.match") { BooleanValue.new(regexp.match?(string)) } end |
.register! ⇒ Ruby::Rego::Builtins::BuiltinRegistry
89 90 91 92 93 |
# File 'lib/ruby/rego/builtins/regex.rb', line 89 def self.register! registry = BuiltinRegistry.instance register_configured_functions(registry, REGEX_FUNCTIONS) registry end |
.replace(string_value, pattern_value, replacement_value) ⇒ Ruby::Rego::StringValue
Replaces every match of pattern in string with replacement, expanding
Go’s Expand template syntax in the replacement ($1/${name}/$$), matching
OPA’s regex.replace. A backslash is a literal; only $ is special.
16 17 18 19 20 21 22 23 |
# File 'lib/ruby/rego/builtins/regex/replace.rb', line 16 def self.replace(string_value, pattern_value, replacement_value) regexp, names = compile_pattern(string_arg(pattern_value, "regex.replace"), "regex.replace") string = string_arg(string_value, "regex.replace") replacement = string_arg(replacement_value, "regex.replace") assert_source_length(replacement, "regex.replace") template = GoTemplate.new(replacement, names) guarded("regex.replace") { StringValue.new((string, regexp, template)) } end |
.split(pattern_value, string_value) ⇒ Ruby::Rego::ArrayValue
Splits a string on a pattern, matching Go’s regexp.Split (n = -1): leading/trailing empty segments are kept, a zero-width match does not produce a trailing empty segment, and empty input yields [””].
140 141 142 143 144 |
# File 'lib/ruby/rego/builtins/regex.rb', line 140 def self.split(pattern_value, string_value) regexp = compile(pattern_value, "regex.split") string = string_arg(string_value, "regex.split") guarded("regex.split") { string_array(split_segments(regexp, string)) } end |
.template_match(template_value, string_value, delim_start_value, delim_end_value) ⇒ Ruby::Rego::BooleanValue
regex.template_match(template, string, delim_start, delim_end) — the
template is literal text with delimited regex sections ({...} by default).
OPA requires each delimiter to be a single character; anything else (empty or
multi-character) or an unbalanced section yields undefined.
:reek:LongParameterList :reek:TooManyStatements
16 17 18 19 20 21 22 23 24 25 |
# File 'lib/ruby/rego/builtins/regex/template.rb', line 16 def self.template_match(template_value, string_value, delim_start_value, delim_end_value) context = "regex.template_match" template = string_arg(template_value, context) string = string_arg(string_value, context) delim_start = single_delimiter(delim_start_value, context) delim_end = single_delimiter(delim_end_value, context) assert_source_length(template, context) regexp = compile_pattern(template_source(template, delim_start, delim_end, context), context).first guarded(context) { BooleanValue.new(regexp.match?(string)) } end |