1
#include <regex>
2
#include <string>
3

            
4
#include "envoy/server/factory_context.h"
5

            
6
#include "source/common/common/assert.h"
7
#include "source/common/json/json_loader.h"
8
#include "source/common/router/header_parser.h"
9

            
10
#include "absl/strings/str_replace.h"
11
#include "re2/re2.h"
12

            
13
namespace Envoy {
14
namespace Router {
15
3068
static const re2::RE2& getMetadataTranslatorPattern() {
16
3068
  CONSTRUCT_ON_FIRST_USE(re2::RE2,
17
3068
                         R"EOF(%(UPSTREAM|DYNAMIC)_METADATA\(\s*(\[(?:.|\r?\n)+?\]\s*)\)%)EOF");
18
3068
}
19

            
20
// Related to issue 20389. Header formatters are parsed and processed by formatters defined in
21
// source/common/formatter/substitution_formatter.cc. For backwards compatibility UPSTREAM_METADATA
22
// and UPSTREAM_METADATA format must be changed. Those formatters used to take a JSON format like
23
// UPSTREAM_METADATA(["a", "b"]) and substitution formatters use UPSTREAM_METADATA(a:b) format.
24
// This translator translates UPSTREAM_METADATA and DYNAMIC_METADATA from JSON format to colon
25
// format.
26
// TODO(cpakulski): Eventually JSON format should be deprecated in favor of colon format.
27
3068
std::string HeaderParser::translateMetadataFormat(const std::string& header_value) {
28
3068
  const re2::RE2& re = getMetadataTranslatorPattern();
29
3068
  ASSERT(re.ok());
30
3068
  std::string new_header_value = header_value;
31
3068
  absl::string_view matches[3];
32
3083
  while (re.Match(new_header_value, 0, new_header_value.size(), re2::RE2::UNANCHORED, matches, 3)) {
33
18
    TRY_ASSERT_MAIN_THREAD {
34
18
      std::string new_format;
35
18
      auto params_or_error = Json::Factory::loadFromString(std::string(matches[2]));
36
18
      if (!params_or_error.status().ok()) {
37
2
        return header_value;
38
2
      }
39
16
      Json::ObjectSharedPtr parsed_params = params_or_error.value();
40

            
41
      // The given json string may be an invalid object or with an empty object array.
42
16
      if (parsed_params == nullptr) {
43
        // return original value
44
        return header_value;
45
      }
46
16
      auto array_or_error = parsed_params->asObjectArray();
47
16
      if (!array_or_error.status().ok() || array_or_error.value().empty()) {
48
        // return original value
49
1
        return header_value;
50
1
      }
51
15
      auto format_or_error = array_or_error.value()[0]->asString();
52
15
      if (!format_or_error.status().ok()) {
53
        return header_value;
54
      }
55
15
      new_format = format_or_error.value();
56
35
      for (size_t i = 1; i < array_or_error.value().size(); i++) {
57
20
        auto string_or_error = array_or_error.value()[i]->asString();
58
20
        if (!string_or_error.status().ok()) {
59
          return header_value;
60
        }
61
20
        absl::StrAppend(&new_format, ":", string_or_error.value());
62
20
      }
63

            
64
15
      new_format = absl::StrCat("%", matches[1], "_METADATA(", new_format, ")%");
65
15
      ENVOY_LOG_MISC(warn,
66
15
                     "Header formatter: JSON format of {}_METADATA parameters has been obsoleted. "
67
15
                     "Use colon format: {}",
68
15
                     matches[1], new_format.c_str());
69
      // The parsing should only happen on the main thread and the singleton context should be
70
      // available. In case it is not set in tests or other non-standard Envoy usage, we skip
71
      // counting the deprecated feature usage instead of crashing.
72
15
      auto* context = Server::Configuration::ServerFactoryContextInstance::getExisting();
73
15
      if (context != nullptr) {
74
12
        context->runtime().countDeprecatedFeatureUse();
75
12
      }
76

            
77
15
      int subs = absl::StrReplaceAll({{matches[0], new_format}}, &new_header_value);
78
15
      ASSERT(subs > 0);
79
15
    }
80
18
    END_TRY CATCH(..., { return header_value; });
81
15
  }
82

            
83
3065
  return new_header_value;
84
3068
}
85

            
86
3060
static const re2::RE2& getPerRequestTranslatorPattern() {
87
3060
  CONSTRUCT_ON_FIRST_USE(re2::RE2, R"EOF(%PER_REQUEST_STATE\((.+?)\)%)EOF");
88
3060
}
89

            
90
// Related to issue 20389.
91
// Header's formatter PER_REQUEST_STATE(key) is equivalent to substitution
92
// formatter FILTER_STATE(key:PLAIN). translatePerRequestState method
93
// translates between these 2 formats.
94
// TODO(cpakulski): eventually PER_REQUEST_STATE formatter should be deprecated in
95
// favor of FILTER_STATE.
96
3060
std::string HeaderParser::translatePerRequestState(const std::string& header_value) {
97
3060
  const re2::RE2& re = getPerRequestTranslatorPattern();
98
3060
  ASSERT(re.ok());
99
3060
  std::string new_header_value = header_value;
100
3060
  absl::string_view matches[2];
101
3070
  while (re.Match(new_header_value, 0, new_header_value.size(), re2::RE2::UNANCHORED, matches, 2)) {
102
10
    const std::string new_format = absl::StrCat("%FILTER_STATE(", matches[1], ":PLAIN)%");
103

            
104
10
    ENVOY_LOG_MISC(warn, "PER_REQUEST_STATE header formatter has been obsoleted. Use {}",
105
10
                   new_format.c_str());
106

            
107
    // The parsing should only happen on the main thread and the singleton context should be
108
    // available. In case it is not set in tests or other non-standard Envoy usage, we skip
109
    // counting the deprecated feature usage instead of crashing.
110
10
    auto* context = Server::Configuration::ServerFactoryContextInstance::getExisting();
111
10
    if (context != nullptr) {
112
6
      context->runtime().countDeprecatedFeatureUse();
113
6
    }
114

            
115
10
    int subs = absl::StrReplaceAll({{matches[0], new_format}}, &new_header_value);
116
10
    ASSERT(subs > 0);
117
10
  }
118
3060
  return new_header_value;
119
3060
}
120

            
121
} // namespace Router
122
} // namespace Envoy