LCOV - code coverage report
Current view: top level - source/extensions/path/uri_template_lib - uri_template_internal.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 219 256 85.5 %
Date: 2024-01-05 06:35:25 Functions: 21 31 67.7 %

          Line data    Source code
       1             : #include "source/extensions/path/uri_template_lib/uri_template_internal.h"
       2             : 
       3             : #include <optional>
       4             : #include <string>
       5             : #include <type_traits>
       6             : #include <variant>
       7             : #include <vector>
       8             : 
       9             : #include "absl/container/flat_hash_set.h"
      10             : #include "absl/flags/flag.h"
      11             : #include "absl/functional/function_ref.h"
      12             : #include "absl/status/status.h"
      13             : #include "absl/status/statusor.h"
      14             : #include "absl/strings/match.h"
      15             : #include "absl/strings/str_cat.h"
      16             : #include "absl/strings/str_join.h"
      17             : #include "absl/strings/str_replace.h"
      18             : #include "absl/strings/str_split.h"
      19             : #include "absl/strings/string_view.h"
      20             : #include "absl/types/variant.h"
      21             : #include "re2/re2.h"
      22             : 
      23             : namespace Envoy {
      24             : namespace Extensions {
      25             : namespace UriTemplate {
      26             : 
      27             : namespace Internal {
      28             : 
      29             : namespace {
      30             : 
      31             : #ifndef SWIG
      32             : // Silence warnings about missing initializers for members of LazyRE2.
      33             : #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
      34             : #endif
      35             : 
      36             : constexpr unsigned long kPatternMatchingMaxVariablesPerPath = 5;
      37             : constexpr unsigned long kPatternMatchingMaxVariableNameLen = 16;
      38             : constexpr unsigned long kPatternMatchingMinVariableNameLen = 1;
      39             : 
      40             : // Valid pchar from https://datatracker.ietf.org/doc/html/rfc3986#appendix-A
      41             : constexpr absl::string_view kLiteral = "a-zA-Z0-9-._~" // Unreserved
      42             :                                        "%"             // pct-encoded
      43             :                                        "!$&'()+,;"     // sub-delims excluding *=
      44             :                                        ":@"
      45             :                                        "="; // user included "=" allowed
      46             : 
      47             : // Default operator used for the variable when none specified.
      48             : constexpr Operator kDefaultVariableOperator = Operator::PathGlob;
      49             : 
      50             : // Visitor for displaying debug info of a ParsedSegment/Variable.var_match.
      51             : struct ToStringVisitor {
      52             :   template <typename T> std::string operator()(const T& val) const;
      53             : };
      54             : 
      55             : // Formatter used to allow joining variants together with StrJoin.
      56             : struct ToStringFormatter {
      57           0 :   template <typename T> void operator()(std::string* out, const T& t) const {
      58           0 :     absl::StrAppend(out, absl::visit(ToStringVisitor(), t));
      59           0 :   }
      60             : };
      61             : 
      62             : // Visitor for converting a ParsedSegment variant to the regex.
      63             : struct ToRegexPatternVisitor {
      64         882 :   template <typename T> std::string operator()(const T& val) const { return toRegexPattern(val); }
      65             : };
      66             : 
      67             : // Formatter used to allow joining variants together with StrJoin.
      68             : struct ToRegexPatternFormatter {
      69         882 :   template <typename T> void operator()(std::string* out, const T& t) const {
      70         882 :     absl::StrAppend(out, absl::visit(ToRegexPatternVisitor(), t));
      71         882 :   }
      72             : };
      73             : 
      74           0 : std::string toString(const Literal val) { return std::string(val); }
      75             : 
      76           0 : std::string toString(const Operator val) {
      77           0 :   switch (val) {
      78           0 :   case Operator::PathGlob:
      79           0 :     return "*";
      80           0 :   case Operator::TextGlob:
      81           0 :     return "**";
      82           0 :   }
      83           0 :   return "";
      84           0 : }
      85             : 
      86           0 : std::string toString(const Variable val) {
      87           0 :   if (val.match_.empty()) {
      88           0 :     return absl::StrCat("{", val.name_, "}");
      89           0 :   }
      90             : 
      91           0 :   return absl::StrCat("{", val.name_, "=", absl::StrJoin(val.match_, "/", ToStringFormatter()),
      92           0 :                       "}");
      93           0 : }
      94             : 
      95           0 : template <typename T> std::string ToStringVisitor::operator()(const T& val) const {
      96           0 :   return toString(val);
      97           0 : }
      98             : 
      99             : template <typename T>
     100             : absl::StatusOr<T>
     101             : alsoUpdatePattern(absl::FunctionRef<absl::StatusOr<ParsedResult<T>>(absl::string_view)> parse_func,
     102        1354 :                   absl::string_view* patt) {
     103             : 
     104        1354 :   absl::StatusOr<ParsedResult<T>> status = parse_func(*patt);
     105        1354 :   if (!status.ok()) {
     106          73 :     return status.status();
     107          73 :   }
     108        1281 :   ParsedResult<T> result = *std::move(status);
     109             : 
     110        1281 :   *patt = result.unparsed_pattern_;
     111        1281 :   return result.parsed_value_;
     112        1354 : }
     113             : 
     114             : } // namespace
     115             : 
     116           0 : std::string Variable::debugString() const { return toString(*this); }
     117             : 
     118           0 : std::string ParsedPathPattern::debugString() const {
     119           0 :   return absl::StrCat("/", absl::StrJoin(parsed_segments_, "/", ToStringFormatter()), suffix_);
     120           0 : }
     121             : 
     122         660 : bool isValidLiteral(absl::string_view literal) {
     123         660 :   static const std::string* kValidLiteralRegex =
     124         660 :       new std::string(absl::StrCat("^[", kLiteral, "]+$"));
     125         660 :   static const LazyRE2 literal_regex = {kValidLiteralRegex->data()};
     126         660 :   return RE2::FullMatch(literal, *literal_regex);
     127         660 : }
     128             : 
     129         720 : bool isValidRewriteLiteral(absl::string_view literal) {
     130         720 :   static const std::string* kValidLiteralRegex =
     131         720 :       new std::string(absl::StrCat("^[", kLiteral, "/]+$"));
     132         720 :   static const LazyRE2 literal_regex = {kValidLiteralRegex->data()};
     133         720 :   return RE2::FullMatch(literal, *literal_regex);
     134         720 : }
     135             : 
     136        1107 : bool isValidVariableName(absl::string_view variable) {
     137        1107 :   static const LazyRE2 variable_regex = {"^[a-zA-Z][a-zA-Z0-9_]*$"};
     138        1107 :   return RE2::FullMatch(variable, *variable_regex);
     139        1107 : }
     140             : 
     141         660 : absl::StatusOr<ParsedResult<Literal>> parseLiteral(absl::string_view pattern) {
     142         660 :   absl::string_view literal =
     143         660 :       std::vector<absl::string_view>(absl::StrSplit(pattern, absl::MaxSplits('/', 1)))[0];
     144         660 :   absl::string_view unparsed_pattern = pattern.substr(literal.size());
     145         660 :   if (!isValidLiteral(literal)) {
     146          45 :     return absl::InvalidArgumentError("Invalid literal");
     147          45 :   }
     148         615 :   return ParsedResult<Literal>(literal, unparsed_pattern);
     149         660 : }
     150             : 
     151         129 : absl::StatusOr<ParsedResult<Operator>> parseOperator(absl::string_view pattern) {
     152         129 :   if (absl::StartsWith(pattern, "**")) {
     153          43 :     return ParsedResult<Operator>(Operator::TextGlob, pattern.substr(2));
     154          43 :   }
     155          86 :   if (absl::StartsWith(pattern, "*")) {
     156          86 :     return ParsedResult<Operator>(Operator::PathGlob, pattern.substr(1));
     157          86 :   }
     158           0 :   return absl::InvalidArgumentError("Invalid Operator");
     159          86 : }
     160             : 
     161         565 : absl::StatusOr<ParsedResult<Variable>> parseVariable(absl::string_view pattern) {
     162             :   // Locate the variable pattern to parse.
     163         565 :   if (pattern.size() < 2 || (pattern)[0] != '{') {
     164           2 :     return absl::InvalidArgumentError("Invalid variable");
     165           2 :   }
     166         563 :   std::vector<absl::string_view> parts = absl::StrSplit(pattern.substr(1), absl::MaxSplits('}', 1));
     167         563 :   if (parts.size() != 2) {
     168           7 :     return absl::InvalidArgumentError("Unmatched variable bracket");
     169           7 :   }
     170         556 :   absl::string_view unparsed_pattern = parts[1];
     171             : 
     172             :   // Parse the actual variable pattern, starting with the variable name.
     173         556 :   std::vector<absl::string_view> variable_parts = absl::StrSplit(parts[0], absl::MaxSplits('=', 1));
     174         556 :   if (!isValidVariableName(variable_parts[0])) {
     175          10 :     return absl::InvalidArgumentError("Invalid variable name");
     176          10 :   }
     177         546 :   Variable var = Variable(variable_parts[0], {});
     178             : 
     179             :   // Parse the variable match pattern (if any).
     180         546 :   if (variable_parts.size() < 2) {
     181         437 :     return ParsedResult<Variable>(var, unparsed_pattern);
     182         437 :   }
     183         109 :   absl::string_view pattern_item = variable_parts[1];
     184         109 :   if (pattern_item.empty()) {
     185           1 :     return absl::InvalidArgumentError("Empty variable match");
     186           1 :   }
     187         266 :   while (!pattern_item.empty()) {
     188         166 :     absl::variant<Operator, Literal> match;
     189         166 :     if (pattern_item[0] == '*') {
     190             : 
     191          78 :       absl::StatusOr<Operator> status = alsoUpdatePattern<Operator>(parseOperator, &pattern_item);
     192          78 :       if (!status.ok()) {
     193           0 :         return status.status();
     194           0 :       }
     195          78 :       match = *std::move(status);
     196             : 
     197         148 :     } else {
     198          88 :       absl::StatusOr<Literal> status = alsoUpdatePattern<Literal>(parseLiteral, &pattern_item);
     199          88 :       if (!status.ok()) {
     200           5 :         return status.status();
     201           5 :       }
     202          83 :       match = *std::move(status);
     203          83 :     }
     204         161 :     var.match_.push_back(match);
     205         161 :     if (!pattern_item.empty()) {
     206          61 :       if (pattern_item[0] != '/' || pattern_item.size() == 1) {
     207           3 :         return absl::InvalidArgumentError("Invalid variable match");
     208           3 :       }
     209          58 :       pattern_item = pattern_item.substr(1);
     210          58 :     }
     211         161 :   }
     212             : 
     213         100 :   return ParsedResult<Variable>(var, unparsed_pattern);
     214         108 : }
     215             : 
     216             : absl::StatusOr<absl::flat_hash_set<absl::string_view>>
     217         236 : gatherCaptureNames(const struct ParsedPathPattern& pattern) {
     218         236 :   absl::flat_hash_set<absl::string_view> captured_variables;
     219             : 
     220         858 :   for (const ParsedSegment& segment : pattern.parsed_segments_) {
     221         858 :     if (!absl::holds_alternative<Variable>(segment)) {
     222         480 :       continue;
     223         480 :     }
     224         378 :     if (captured_variables.size() >= kPatternMatchingMaxVariablesPerPath) {
     225          10 :       return absl::InvalidArgumentError("Exceeded variable count limit");
     226          10 :     }
     227         368 :     absl::string_view name = absl::get<Variable>(segment).name_;
     228             : 
     229         368 :     if (name.size() < kPatternMatchingMinVariableNameLen ||
     230         368 :         name.size() > kPatternMatchingMaxVariableNameLen) {
     231           5 :       return absl::InvalidArgumentError("Invalid variable name length");
     232           5 :     }
     233         363 :     if (captured_variables.contains(name)) {
     234           4 :       return absl::InvalidArgumentError("Repeated variable name");
     235           4 :     }
     236         359 :     captured_variables.emplace(name);
     237         359 :   }
     238             : 
     239         217 :   return captured_variables;
     240         236 : }
     241             : 
     242         217 : absl::Status validateNoOperatorAfterTextGlob(const struct ParsedPathPattern& pattern) {
     243         217 :   bool seen_text_glob = false;
     244         764 :   for (const ParsedSegment& segment : pattern.parsed_segments_) {
     245         764 :     if (absl::holds_alternative<Operator>(segment)) {
     246          36 :       if (seen_text_glob) {
     247           1 :         return absl::InvalidArgumentError("Glob after text glob.");
     248           1 :       }
     249          35 :       seen_text_glob = (absl::get<Operator>(segment) == Operator::TextGlob);
     250         728 :     } else if (absl::holds_alternative<Variable>(segment)) {
     251         298 :       const Variable& var = absl::get<Variable>(segment);
     252         298 :       if (var.match_.empty()) {
     253         214 :         if (seen_text_glob) {
     254             :           // A variable with no explicit matcher is treated as a path glob.
     255           0 :           return absl::InvalidArgumentError("Implicit variable path glob after text glob.");
     256           0 :         }
     257         214 :       } else {
     258         124 :         for (const absl::variant<Operator, absl::string_view>& var_seg : var.match_) {
     259         124 :           if (!absl::holds_alternative<Operator>(var_seg)) {
     260          48 :             continue;
     261          48 :           }
     262          76 :           if (seen_text_glob) {
     263           0 :             return absl::InvalidArgumentError("Glob after text glob.");
     264           0 :           }
     265          76 :           seen_text_glob = (absl::get<Operator>(var_seg) == Operator::TextGlob);
     266          76 :         }
     267          84 :       }
     268         298 :     }
     269         764 :   }
     270         216 :   return absl::OkStatus();
     271         217 : }
     272             : 
     273         362 : absl::StatusOr<ParsedPathPattern> parsePathPatternSyntax(absl::string_view path) {
     274         362 :   struct ParsedPathPattern parsed_pattern;
     275             : 
     276         362 :   static const LazyRE2 printable_regex = {"^/[[:graph:]]*$"};
     277         362 :   if (!RE2::FullMatch(path, *printable_regex)) {
     278          46 :     return absl::InvalidArgumentError("Invalid pattern");
     279          46 :   }
     280             : 
     281             :   // Parse the leading '/'
     282         316 :   path = path.substr(1);
     283             : 
     284             :   // Do the initial lexical parsing.
     285        1314 :   while (!path.empty()) {
     286        1128 :     ParsedSegment segment;
     287        1128 :     if (path[0] == '*') {
     288          51 :       absl::StatusOr<Operator> status = alsoUpdatePattern<Operator>(parseOperator, &path);
     289          51 :       if (!status.ok()) {
     290           0 :         return status.status();
     291           0 :       }
     292          51 :       segment = *std::move(status);
     293        1077 :     } else if (path[0] == '{') {
     294         565 :       absl::StatusOr<Variable> status = alsoUpdatePattern<Variable>(parseVariable, &path);
     295         565 :       if (!status.ok()) {
     296          28 :         return status.status();
     297          28 :       }
     298         537 :       segment = *std::move(status);
     299         580 :     } else {
     300         512 :       absl::StatusOr<Literal> status = alsoUpdatePattern<Literal>(parseLiteral, &path);
     301         512 :       if (!status.ok()) {
     302          24 :         return status.status();
     303          24 :       }
     304         488 :       segment = *std::move(status);
     305         488 :     }
     306        1076 :     parsed_pattern.parsed_segments_.push_back(segment);
     307             : 
     308             :     // Deal with trailing '/' or suffix.
     309        1076 :     if (!path.empty()) {
     310         892 :       if (path == "/") {
     311             :         // Single trailing '/' at the end, mark this with empty literal.
     312          18 :         parsed_pattern.parsed_segments_.emplace_back("");
     313          18 :         break;
     314         874 :       } else if (path[0] == '/') {
     315             :         // Have '/' followed by more text, parse the '/'.
     316         814 :         path = path.substr(1);
     317         814 :       } else {
     318             :         // Not followed by '/', treat as suffix.
     319          60 :         absl::StatusOr<Literal> status = alsoUpdatePattern<Literal>(parseLiteral, &path);
     320          60 :         if (!status.ok()) {
     321          16 :           return status.status();
     322          16 :         }
     323          44 :         parsed_pattern.suffix_ = *std::move(status);
     324          44 :         if (!path.empty()) {
     325             :           // Suffix didn't parse whole remaining pattern ('/' in path).
     326          12 :           return absl::InvalidArgumentError("Prefix match not supported.");
     327          12 :         }
     328          32 :         break;
     329          44 :       }
     330         892 :     }
     331        1076 :   }
     332         236 :   absl::StatusOr<absl::flat_hash_set<absl::string_view>> status =
     333         236 :       gatherCaptureNames(parsed_pattern);
     334         236 :   if (!status.ok()) {
     335          19 :     return status.status();
     336          19 :   }
     337         217 :   parsed_pattern.captured_variables_ = *std::move(status);
     338             : 
     339         217 :   absl::Status validate_status = validateNoOperatorAfterTextGlob(parsed_pattern);
     340         217 :   if (!validate_status.ok()) {
     341           1 :     return validate_status;
     342           1 :   }
     343             : 
     344         216 :   return parsed_pattern;
     345         217 : }
     346             : 
     347         690 : std::string toRegexPattern(absl::string_view pattern) {
     348         690 :   return absl::StrReplaceAll(
     349         690 :       pattern, {{"$", "\\$"}, {"(", "\\("}, {")", "\\)"}, {"+", "\\+"}, {".", "\\."}});
     350         690 : }
     351             : 
     352         324 : std::string toRegexPattern(Operator pattern) {
     353         324 :   static const std::string* kPathGlobRegex = new std::string(absl::StrCat("[", kLiteral, "]+"));
     354         324 :   static const std::string* kTextGlobRegex = new std::string(absl::StrCat("[", kLiteral, "/]*"));
     355         324 :   switch (pattern) {
     356         282 :   case Operator::PathGlob: // "*"
     357         282 :     return *kPathGlobRegex;
     358          42 :   case Operator::TextGlob: // "**"
     359          42 :     return *kTextGlobRegex;
     360         324 :   }
     361           0 :   return "";
     362         324 : }
     363             : 
     364         298 : std::string toRegexPattern(const Variable& pattern) {
     365         298 :   return absl::StrCat("(?P<", pattern.name_, ">",
     366         298 :                       pattern.match_.empty()
     367         298 :                           ? toRegexPattern(kDefaultVariableOperator)
     368         298 :                           : absl::StrJoin(pattern.match_, "/", ToRegexPatternFormatter()),
     369         298 :                       ")");
     370         298 : }
     371             : 
     372         216 : std::string toRegexPattern(const struct ParsedPathPattern& pattern) {
     373         216 :   return absl::StrCat("/", absl::StrJoin(pattern.parsed_segments_, "/", ToRegexPatternFormatter()),
     374         216 :                       toRegexPattern(pattern.suffix_));
     375         216 : }
     376             : 
     377             : } // namespace Internal
     378             : } // namespace UriTemplate
     379             : } // namespace Extensions
     380             : } // namespace Envoy

Generated by: LCOV version 1.15