Skip to content

Commit ae3f40c

Browse files
committed
Implement RE2 fallback to libpcre
RE2 doesn't support certain features, like negative lookaround, so when a regular expression cannot be compiled with RE2, it's compiled with libpcre instead. This has some runtime cost, as this fallback is implemented with an extra heap object and virtual function calls. When RE2 is not enabled, however, everything works as it did before.
1 parent b767e14 commit ae3f40c

File tree

6 files changed

+140
-15
lines changed

6 files changed

+140
-15
lines changed

src/regex/backend/backend.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* ModSecurity, http://www.modsecurity.org/
3+
* Copyright (c) 2019
4+
*
5+
* You may not use this file except in compliance with
6+
* the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* If any of the files related to licensing are missing or if you have any
11+
* other questions related to licensing please contact Trustwave Holdings, Inc.
12+
* directly using the email address [email protected].
13+
*
14+
*/
15+
#ifndef SRC_REGEX_BACKEND_BACKEND_H_
16+
#define SRC_REGEX_BACKEND_BACKEND_H_
17+
18+
#include <list>
19+
#include <string>
20+
21+
#include "src/regex/regex_match.h"
22+
23+
namespace modsecurity {
24+
namespace regex {
25+
namespace backend {
26+
27+
class Backend {
28+
public:
29+
virtual ~Backend() {}
30+
31+
virtual bool ok(std::string *error = nullptr) const = 0;
32+
33+
virtual std::vector<RegexMatch> searchAll(const std::string& s, bool overlapping = false) const = 0;
34+
virtual bool search(const std::string &s, RegexMatch *m = nullptr, ssize_t max_groups = -1) const = 0;
35+
36+
virtual const std::string& getPattern() const = 0;
37+
};
38+
39+
} // namespace backend
40+
} // namespace regex
41+
} // namespace modsecurity
42+
43+
#endif // SRC_REGEX_BACKEND_BACKEND_H_

src/regex/backend/pcre.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <string>
2323
#include <list>
2424

25+
#include "src/regex/backend/backend.h"
2526
#include "src/regex/regex_match.h"
2627

2728
#ifndef SRC_REGEX_BACKEND_PCRE_H_
@@ -33,7 +34,7 @@ namespace backend {
3334

3435
#ifdef WITH_PCRE
3536

36-
class Pcre {
37+
class Pcre : public Backend {
3738
public:
3839
explicit Pcre(const std::string& pattern_);
3940
~Pcre();
@@ -42,10 +43,10 @@ class Pcre {
4243
Pcre(const Pcre&) = delete;
4344
Pcre& operator=(const Pcre&) = delete;
4445

45-
std::vector<RegexMatch> searchAll(const std::string& s, bool overlapping = false) const;
46-
bool search(const std::string &s, RegexMatch *m = nullptr, ssize_t max_groups = -1) const;
46+
std::vector<RegexMatch> searchAll(const std::string& s, bool overlapping = false) const override;
47+
bool search(const std::string &s, RegexMatch *m = nullptr, ssize_t max_groups = -1) const override;
4748

48-
bool ok(std::string *error = nullptr) const {
49+
bool ok(std::string *error = nullptr) const override {
4950
if (m_pc != NULL) {
5051
return true;
5152
}
@@ -56,7 +57,7 @@ class Pcre {
5657
return false;
5758
}
5859

59-
const std::string& getPattern() const {
60+
const std::string& getPattern() const override {
6061
return pattern;
6162
};
6263
private:

src/regex/backend/re2.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ namespace backend {
2929
static RE2::Options get_re2_options() {
3030
RE2::Options res;
3131

32+
// Re2 is usually used with fallback to libpcre,
33+
// so disable unnecessary stderr noise
34+
res.set_log_errors(false);
35+
3236
res.set_dot_nl(true);
3337

3438
return res;

src/regex/backend/re2.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <string>
2121
#include <list>
2222

23+
#include "src/regex/backend/backend.h"
2324
#include "src/regex/regex_match.h"
2425

2526
#ifndef SRC_REGEX_BACKEND_RE2_H_
@@ -31,18 +32,18 @@ namespace backend {
3132

3233
#ifdef WITH_RE2
3334

34-
class Re2 {
35+
class Re2 : public Backend {
3536
public:
3637
explicit Re2(const std::string& pattern_);
3738

3839
// RE2 class is not copyable, so neither is this
3940
Re2(const Re2&) = delete;
4041
Re2& operator=(const Re2&) = delete;
4142

42-
std::vector<RegexMatch> searchAll(const std::string& s, bool overlapping = false) const;
43-
bool search(const std::string &s, RegexMatch *m = nullptr, ssize_t max_groups = -1) const;
43+
std::vector<RegexMatch> searchAll(const std::string& s, bool overlapping = false) const override;
44+
bool search(const std::string &s, RegexMatch *m = nullptr, ssize_t max_groups = -1) const override;
4445

45-
bool ok(std::string *error = nullptr) const {
46+
bool ok(std::string *error = nullptr) const override {
4647
if (re.ok()) {
4748
return true;
4849
}
@@ -52,7 +53,7 @@ class Re2 {
5253
return false;
5354
}
5455

55-
const std::string& getPattern() const {
56+
const std::string& getPattern() const override {
5657
return re.pattern();
5758
};
5859
private:

src/regex/backend_fallback.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* ModSecurity, http://www.modsecurity.org/
3+
* Copyright (c) 2019
4+
*
5+
* You may not use this file except in compliance with
6+
* the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* If any of the files related to licensing are missing or if you have any
11+
* other questions related to licensing please contact Trustwave Holdings, Inc.
12+
* directly using the email address [email protected].
13+
*
14+
*/
15+
#ifndef SRC_REGEX_BACKEND_FALLBACK_H_
16+
#define SRC_REGEX_BACKEND_FALLBACK_H_
17+
18+
#include <memory>
19+
20+
#include "src/regex/backend/backend.h"
21+
22+
namespace modsecurity {
23+
namespace regex {
24+
25+
template<typename T>
26+
static backend::Backend* compile_regex_fallback(const std::string& pattern) {
27+
return new T(pattern);
28+
}
29+
30+
template<typename T, typename T2, typename... Args>
31+
static backend::Backend* compile_regex_fallback(const std::string& pattern) {
32+
T *regex = new T{pattern};
33+
if (regex->ok()) {
34+
return regex;
35+
} else {
36+
delete regex;
37+
return compile_regex_fallback<T2, Args...>(pattern);
38+
}
39+
}
40+
41+
template<typename... Args>
42+
class BackendFallback : public backend::Backend {
43+
public:
44+
BackendFallback(const std::string& pattern)
45+
: backend(compile_regex_fallback<Args...>(pattern))
46+
{}
47+
48+
virtual bool ok(std::string *error = nullptr) const override {
49+
return backend->ok(error);
50+
}
51+
52+
std::vector<RegexMatch> searchAll(const std::string& s, bool overlapping = false) const override {
53+
return backend->searchAll(s, overlapping);
54+
}
55+
56+
bool search(const std::string &s, RegexMatch *m = nullptr, ssize_t max_groups = -1) const override {
57+
return backend->search(s, m, max_groups);
58+
}
59+
60+
const std::string& getPattern() const override {
61+
return backend->getPattern();
62+
}
63+
private:
64+
std::unique_ptr<backend::Backend> backend;
65+
};
66+
67+
} // namespace regex
68+
} // namespace modsecurity
69+
70+
#endif // SRC_REGEX_BACKEND_FALLBACK_H_

src/regex/regex.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
#include <string>
2121
#include <list>
2222

23+
#include "src/regex/backend/backend.h"
2324
#include "src/regex/backend/pcre.h"
2425
#include "src/regex/backend/re2.h"
2526
#include "src/regex/regex_match.h"
27+
#include "src/regex/backend_fallback.h"
2628

2729
#ifndef SRC_REGEX_REGEX_H_
2830
#define SRC_REGEX_REGEX_H_
@@ -31,12 +33,16 @@
3133
namespace modsecurity {
3234
namespace regex {
3335

34-
#ifdef WITH_PCRE
35-
using selectedBackend = backend::Pcre;
36-
#elif WITH_RE2
37-
using selectedBackend = backend::Re2;
36+
#if defined(WITH_PCRE) && defined(WITH_RE2)
37+
using selectedBackend = BackendFallback<
38+
backend::Re2, backend::Pcre
39+
>;
40+
#elif defined(WITH_RE2)
41+
using selectedBackend = backend::Re2;
42+
#elif defined(WITH_PCRE)
43+
using selectedBackend = backend::Pcre;
3844
#else
39-
#error "no regex backend selected"
45+
#error "No regexp backend available. Enable either libpcre or re2."
4046
#endif
4147

4248
class Regex : public selectedBackend {

0 commit comments

Comments
 (0)