Class: Gitlab::UntrustedRegexp

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/untrusted_regexp.rb,
lib/gitlab/untrusted_regexp/ruby_syntax.rb

Overview

An untrusted regular expression is any regexp containing patterns sourced from user input.

Ruby's built-in regular expression library allows patterns which complete in exponential time, permitting denial-of-service attacks.

Not all regular expression features are available in untrusted regexes, and there is a strict limit on total execution time. See the RE2 documentation at github.com/google/re2/wiki/Syntax for more details.

Defined Under Namespace

Classes: RubySyntax

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pattern, multiline: false) ⇒ UntrustedRegexp

Returns a new instance of UntrustedRegexp.

Raises:

  • (RegexpError)

18
19
20
21
22
23
24
25
26
# File 'lib/gitlab/untrusted_regexp.rb', line 18

def initialize(pattern, multiline: false)
  if multiline
    pattern = "(?m)#{pattern}"
  end

  @regexp = RE2::Regexp.new(pattern, log_errors: false)

  raise RegexpError, regexp.error unless regexp.ok?
end

Class Method Details

.with_fallback(pattern, multiline: false) ⇒ Object

Handles regular expressions with the preferred RE2 library where possible via UntustedRegex. Falls back to Ruby's built-in regular expression library when the syntax would be invalid in RE2.

One difference between these is `(?m)` multi-line mode. Ruby regex enables this by default, but also handles `^` and `$` differently. See: www.regular-expressions.info/modifiers.html


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/gitlab/untrusted_regexp.rb', line 61

def self.with_fallback(pattern, multiline: false)
  UntrustedRegexp.new(pattern, multiline: multiline)
rescue RegexpError
  raise if Feature.enabled?(:disable_unsafe_regexp)

  if Feature.enabled?(:ci_unsafe_regexp_logger, type: :ops)
    Gitlab::AppJsonLogger.info(
      class: self.name,
      regexp: pattern.to_s,
      fabricated: 'unsafe ruby regexp'
    )
  end

  Regexp.new(pattern)
end

Instance Method Details

#==(other) ⇒ Object


50
51
52
# File 'lib/gitlab/untrusted_regexp.rb', line 50

def ==(other)
  self.source == other.source
end

#match(text) ⇒ Object


38
39
40
# File 'lib/gitlab/untrusted_regexp.rb', line 38

def match(text)
  scan_regexp.match(text)
end

#match?(text) ⇒ Boolean

Returns:

  • (Boolean)

42
43
44
# File 'lib/gitlab/untrusted_regexp.rb', line 42

def match?(text)
  text.present? && scan(text).present?
end

#replace(text, rewrite) ⇒ Object


46
47
48
# File 'lib/gitlab/untrusted_regexp.rb', line 46

def replace(text, rewrite)
  RE2.Replace(text, regexp, rewrite)
end

#replace_all(text, rewrite) ⇒ Object


28
29
30
# File 'lib/gitlab/untrusted_regexp.rb', line 28

def replace_all(text, rewrite)
  RE2.GlobalReplace(text, regexp, rewrite)
end

#scan(text) ⇒ Object


32
33
34
35
36
# File 'lib/gitlab/untrusted_regexp.rb', line 32

def scan(text)
  matches = scan_regexp.scan(text).to_a
  matches.map!(&:first) if regexp.number_of_capturing_groups == 0
  matches
end