Repository: trafficserver Updated Branches: refs/heads/master 370ad860d -> 8c148c9e8
TS-3956: Header_rewrite applies strange logic with = operator, this closes #300 Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/8c148c9e Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/8c148c9e Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/8c148c9e Branch: refs/heads/master Commit: 8c148c9e885ea28ead4d46f49350256602b84d82 Parents: 370ad86 Author: Brian Geffon <bri...@apache.org> Authored: Tue Oct 6 00:06:49 2015 -0700 Committer: Brian Geffon <bri...@apache.org> Committed: Tue Oct 6 00:15:20 2015 -0700 ---------------------------------------------------------------------- plugins/header_rewrite/Makefile.am | 6 +- plugins/header_rewrite/header_rewrite_test.cc | 225 +++++++++++++++++++++ plugins/header_rewrite/parser.cc | 141 ++++++++----- plugins/header_rewrite/parser.h | 5 +- 4 files changed, 323 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8c148c9e/plugins/header_rewrite/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/header_rewrite/Makefile.am b/plugins/header_rewrite/Makefile.am index 8a1abf6..5a7acd3 100644 --- a/plugins/header_rewrite/Makefile.am +++ b/plugins/header_rewrite/Makefile.am @@ -31,5 +31,9 @@ header_rewrite_la_SOURCES = \ resources.cc \ ruleset.cc \ statement.cc - + header_rewrite_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) + +bin_PROGRAMS = header_rewrite_test +header_rewrite_test_SOURCES = parser.cc header_rewrite_test.cc +header_rewrite_test_CXXFLAGS = $(AM_CXXFLAGS) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8c148c9e/plugins/header_rewrite/header_rewrite_test.cc ---------------------------------------------------------------------- diff --git a/plugins/header_rewrite/header_rewrite_test.cc b/plugins/header_rewrite/header_rewrite_test.cc new file mode 100644 index 0000000..ebc9e2b --- /dev/null +++ b/plugins/header_rewrite/header_rewrite_test.cc @@ -0,0 +1,225 @@ +/* + 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. +*/ + +/* + * These are misc unit tests for header rewrite + */ + +#include <cstdio> +#include <cstdarg> +#include <parser.h> + +const char PLUGIN_NAME[] = "TEST_header_rewrite"; +const char PLUGIN_NAME_DBG[] = "TEST_dbg_header_rewrite"; + +extern "C" void TSError(const char* fmt, ...) { + char buf[2048]; + int bytes = 0; + va_list args; + va_start (args, fmt); + if((bytes = vsnprintf (buf, sizeof(buf), fmt, args)) > 0) { + fprintf(stderr, "TSError: %s: %.*s\n", PLUGIN_NAME, bytes, buf); + } +} + +extern "C" void TSDebug(const char *tag, const char* fmt, ...) { + char buf[2048]; + int bytes = 0; + va_list args; + va_start (args, fmt); + if((bytes = vsnprintf (buf, sizeof(buf), fmt, args)) > 0) { + fprintf(stdout, "TSDebug: %s: %.*s\n", PLUGIN_NAME, bytes, buf); + } +} + +#define CHECK_EQ(x, y) \ + do { \ + if ( (x) != (y) ) { \ + fprintf(stderr, "CHECK FAILED " #x " != " #y "\n"); \ + return 1; \ + } \ + } while (false); + +class ParserTest : public Parser { +public: + ParserTest(std::string line) : Parser(line) { } + + std::vector<std::string> getTokens() { + return _tokens; + } +}; + +int test_parsing() { + + { + ParserTest p("cond %{READ_REQUEST_HDR_HOOK}"); + CHECK_EQ(p.getTokens().size(), 2); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{READ_REQUEST_HDR_HOOK}"); + } + + { + ParserTest p("cond %{CLIENT-HEADER:Host} =a"); + CHECK_EQ(p.getTokens().size(), 4); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:Host}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], "a"); + } + + { + ParserTest p(" # COMMENT!"); + CHECK_EQ(p.getTokens().size(), 0); + CHECK_EQ(p.empty(), true); + } + + { + ParserTest p("# COMMENT"); + CHECK_EQ(p.getTokens().size(), 0); + CHECK_EQ(p.empty(), true); + } + + { + ParserTest p("cond %{Client-HEADER:Foo} =b"); + CHECK_EQ(p.getTokens().size(), 4); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{Client-HEADER:Foo}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], "b"); + } + + { + ParserTest p("cond %{Client-HEADER:Blah} = x"); + CHECK_EQ(p.getTokens().size(), 4); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{Client-HEADER:Blah}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], "x"); + } + + { + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} = \"shouldnt_ exist _anyway\" [AND]"); + CHECK_EQ(p.getTokens().size(), 5); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], "shouldnt_ exist _anyway"); + CHECK_EQ(p.getTokens()[4], "[AND]"); + } + + { + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} = \"shouldnt_ = _anyway\" [AND]"); + CHECK_EQ(p.getTokens().size(), 5); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], "shouldnt_ = _anyway"); + CHECK_EQ(p.getTokens()[4], "[AND]"); + } + + { + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} =\"=\" [AND]"); + CHECK_EQ(p.getTokens().size(), 5); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], "="); + CHECK_EQ(p.getTokens()[4], "[AND]"); + } + + { + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} =\"\" [AND]"); + CHECK_EQ(p.getTokens().size(), 5); + CHECK_EQ(p.getTokens()[0], "cond"); + CHECK_EQ(p.getTokens()[1], "%{CLIENT-HEADER:non_existent_header}"); + CHECK_EQ(p.getTokens()[2], "="); + CHECK_EQ(p.getTokens()[3], ""); + CHECK_EQ(p.getTokens()[4], "[AND]"); + } + + { + ParserTest p("add-header X-HeaderRewriteApplied true"); + CHECK_EQ(p.getTokens().size(), 3); + CHECK_EQ(p.getTokens()[0], "add-header"); + CHECK_EQ(p.getTokens()[1], "X-HeaderRewriteApplied"); + CHECK_EQ(p.getTokens()[2], "true"); + } + + /* + * test some failure scenarios + */ + + { /* unterminated quote */ + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} =\" [AND]"); + CHECK_EQ(p.getTokens().size(), 0); + } + + { /* quote in a token */ + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} =a\"b [AND]"); + CHECK_EQ(p.getTokens().size(), 0); + } + + return 0; +} + +int test_processing() { + + /* + * These tests are designed to verify that the processing of the parsed input is correct. + */ + { + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} =\"=\" [AND]"); + CHECK_EQ(p.getTokens().size(), 5); + CHECK_EQ(p.get_op(), "CLIENT-HEADER:non_existent_header"); + CHECK_EQ(p.get_arg(), "=="); + CHECK_EQ(p.is_cond(), true); + } + + { + ParserTest p("cond %{CLIENT-HEADER:non_existent_header} = \"shouldnt_ = _anyway\" [AND]"); + CHECK_EQ(p.getTokens().size(), 5); + CHECK_EQ(p.get_op(), "CLIENT-HEADER:non_existent_header"); + CHECK_EQ(p.get_arg(), "=shouldnt_ = _anyway"); + CHECK_EQ(p.is_cond(), true); + } + + { + ParserTest p("add-header X-HeaderRewriteApplied true"); + CHECK_EQ(p.getTokens().size(), 3); + CHECK_EQ(p.get_op(), "add-header"); + CHECK_EQ(p.get_arg(), "X-HeaderRewriteApplied"); + CHECK_EQ(p.get_value(), "true") + CHECK_EQ(p.is_cond(), false); + } + + return 0; +} + +int tests() { + if (test_parsing() || + test_processing()) { + return 1; + } + + return 0; +} + +int main () { + return tests(); +} + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8c148c9e/plugins/header_rewrite/parser.cc ---------------------------------------------------------------------- diff --git a/plugins/header_rewrite/parser.cc b/plugins/header_rewrite/parser.cc index 549e4f7..e7e32c3 100644 --- a/plugins/header_rewrite/parser.cc +++ b/plugins/header_rewrite/parser.cc @@ -28,9 +28,92 @@ #include "parser.h" +Parser::Parser(const std::string &line) : _cond(false), _empty(false) +{ + TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for Parser"); + bool inquote = false; + bool extracting_token = false; + off_t cur_token_start = 0; + size_t cur_token_length = 0; + for (size_t i = 0; i < line.size(); ++i) { + + if (!inquote && + (std::isspace(line[i]) || (line[i] == '=' || line[i] == '>' || line[i] == '<'))) { + if (extracting_token) { + cur_token_length = i - cur_token_start; + + if (cur_token_length) { + _tokens.push_back(line.substr(cur_token_start, cur_token_length)); + } + + extracting_token = false; + } else if (!std::isspace(line[i])) { + /* we got a standalone =, > or < */ + _tokens.push_back(std::string(1, line[i])); + } + continue; /* always eat whitespace */ + } else if (line[i] == '"') { + if (!inquote && !extracting_token) { + inquote = true; + extracting_token = true; + cur_token_start = i + 1; /* eat the leading quote */ + continue; + } else if (inquote && extracting_token) { + cur_token_length = i - cur_token_start; + _tokens.push_back(line.substr(cur_token_start, cur_token_length)); + inquote = false; + extracting_token = false; + } else { + /* malformed */ + TSError("[%s] malformed line \"%s\" ignoring...", PLUGIN_NAME, line.c_str()); + _tokens.clear(); + _empty = true; + return; + } + } else if (!extracting_token) { + if (inquote) + continue; /* just keep eating until we hit the closing quote */ + + if (_tokens.empty() && line[i] == '#') { + // this is a comment line (it may have had leading whitespace before the #) + _empty = true; + break; + } + + if (line[i] == '=' || line[i] == '>' || line[i] == '<') { + /* these are always a seperate token */ + _tokens.push_back(std::string(1, line[i])); + continue; + } + + extracting_token = true; + cur_token_start = i; + } + } + + if (extracting_token) { + if (inquote) { + // unterminated quote, error case. + TSError("[%s] malformed line, unterminated quotation: \"%s\" ignoring...", PLUGIN_NAME, line.c_str()); + _tokens.clear(); + _empty = true; + return; + } else { + /* we hit the end of the line while parsing a token, let's add it */ + _tokens.push_back(line.substr(cur_token_start)); + } + } + + if (_tokens.empty()) { + _empty = true; + } else { + preprocess(_tokens); + } +} + // This is the core "parser", parsing rule sets void -Parser::preprocess(std::vector<std::string> &tokens) +Parser::preprocess(std::vector<std::string> tokens) { // Special case for "conditional" values if (tokens[0].substr(0, 2) == "%{") { @@ -46,9 +129,12 @@ Parser::preprocess(std::vector<std::string> &tokens) std::string s = tokens[0].substr(2, tokens[0].size() - 3); _op = s; - if (tokens.size() > 1) + if (tokens.size() > 2 + && (tokens[1][0] == '=' || tokens[1][0] == '>' || tokens[1][0] == '<')) { // cond + (=/</>) + argument + _arg = tokens[1] + tokens[2]; + } else if (tokens.size() > 1) { _arg = tokens[1]; - else + } else _arg = ""; } else { TSError("[%s] conditions must be embraced in %%{}", PLUGIN_NAME); @@ -93,52 +179,3 @@ Parser::preprocess(std::vector<std::string> &tokens) } } } - - -Parser::Parser(const std::string &line) : _cond(false), _empty(false) -{ - TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for Parser"); - - if (line[0] == '#') { - _empty = true; - } else { - std::string tmp = line; - std::vector<std::string> tokens; - bool in_quotes = false; - int t = 0; - - for (unsigned int i = 0; i < tmp.size(); i++) { - if (tmp[i] == '\\') { - tmp.erase(i, 1); - i++; - } - - if (tmp[i] == '\"') { - tmp.erase(i, 1); - - if (in_quotes) { - in_quotes = false; - } else { - in_quotes = true; - } - } - - if ((tmp[i] == ' ' || i >= tmp.size() - 1) && !in_quotes) { - if (i == tmp.size() - 1) { - i++; - } - std::string s = tmp.substr(t, i - t); - t = i + 1; - if (s.size() > 0) { - tokens.push_back(s); - } - } - } - - if (tokens.empty()) { - _empty = true; - } else { - preprocess(tokens); - } - } -} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8c148c9e/plugins/header_rewrite/parser.h ---------------------------------------------------------------------- diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h index 35be7ec..687321d 100644 --- a/plugins/header_rewrite/parser.h +++ b/plugins/header_rewrite/parser.h @@ -81,7 +81,7 @@ public: } private: - void preprocess(std::vector<std::string> &tokens); + void preprocess(std::vector<std::string> tokens); DISALLOW_COPY_AND_ASSIGN(Parser); bool _cond; @@ -90,6 +90,9 @@ private: std::string _op; std::string _arg; std::string _val; + +protected: + std::vector<std::string> _tokens; };