Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
This adds support for NOCASE on string compare / regex (#11152)
  • Loading branch information
zwoop committed Mar 28, 2024
1 parent b1ae239 commit 0fef5a3
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 74 deletions.
2 changes: 2 additions & 0 deletions doc/admin-guide/plugins/header_rewrite.en.rst
Expand Up @@ -606,6 +606,8 @@ AND Indicates that both the current condition and the next must be true.
NOT Inverts the condition.
OR Indicates that either the current condition or the next one must be
true, as contrasted with the default behavior from ``[AND]``.
NOCASE Indicates that the string comparison, or regular expression, should be
case-insensitive. The default is to be case-sensitive.
====== ========================================================================

Operators
Expand Down
1 change: 1 addition & 0 deletions plugins/header_rewrite/CMakeLists.txt
Expand Up @@ -22,6 +22,7 @@ add_atsplugin(
factory.cc
header_rewrite.cc
lulu.cc
matcher.cc
operator.cc
operators.cc
parser.cc
Expand Down
8 changes: 8 additions & 0 deletions plugins/header_rewrite/condition.cc
Expand Up @@ -75,6 +75,14 @@ Condition::initialize(Parser &p)
_mods = static_cast<CondModifiers>(_mods | COND_NOT);
}

// The NOCASE / CASE modifier is a bit special, since it ripples down into the Matchers for
// strings and regexes.
if (p.mod_exist("NOCASE")) {
_mods = static_cast<CondModifiers>(_mods | COND_NOCASE);
} else if (p.mod_exist("CASE")) {
// Nothing to do, this is the default
}

if (p.mod_exist("L")) {
_mods = static_cast<CondModifiers>(_mods | COND_LAST);
}
Expand Down
17 changes: 6 additions & 11 deletions plugins/header_rewrite/condition.h
Expand Up @@ -30,17 +30,6 @@
#include "matcher.h"
#include "parser.h"

// Condition modifiers
enum CondModifiers {
COND_NONE = 0,
COND_OR = 1,
COND_AND = 2,
COND_NOT = 4,
COND_NOCASE = 8, // Not implemented
COND_LAST = 16,
COND_CHAIN = 32 // Not implemented
};

///////////////////////////////////////////////////////////////////////////////
// Base class for all Conditions (this is also the interface)
//
Expand Down Expand Up @@ -93,6 +82,12 @@ class Condition : public Statement
return _mods & COND_LAST;
}

CondModifiers
mods() const
{
return _mods;
}

// Setters
virtual void
set_qualifier(const std::string &q)
Expand Down
43 changes: 23 additions & 20 deletions plugins/header_rewrite/conditions.cc
Expand Up @@ -41,7 +41,7 @@ ConditionStatus::initialize(Parser &p)
Condition::initialize(p);
MatcherType *match = new MatcherType(_cond_op);

match->set(static_cast<TSHttpStatus>(strtol(p.get_arg().c_str(), nullptr, 10)));
match->set(static_cast<TSHttpStatus>(strtol(p.get_arg().c_str(), nullptr, 10)), mods());
_matcher = match;

require_resources(RSRC_SERVER_RESPONSE_HEADERS);
Expand Down Expand Up @@ -77,7 +77,7 @@ ConditionMethod::initialize(Parser &p)
Condition::initialize(p);
MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;

require_resources(RSRC_CLIENT_REQUEST_HEADERS);
Expand Down Expand Up @@ -123,7 +123,7 @@ ConditionRandom::initialize(Parser &p)
_seed = getpid() * tv.tv_usec;
_max = strtol(_qualifier.c_str(), nullptr, 10);

match->set(static_cast<unsigned int>(strtol(p.get_arg().c_str(), nullptr, 10)));
match->set(static_cast<unsigned int>(strtol(p.get_arg().c_str(), nullptr, 10)), mods());
_matcher = match;
}

Expand Down Expand Up @@ -195,7 +195,7 @@ ConditionHeader::initialize(Parser &p)
Condition::initialize(p);
MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;

require_resources(RSRC_CLIENT_REQUEST_HEADERS);
Expand Down Expand Up @@ -259,7 +259,7 @@ ConditionUrl::initialize(Parser &p)
Condition::initialize(p);

MatcherType *match = new MatcherType(_cond_op);
match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}

Expand Down Expand Up @@ -367,6 +367,7 @@ ConditionUrl::eval(const Resources &res)
std::string s;

append_value(s, res);

return static_cast<const Matchers<std::string> *>(_matcher)->test(s);
}

Expand All @@ -377,7 +378,7 @@ ConditionDBM::initialize(Parser &p)
Condition::initialize(p);

MatcherType *match = new MatcherType(_cond_op);
match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;

std::string::size_type pos = _qualifier.find_first_of(',');
Expand Down Expand Up @@ -442,7 +443,7 @@ ConditionCookie::initialize(Parser &p)

MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;

require_resources(RSRC_CLIENT_REQUEST_HEADERS);
Expand Down Expand Up @@ -527,7 +528,7 @@ ConditionIp::initialize(Parser &p)
} else {
MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}
}
Expand Down Expand Up @@ -625,7 +626,7 @@ ConditionTransactCount::initialize(Parser &p)
MatcherType *match = new MatcherType(_cond_op);
std::string const &arg = p.get_arg();

match->set(strtol(arg.c_str(), nullptr, 10));
match->set(strtol(arg.c_str(), nullptr, 10), mods());
_matcher = match;
}

Expand Down Expand Up @@ -715,7 +716,7 @@ ConditionNow::initialize(Parser &p)

MatcherType *match = new MatcherType(_cond_op);

match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10)));
match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10)), mods());
_matcher = match;
}

Expand Down Expand Up @@ -788,13 +789,13 @@ ConditionGeo::initialize(Parser &p)
if (is_int_type()) {
Matchers<int64_t> *match = new Matchers<int64_t>(_cond_op);

match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10)));
match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10)), mods());
_matcher = match;
} else {
// The default is to have a string matcher
Matchers<std::string> *match = new Matchers<std::string>(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}
}
Expand Down Expand Up @@ -869,13 +870,13 @@ ConditionId::initialize(Parser &p)
if (_id_qual == ID_QUAL_REQUEST) {
Matchers<uint64_t> *match = new Matchers<uint64_t>(_cond_op);

match->set(static_cast<uint64_t>(strtol(p.get_arg().c_str(), nullptr, 10)));
match->set(static_cast<uint64_t>(strtol(p.get_arg().c_str(), nullptr, 10)), mods());
_matcher = match;
} else {
// The default is to have a string matcher
Matchers<std::string> *match = new Matchers<std::string>(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}
}
Expand Down Expand Up @@ -952,7 +953,7 @@ ConditionCidr::initialize(Parser &p)

MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}

Expand Down Expand Up @@ -1061,7 +1062,7 @@ ConditionInbound::initialize(Parser &p)
} else {
MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}
}
Expand Down Expand Up @@ -1231,7 +1232,7 @@ ConditionSessionTransactCount::initialize(Parser &p)
MatcherType *match = new MatcherType(_cond_op);
std::string const &arg = p.get_arg();

match->set(strtol(arg.c_str(), nullptr, 10));
match->set(strtol(arg.c_str(), nullptr, 10), mods());
_matcher = match;
}

Expand Down Expand Up @@ -1265,7 +1266,7 @@ ConditionTcpInfo::initialize(Parser &p)
MatcherType *match = new MatcherType(_cond_op);
std::string const &arg = p.get_arg();

match->set(strtol(arg.c_str(), nullptr, 10));
match->set(strtol(arg.c_str(), nullptr, 10), mods());
_matcher = match;
}

Expand Down Expand Up @@ -1336,7 +1337,7 @@ ConditionCache::initialize(Parser &p)
Condition::initialize(p);
MatcherType *match = new MatcherType(_cond_op);

match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}

Expand Down Expand Up @@ -1384,7 +1385,7 @@ ConditionNextHop::initialize(Parser &p)
Condition::initialize(p);

MatcherType *match = new MatcherType(_cond_op);
match->set(p.get_arg());
match->set(p.get_arg(), mods());
_matcher = match;
}

Expand Down Expand Up @@ -1424,6 +1425,8 @@ bool
ConditionNextHop::eval(const Resources &res)
{
std::string s;

append_value(s, res);

return static_cast<const Matchers<std::string> *>(_matcher)->test(s);
}
77 changes: 77 additions & 0 deletions plugins/header_rewrite/matcher.cc
@@ -0,0 +1,77 @@
/* @file
Implementation for creating all values.
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <string>
#include <algorithm>

#include "matcher.h"

// Special case for strings, to make the distinction between regexes and string matching
template <>
void
Matchers<std::string>::set(const std::string &d, CondModifiers mods)
{
_data = d;
if (mods & COND_NOCASE) {
_nocase = true;
}

if (_op == MATCH_REGULAR_EXPRESSION) {
if (!_reHelper.setRegexMatch(_data, _nocase)) {
std::stringstream ss;

ss << _data;
TSError("[%s] Invalid regex: failed to precompile: %s", PLUGIN_NAME, ss.str().c_str());
Dbg(pi_dbg_ctl, "Invalid regex: failed to precompile: %s", ss.str().c_str());
throw std::runtime_error("Malformed regex");
} else {
Dbg(pi_dbg_ctl, "Regex precompiled successfully");
}
}
}

// Special case for strings, to allow for insensitive case comparisons for std::string matchers.
template <>
bool
Matchers<std::string>::test_eq(const std::string &t) const
{
bool r = false;

if (_data.length() == t.length()) {
if (_nocase) {
// ToDo: in C++20, this would be nicer with std::range, e.g.
// r = std::ranges::equal(_data, t, [](char c1, char c2) { return std::tolower(c1) == std::tolower(c2); });
r = std::equal(_data.begin(), _data.end(), t.begin(), [](char c1, char c2) {
return std::tolower(static_cast<unsigned char>(c1)) == std::tolower(static_cast<unsigned char>(c2));
});
} else {
r = (t == _data);
}
}

if (pi_dbg_ctl.on()) {
debug_helper(t, " == ", r);
}

return r;
}

0 comments on commit 0fef5a3

Please sign in to comment.