1
#pragma once
2

            
3
#include <algorithm>
4

            
5
#include "source/common/common/assert.h"
6
#include "source/common/common/utility.h"
7

            
8
#include "absl/container/inlined_vector.h"
9
#include "absl/strings/str_cat.h"
10
#include "absl/strings/string_view.h"
11
#include "absl/types/variant.h"
12

            
13
namespace Envoy {
14

            
15
/**
16
 * Convenient type for an inline vector that will be used by InlinedString.
17
 */
18
using InlinedStringVector = absl::InlinedVector<char, 128>;
19

            
20
/**
21
 * Convenient type for the underlying type of InlinedString that allows a variant
22
 * between string_view and the InlinedVector.
23
 */
24
using VariantStringOrView = absl::variant<absl::string_view, InlinedStringVector>;
25

            
26
// This includes the NULL (StringUtil::itoa technically only needs 21).
27
inline constexpr size_t MaxIntegerLength{32};
28

            
29
889983
inline void validateCapacity(uint64_t new_capacity) {
30
  // If the resizing will cause buffer overflow due to hitting uint32_t::max, an OOM is likely
31
  // imminent. Fast-fail rather than allow a buffer overflow attack (issue #1421)
32
889983
  RELEASE_ASSERT(new_capacity <= std::numeric_limits<uint32_t>::max(),
33
889983
                 "Trying to allocate overly large headers.");
34
889983
}
35

            
36
10043327
inline absl::string_view getStrView(const VariantStringOrView& buffer) {
37
10043327
  return absl::get<absl::string_view>(buffer);
38
10043327
}
39

            
40
24867661
inline InlinedStringVector& getInVec(VariantStringOrView& buffer) {
41
24867661
  return absl::get<InlinedStringVector>(buffer);
42
24867661
}
43

            
44
115198064
inline const InlinedStringVector& getInVec(const VariantStringOrView& buffer) {
45
115198064
  return absl::get<InlinedStringVector>(buffer);
46
115198064
}
47

            
48
/**
49
 * This is a string implementation that unified string reference and owned string. It is heavily
50
 * optimized for performance. It supports 2 different types of storage and can switch between them:
51
 * 1) A string reference.
52
 * 2) A string InlinedVector (an optimized interned string for small strings, but allows heap
53
 * allocation if needed).
54
 */
55
template <class Validator> class UnionStringBase {
56
public:
57
  using Storage = VariantStringOrView;
58

            
59
  /**
60
   * Default constructor. Sets up for inline storage.
61
   */
62
6617814
  UnionStringBase() : buffer_(std::in_place_type<InlinedStringVector>) {
63
6617814
    ASSERT((getInVec(buffer_).capacity()) >= MaxIntegerLength);
64
6617814
    assertValid();
65
6617814
  }
66

            
67
18289500
  inline void assertValid() const {
68
18289500
    ASSERT(valid(), absl::StrCat(typeid(Validator).name(), " failed to validate string \"",
69
18289500
                                 getStringView(), "\""));
70
18289500
  }
71

            
72
  /**
73
   * Constructor for a string reference.
74
   * @param ref_value MUST point to data that will live beyond the lifetime of any request/response
75
   *        using the string (since a codec may optimize for zero copy).
76
   */
77
2924529
  explicit UnionStringBase(absl::string_view ref_value) : buffer_(ref_value) { assertValid(); }
78

            
79
4269181
  UnionStringBase(UnionStringBase&& move_value) noexcept : buffer_(std::move(move_value.buffer_)) {
80
4269181
    move_value.clear();
81
    // Move constructor does not validate and relies on the source object validating its mutations.
82
4269181
  }
83
13808755
  ~UnionStringBase() = default;
84

            
85
  /**
86
   * Append data to an existing string. If the string is a reference string the reference data is
87
   * not copied.
88
   */
89
889983
  void append(const char* data, uint32_t data_size) {
90
    // Make sure the requested memory allocation is below uint32_t::max
91
889983
    const uint64_t new_capacity = static_cast<uint64_t>(data_size) + size();
92
889983
    validateCapacity(new_capacity);
93
889983
    ASSERT(valid(absl::string_view(data, data_size)),
94
889983
           absl::StrCat(typeid(Validator).name(), " failed to validate string \"",
95
889983
                        absl::string_view(data, data_size), "\""));
96

            
97
889983
    switch (type()) {
98
4
    case Type::Reference: {
99
      // Rather than be too clever and optimize this uncommon case, we switch to
100
      // Inline mode and copy.
101
4
      const absl::string_view prev = getStrView(buffer_);
102
4
      buffer_ = InlinedStringVector();
103
      // Assigning new_capacity to avoid resizing when appending the new data
104
4
      getInVec(buffer_).reserve(new_capacity);
105
4
      getInVec(buffer_).assign(prev.begin(), prev.end());
106
4
      break;
107
    }
108
889979
    case Type::Inline: {
109
889979
      getInVec(buffer_).reserve(new_capacity);
110
889979
      break;
111
    }
112
889983
    }
113
889983
    getInVec(buffer_).insert(getInVec(buffer_).end(), data, data + data_size);
114
889983
  }
115

            
116
  /**
117
   * Transforms the inlined vector data using the given UnaryOperation (conforms
118
   * to std::transform).
119
   * @param unary_op the operations to be performed on each of the elements.
120
   */
121
376664
  template <typename UnaryOperation> void inlineTransform(UnaryOperation&& unary_op) {
122
376664
    ASSERT(type() == Type::Inline);
123
376664
    std::transform(absl::get<InlinedStringVector>(buffer_).begin(),
124
376664
                   absl::get<InlinedStringVector>(buffer_).end(),
125
376664
                   absl::get<InlinedStringVector>(buffer_).begin(), unary_op);
126
376664
  }
127

            
128
  /**
129
   * Trim trailing whitespaces from the InlinedString. Only supported by the "Inline" InlinedString
130
   * representation.
131
   */
132
376663
  void rtrim() {
133
376663
    ASSERT(type() == Type::Inline);
134
376663
    absl::string_view original = getStringView();
135
376663
    absl::string_view rtrimmed = StringUtil::rtrim(original);
136
376663
    if (original.size() != rtrimmed.size()) {
137
1
      getInVec(buffer_).resize(rtrimmed.size());
138
1
    }
139
376663
  }
140

            
141
  /**
142
   * Get an absl::string_view. It will NOT be NUL terminated!
143
   *
144
   * @return an absl::string_view.
145
   */
146
21497176
  absl::string_view getStringView() const {
147
21497176
    if (type() == Type::Reference) {
148
4918473
      return getStrView(buffer_);
149
4918473
    }
150
16578703
    ASSERT(type() == Type::Inline);
151
16578703
    return {getInVec(buffer_).data(), getInVec(buffer_).size()};
152
21497176
  }
153

            
154
  /**
155
   * Return the string to a default state. Reference strings are not touched. Both inline/dynamic
156
   * strings are reset to zero size.
157
   */
158
6592358
  void clear() {
159
6592358
    if (type() == Type::Inline) {
160
5985512
      getInVec(buffer_).clear();
161
5985512
    }
162
6592358
  }
163

            
164
  /**
165
   * @return whether the string is empty or not.
166
   */
167
946226
  bool empty() const { return size() == 0; }
168

            
169
  // Looking for find? Use getStringView().find()
170

            
171
  /**
172
   * Set the value of the string by copying data into it. This overwrites any existing string.
173
   */
174
8027893
  void setCopy(const char* data, uint32_t size) {
175
8027893
    if (!absl::holds_alternative<InlinedStringVector>(buffer_)) {
176
      // Switching from Type::Reference to Type::Inline
177
26
      buffer_ = InlinedStringVector();
178
26
    }
179

            
180
8027893
    getInVec(buffer_).reserve(size);
181
8027893
    getInVec(buffer_).assign(data, data + size);
182
8027893
    assertValid();
183
8027893
  }
184

            
185
  /**
186
   * Set the value of the string by copying data into it. This overwrites any existing string.
187
   */
188
6970276
  void setCopy(absl::string_view view) { setCopy(view.data(), view.size()); }
189

            
190
  /**
191
   * Set the value of the string to an integer. This overwrites any existing string.
192
   */
193
133996
  void setInteger(uint64_t value) {
194
    // Initialize the size to the max length, copy the actual data, and then
195
    // reduce the size (but not the capacity) as needed
196
    // Note: instead of using the inner_buffer, attempted the following:
197
    // resize buffer_ to MaxIntegerLength, apply StringUtil::itoa to the buffer_.data(), and then
198
    // resize buffer_ to int_length (the number of digits in value).
199
    // However it was slower than the following approach.
200
133996
    char inner_buffer[MaxIntegerLength];
201
133996
    const uint32_t int_length = StringUtil::itoa(inner_buffer, MaxIntegerLength, value);
202

            
203
133996
    if (type() == Type::Reference) {
204
      // Switching from Type::Reference to Type::Inline
205
1
      buffer_ = InlinedStringVector();
206
1
    }
207
133996
    ASSERT((getInVec(buffer_).capacity()) > MaxIntegerLength);
208
133996
    getInVec(buffer_).assign(inner_buffer, inner_buffer + int_length);
209
133996
  }
210

            
211
  /**
212
   * Set the value of the string to a string reference.
213
   * @param ref_value MUST point to data that will live beyond the lifetime of any request/response
214
   *        using the string (since a codec may optimize for zero copy).
215
   */
216
719385
  void setReference(absl::string_view ref_value) {
217
719385
    buffer_ = ref_value;
218
719385
    assertValid();
219
719385
  }
220

            
221
  /**
222
   * @return whether the string is a reference or an InlinedVector.
223
   */
224
1258175
  bool isReference() const { return type() == Type::Reference; }
225

            
226
  /**
227
   * @return the size of the string, not including the null terminator.
228
   */
229
87165923
  uint32_t size() const {
230
87165923
    if (type() == Type::Reference) {
231
5124896
      return getStrView(buffer_).size();
232
5124896
    }
233
82041027
    ASSERT(type() == Type::Inline);
234
82041027
    return getInVec(buffer_).size();
235
87165923
  }
236

            
237
402364
  bool operator==(const char* rhs) const {
238
402364
    return getStringView() == absl::NullSafeStringView(rhs);
239
402364
  }
240
1141944
  bool operator==(absl::string_view rhs) const { return getStringView() == rhs; }
241
763428
  bool operator!=(const char* rhs) const {
242
763428
    return getStringView() != absl::NullSafeStringView(rhs);
243
763428
  }
244
5066
  bool operator!=(absl::string_view rhs) const { return getStringView() != rhs; }
245

            
246
  // Test only method that does not have validation and allows setting arbitrary values.
247
11257
  void setCopyUnvalidatedForTestOnly(absl::string_view view) {
248
11257
    if (!absl::holds_alternative<InlinedStringVector>(buffer_)) {
249
      // Switching from Type::Reference to Type::Inline
250
1533
      buffer_ = InlinedStringVector();
251
1533
    }
252

            
253
11257
    getInVec(buffer_).reserve(view.size());
254
11257
    getInVec(buffer_).assign(view.data(), view.data() + view.size());
255
11257
  }
256

            
257
  /**
258
   * @return raw Storage for cross-class move. This method is used to tranfer ownership
259
   * between UnionString with different Validator.
260
   */
261
1
  Storage& storage() { return buffer_; }
262

            
263
protected:
264
  enum class Type { Reference, Inline };
265

            
266
  bool valid() const { return Validator()(getStringView()); }
267

            
268
  bool valid(absl::string_view data) const { return Validator()(data); }
269

            
270
  /**
271
   * @return the type of backing storage for the string.
272
   */
273
117536890
  Type type() const {
274
    // buffer_.index() is correlated with the order of Reference and Inline in the
275
    // enum.
276
117536890
    ASSERT((buffer_.index() == 0) || (buffer_.index() == 1));
277
117536890
    ASSERT((buffer_.index() == 0 && absl::holds_alternative<absl::string_view>(buffer_)) ||
278
117536890
           (buffer_.index() != 0));
279
117536890
    ASSERT((buffer_.index() == 1 && absl::holds_alternative<InlinedStringVector>(buffer_)) ||
280
117536890
           (buffer_.index() != 1));
281
117536890
    return Type(buffer_.index());
282
117536890
  }
283

            
284
  Storage buffer_;
285
};
286

            
287
class EmptyStringValidator {
288
public:
289
  bool operator()(absl::string_view) { return true; }
290
};
291

            
292
using UnionString = UnionStringBase<EmptyStringValidator>;
293

            
294
} // namespace Envoy