Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/common/protobuf/utility.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/common/protobuf/utility.h"
2
3
#include <limits>
4
#include <numeric>
5
6
#include "envoy/annotations/deprecation.pb.h"
7
#include "envoy/protobuf/message_validator.h"
8
#include "envoy/type/v3/percent.pb.h"
9
10
#include "source/common/common/assert.h"
11
#include "source/common/common/documentation_url.h"
12
#include "source/common/common/fmt.h"
13
#include "source/common/protobuf/deterministic_hash.h"
14
#include "source/common/protobuf/message_validator_impl.h"
15
#include "source/common/protobuf/protobuf.h"
16
#include "source/common/protobuf/visitor.h"
17
#include "source/common/runtime/runtime_features.h"
18
19
#include "absl/strings/match.h"
20
#include "udpa/annotations/sensitive.pb.h"
21
#include "udpa/annotations/status.pb.h"
22
#include "validate/validate.h"
23
#include "xds/annotations/v3/status.pb.h"
24
25
using namespace std::chrono_literals;
26
27
namespace Envoy {
28
namespace {
29
30
// Validates that the max value of nanoseconds and seconds doesn't cause an
31
// overflow in the protobuf time-util computations.
32
// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation"
33
// is removed this function should be renamed to validateDurationNoThrow.
34
769k
absl::Status validateDurationUnifiedNoThrow(const ProtobufWkt::Duration& duration) {
35
  // Apply a strict max boundary to the `seconds` value to avoid overflow when
36
  // both seconds and nanoseconds are at their highest values.
37
  // Note that protobuf internally converts to the input's seconds and
38
  // nanoseconds to nanoseconds (with a max nanoseconds value of 999999999).
39
  // The kMaxSecondsValue = 9223372035, which is about 292 years.
40
769k
  constexpr int64_t kMaxSecondsValue =
41
769k
      (std::numeric_limits<int64_t>::max() - 999999999) / (1000 * 1000 * 1000);
42
43
769k
  if (duration.seconds() < 0 || duration.nanos() < 0) {
44
13
    return absl::OutOfRangeError(
45
13
        fmt::format("Expected positive duration: {}", duration.DebugString()));
46
13
  }
47
769k
  if (!Protobuf::util::TimeUtil::IsDurationValid(duration)) {
48
52
    return absl::OutOfRangeError(
49
52
        fmt::format("Duration out-of-range according to Protobuf: {}", duration.DebugString()));
50
52
  }
51
769k
  if (duration.nanos() > 999999999 || duration.seconds() > kMaxSecondsValue) {
52
14
    return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString()));
53
14
  }
54
769k
  return absl::OkStatus();
55
769k
}
56
57
// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation"
58
// is removed this function should be removed.
59
absl::Status validateDurationNoThrow(const ProtobufWkt::Duration& duration,
60
684k
                                     int64_t max_seconds_value) {
61
684k
  if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) {
62
684k
    return validateDurationUnifiedNoThrow(duration);
63
684k
  }
64
0
  if (duration.seconds() < 0 || duration.nanos() < 0) {
65
0
    return absl::OutOfRangeError(
66
0
        fmt::format("Expected positive duration: {}", duration.DebugString()));
67
0
  }
68
0
  if (duration.nanos() > 999999999 || duration.seconds() > max_seconds_value) {
69
0
    return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString()));
70
0
  }
71
0
  return absl::OkStatus();
72
0
}
73
74
// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation"
75
// is removed this function should call validateDurationUnifiedNoThrow instead
76
// of validateDurationNoThrow.
77
85.5k
void validateDuration(const ProtobufWkt::Duration& duration, int64_t max_seconds_value) {
78
85.5k
  const auto result = validateDurationNoThrow(duration, max_seconds_value);
79
85.5k
  if (!result.ok()) {
80
1
    throwEnvoyExceptionOrPanic(std::string(result.message()));
81
1
  }
82
85.5k
}
83
84
// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation"
85
// is removed this function should be removed.
86
64
void validateDuration(const ProtobufWkt::Duration& duration) {
87
64
  validateDuration(duration, Protobuf::util::TimeUtil::kDurationMaxSeconds);
88
64
}
89
90
// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation"
91
// is removed this function should be removed.
92
85.4k
void validateDurationAsMilliseconds(const ProtobufWkt::Duration& duration) {
93
  // Apply stricter max boundary to the `seconds` value to avoid overflow.
94
  // Note that protobuf internally converts to nanoseconds.
95
  // The kMaxInt64Nanoseconds = 9223372036, which is about 300 years.
96
85.4k
  constexpr int64_t kMaxInt64Nanoseconds =
97
85.4k
      std::numeric_limits<int64_t>::max() / (1000 * 1000 * 1000);
98
85.4k
  validateDuration(duration, kMaxInt64Nanoseconds);
99
85.4k
}
100
101
// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation"
102
// is removed this function should be removed.
103
598k
absl::Status validateDurationAsMillisecondsNoThrow(const ProtobufWkt::Duration& duration) {
104
598k
  constexpr int64_t kMaxInt64Nanoseconds =
105
598k
      std::numeric_limits<int64_t>::max() / (1000 * 1000 * 1000);
106
598k
  return validateDurationNoThrow(duration, kMaxInt64Nanoseconds);
107
598k
}
108
109
} // namespace
110
111
namespace {
112
113
67.0M
absl::string_view filenameFromPath(absl::string_view full_path) {
114
67.0M
  size_t index = full_path.rfind('/');
115
67.0M
  if (index == std::string::npos || index == full_path.size()) {
116
0
    return full_path;
117
0
  }
118
67.0M
  return full_path.substr(index + 1, full_path.size());
119
67.0M
}
120
121
// Logs a warning for use of a deprecated field or runtime-overridden use of an
122
// otherwise fatal field. Throws a warning on use of a fatal by default field.
123
void deprecatedFieldHelper(Runtime::Loader* runtime, bool proto_annotated_as_deprecated,
124
                           bool proto_annotated_as_disallowed, const std::string& feature_name,
125
                           std::string error, const Protobuf::Message& message,
126
201k
                           ProtobufMessage::ValidationVisitor& validation_visitor) {
127
// This option is for Envoy builds with --define deprecated_features=disabled
128
// The build options CI then verifies that as Envoy developers deprecate fields,
129
// that they update canonical configs and unit tests to not use those deprecated
130
// fields, by making their use fatal in the build options CI.
131
#ifdef ENVOY_DISABLE_DEPRECATED_FEATURES
132
  bool warn_only = false;
133
#else
134
201k
  bool warn_only = true;
135
201k
#endif
136
201k
  if (runtime &&
137
201k
      runtime->snapshot().getBoolean("envoy.features.fail_on_any_deprecated_feature", false)) {
138
0
    warn_only = false;
139
0
  }
140
201k
  bool warn_default = warn_only;
141
  // Allow runtime to be null both to not crash if this is called before server initialization,
142
  // and so proto validation works in context where runtime singleton is not set up (e.g.
143
  // standalone config validation utilities)
144
201k
  if (runtime && proto_annotated_as_deprecated) {
145
    // This is set here, rather than above, so that in the absence of a
146
    // registry (i.e. test) the default for if a feature is allowed or not is
147
    // based on ENVOY_DISABLE_DEPRECATED_FEATURES.
148
0
    warn_only &= !proto_annotated_as_disallowed;
149
0
    warn_default = warn_only;
150
0
    warn_only = runtime->snapshot().deprecatedFeatureEnabled(feature_name, warn_only);
151
0
  }
152
  // Note this only checks if the runtime override has an actual effect. It
153
  // does not change the logged warning if someone "allows" a deprecated but not
154
  // yet fatal field.
155
201k
  const bool runtime_overridden = (warn_default == false && warn_only == true);
156
157
201k
  std::string with_overridden = fmt::format(
158
201k
      fmt::runtime(error),
159
201k
      (runtime_overridden ? "runtime overrides to continue using now fatal-by-default " : ""));
160
161
201k
  THROW_IF_NOT_OK(validation_visitor.onDeprecatedField(
162
201k
      "type " + message.GetTypeName() + " " + with_overridden, warn_only));
163
201k
}
164
165
} // namespace
166
167
namespace ProtobufPercentHelper {
168
169
9.22k
uint64_t checkAndReturnDefault(uint64_t default_value, uint64_t max_value) {
170
9.22k
  ASSERT(default_value <= max_value);
171
9.22k
  return default_value;
172
9.22k
}
173
174
617
uint64_t convertPercent(double percent, uint64_t max_value) {
175
  // Checked by schema.
176
617
  ASSERT(percent >= 0.0 && percent <= 100.0);
177
617
  return max_value * (percent / 100.0);
178
617
}
179
180
2.04k
bool evaluateFractionalPercent(envoy::type::v3::FractionalPercent percent, uint64_t random_value) {
181
2.04k
  return random_value % fractionalPercentDenominatorToInt(percent.denominator()) <
182
2.04k
         percent.numerator();
183
2.04k
}
184
185
uint64_t fractionalPercentDenominatorToInt(
186
4.57k
    const envoy::type::v3::FractionalPercent::DenominatorType& denominator) {
187
4.57k
  switch (denominator) {
188
0
    PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
189
4.57k
  case envoy::type::v3::FractionalPercent::HUNDRED:
190
4.57k
    return 100;
191
0
  case envoy::type::v3::FractionalPercent::TEN_THOUSAND:
192
0
    return 10000;
193
0
  case envoy::type::v3::FractionalPercent::MILLION:
194
0
    return 1000000;
195
4.57k
  }
196
0
  PANIC_DUE_TO_CORRUPT_ENUM
197
0
}
198
199
} // namespace ProtobufPercentHelper
200
201
void ProtoExceptionUtil::throwMissingFieldException(const std::string& field_name,
202
113
                                                    const Protobuf::Message& message) {
203
113
  std::string error =
204
113
      fmt::format("Field '{}' is missing in: {}", field_name, message.DebugString());
205
113
  throwEnvoyExceptionOrPanic(error);
206
113
}
207
208
void ProtoExceptionUtil::throwProtoValidationException(const std::string& validation_error,
209
8.45k
                                                       const Protobuf::Message& message) {
210
8.45k
  std::string error = fmt::format("Proto constraint validation failed ({}): {}", validation_error,
211
8.45k
                                  message.DebugString());
212
8.45k
  throwEnvoyExceptionOrPanic(error);
213
8.45k
}
214
215
173k
size_t MessageUtil::hash(const Protobuf::Message& message) {
216
173k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
217
173k
  if (Runtime::runtimeFeatureEnabled("envoy.restart_features.use_fast_protobuf_hash")) {
218
173k
    return DeterministicProtoHash::hash(message);
219
173k
  } else {
220
0
    std::string text_format;
221
0
    Protobuf::TextFormat::Printer printer;
222
0
    printer.SetExpandAny(true);
223
0
    printer.SetUseFieldNumber(true);
224
0
    printer.SetSingleLineMode(true);
225
0
    printer.SetHideUnknownFields(true);
226
0
    printer.PrintToString(message, &text_format);
227
0
    return HashUtil::xxHash64(text_format);
228
0
  }
229
#else
230
  return HashUtil::xxHash64(message.SerializeAsString());
231
#endif
232
173k
}
233
234
#if !defined(ENVOY_ENABLE_FULL_PROTOS)
235
// NOLINTNEXTLINE(readability-identifier-naming)
236
bool MessageLiteDifferencer::Equals(const Protobuf::Message& message1,
237
                                    const Protobuf::Message& message2) {
238
  return MessageUtil::hash(message1) == MessageUtil::hash(message2);
239
}
240
241
// NOLINTNEXTLINE(readability-identifier-naming)
242
bool MessageLiteDifferencer::Equivalent(const Protobuf::Message& message1,
243
                                        const Protobuf::Message& message2) {
244
  return Equals(message1, message2);
245
}
246
#endif
247
248
namespace {
249
250
void checkForDeprecatedNonRepeatedEnumValue(
251
    const Protobuf::Message& message, absl::string_view filename,
252
    const Protobuf::FieldDescriptor* field, const Protobuf::Reflection* reflection,
253
67.0M
    Runtime::Loader* runtime, ProtobufMessage::ValidationVisitor& validation_visitor) {
254
  // Repeated fields will be handled by recursion in checkForUnexpectedFields.
255
67.0M
  if (field->is_repeated() || field->cpp_type() != Protobuf::FieldDescriptor::CPPTYPE_ENUM) {
256
65.2M
    return;
257
65.2M
  }
258
259
1.82M
  Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
260
1.82M
  bool default_value = !reflection->HasField(*reflectable_message, field);
261
262
1.82M
  const Protobuf::EnumValueDescriptor* enum_value_descriptor =
263
1.82M
      reflection->GetEnum(*reflectable_message, field);
264
1.82M
  if (!enum_value_descriptor->options().deprecated()) {
265
1.82M
    return;
266
1.82M
  }
267
268
797
  const std::string error =
269
797
      absl::StrCat("Using {}", (default_value ? "the default now-" : ""), "deprecated value ",
270
797
                   enum_value_descriptor->name(), " for enum '", field->full_name(), "' from file ",
271
797
                   filename, ". This enum value will be removed from Envoy soon",
272
797
                   (default_value ? " so a non-default value must now be explicitly set" : ""),
273
797
                   ". Please see " ENVOY_DOC_URL_VERSION_HISTORY " for details.");
274
797
  deprecatedFieldHelper(
275
797
      runtime, true /*deprecated*/,
276
797
      enum_value_descriptor->options().GetExtension(envoy::annotations::disallowed_by_default_enum),
277
797
      absl::StrCat("envoy.deprecated_features:", enum_value_descriptor->full_name()), error,
278
797
      message, validation_visitor);
279
797
}
280
281
constexpr absl::string_view WipWarning =
282
    "API features marked as work-in-progress are not considered stable, are not covered by the "
283
    "threat model, are not supported by the security team, and are subject to breaking changes. Do "
284
    "not use this feature without understanding each of the previous points.";
285
286
class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor {
287
public:
288
  UnexpectedFieldProtoVisitor(ProtobufMessage::ValidationVisitor& validation_visitor,
289
                              Runtime::Loader* runtime)
290
302k
      : validation_visitor_(validation_visitor), runtime_(runtime) {}
291
292
67.0M
  void onField(const Protobuf::Message& message, const Protobuf::FieldDescriptor& field) override {
293
67.0M
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
294
67.0M
    const Protobuf::Reflection* reflection = reflectable_message->GetReflection();
295
67.0M
    absl::string_view filename = filenameFromPath(field.file()->name());
296
297
    // Before we check to see if the field is in use, see if there's a
298
    // deprecated default enum value.
299
67.0M
    checkForDeprecatedNonRepeatedEnumValue(message, filename, &field, reflection, runtime_,
300
67.0M
                                           validation_visitor_);
301
302
    // If this field is not in use, continue.
303
67.0M
    if ((field.is_repeated() && reflection->FieldSize(*reflectable_message, &field) == 0) ||
304
67.0M
        (!field.is_repeated() && !reflection->HasField(*reflectable_message, &field))) {
305
48.9M
      return;
306
48.9M
    }
307
308
18.1M
    const auto& field_status = field.options().GetExtension(xds::annotations::v3::field_status);
309
18.1M
    if (field_status.work_in_progress()) {
310
12.0k
      validation_visitor_.onWorkInProgress(fmt::format(
311
12.0k
          "field '{}' is marked as work-in-progress. {}", field.full_name(), WipWarning));
312
12.0k
    }
313
314
    // If this field is deprecated, warn or throw an error.
315
18.1M
    if (field.options().deprecated()) {
316
200k
      const std::string warning =
317
200k
          absl::StrCat("Using {}deprecated option '", field.full_name(), "' from file ", filename,
318
200k
                       ". This configuration will be removed from "
319
200k
                       "Envoy soon. Please see " ENVOY_DOC_URL_VERSION_HISTORY " for details.");
320
321
200k
      deprecatedFieldHelper(runtime_, true /*deprecated*/,
322
200k
                            field.options().GetExtension(envoy::annotations::disallowed_by_default),
323
200k
                            absl::StrCat("envoy.deprecated_features:", field.full_name()), warning,
324
200k
                            message, validation_visitor_);
325
200k
    }
326
18.1M
  }
327
328
  void onMessage(const Protobuf::Message& message,
329
14.1M
                 absl::Span<const Protobuf::Message* const> parents, bool) override {
330
14.1M
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
331
14.1M
    if (reflectable_message->GetDescriptor()
332
14.1M
            ->options()
333
14.1M
            .GetExtension(xds::annotations::v3::message_status)
334
14.1M
            .work_in_progress()) {
335
5
      validation_visitor_.onWorkInProgress(fmt::format(
336
5
          "message '{}' is marked as work-in-progress. {}", message.GetTypeName(), WipWarning));
337
5
    }
338
339
14.1M
    const auto& udpa_file_options =
340
14.1M
        reflectable_message->GetDescriptor()->file()->options().GetExtension(
341
14.1M
            udpa::annotations::file_status);
342
14.1M
    const auto& xds_file_options =
343
14.1M
        reflectable_message->GetDescriptor()->file()->options().GetExtension(
344
14.1M
            xds::annotations::v3::file_status);
345
14.1M
    if (udpa_file_options.work_in_progress() || xds_file_options.work_in_progress()) {
346
418
      validation_visitor_.onWorkInProgress(fmt::format(
347
418
          "message '{}' is contained in proto file '{}' marked as work-in-progress. {}",
348
418
          message.GetTypeName(), reflectable_message->GetDescriptor()->file()->name(), WipWarning));
349
418
    }
350
351
    // Reject unknown fields.
352
14.1M
    const auto& unknown_fields =
353
14.1M
        reflectable_message->GetReflection()->GetUnknownFields(*reflectable_message);
354
14.1M
    if (!unknown_fields.empty()) {
355
29
      std::string error_msg;
356
171
      for (int n = 0; n < unknown_fields.field_count(); ++n) {
357
142
        absl::StrAppend(&error_msg, n > 0 ? ", " : "", unknown_fields.field(n).number());
358
142
      }
359
29
      if (!error_msg.empty()) {
360
29
        THROW_IF_NOT_OK(validation_visitor_.onUnknownField(
361
29
            fmt::format("type {}({}) with unknown field set {{{}}}", message.GetTypeName(),
362
29
                        !parents.empty()
363
29
                            ? absl::StrJoin(parents, "::",
364
29
                                            [](std::string* out, const Protobuf::Message* const m) {
365
29
                                              absl::StrAppend(out, m->GetTypeName());
366
29
                                            })
367
29
                            : "root",
368
29
                        error_msg)));
369
29
      }
370
29
    }
371
14.1M
  }
372
373
private:
374
  ProtobufMessage::ValidationVisitor& validation_visitor_;
375
  Runtime::Loader* runtime_;
376
};
377
378
} // namespace
379
380
void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message,
381
                                           ProtobufMessage::ValidationVisitor& validation_visitor,
382
302k
                                           bool recurse_into_any) {
383
302k
  Runtime::Loader* runtime = validation_visitor.runtime().has_value()
384
302k
                                 ? &validation_visitor.runtime().value().get()
385
302k
                                 : nullptr;
386
302k
  UnexpectedFieldProtoVisitor unexpected_field_visitor(validation_visitor, runtime);
387
302k
  THROW_IF_NOT_OK(
388
302k
      ProtobufMessage::traverseMessage(unexpected_field_visitor, message, recurse_into_any));
389
302k
}
390
391
namespace {
392
393
// A proto visitor that validates the correctness of google.protobuf.Duration messages
394
// as defined by Envoy's duration constraints.
395
class DurationFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor {
396
public:
397
66.6M
  void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) override {}
398
399
  void onMessage(const Protobuf::Message& message, absl::Span<const Protobuf::Message* const>,
400
14.0M
                 bool) override {
401
14.0M
    const Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
402
14.0M
    if (reflectable_message->GetDescriptor()->full_name() == "google.protobuf.Duration") {
403
85.2k
      ProtobufWkt::Duration duration_message;
404
85.2k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
405
85.2k
      duration_message.CheckTypeAndMergeFrom(message);
406
#else
407
      duration_message.MergeFromCord(message.SerializeAsCord());
408
#endif
409
      // Validate the value of the duration.
410
85.2k
      absl::Status status = validateDurationUnifiedNoThrow(duration_message);
411
85.2k
      if (!status.ok()) {
412
78
        throwEnvoyExceptionOrPanic(fmt::format("Invalid duration: {}", status.message()));
413
78
      }
414
85.2k
    }
415
14.0M
  }
416
};
417
418
} // namespace
419
420
300k
void MessageUtil::validateDurationFields(const Protobuf::Message& message, bool recurse_into_any) {
421
300k
  if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) {
422
300k
    DurationFieldProtoVisitor duration_field_visitor;
423
300k
    THROW_IF_NOT_OK(
424
300k
        ProtobufMessage::traverseMessage(duration_field_visitor, message, recurse_into_any));
425
300k
  }
426
300k
}
427
428
namespace {
429
430
class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor {
431
public:
432
  void onMessage(const Protobuf::Message& message, absl::Span<const Protobuf::Message* const>,
433
0
                 bool was_any_or_top_level) override {
434
0
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
435
0
    std::string err;
436
    // PGV verification is itself recursive up to the point at which it hits an Any message. As
437
    // such, to avoid N^2 checking of the tree, we only perform an additional check at the point
438
    // at which PGV would have stopped because it does not itself check within Any messages.
439
0
    if (was_any_or_top_level &&
440
0
        !pgv::BaseValidator::AbstractCheckMessage(*reflectable_message, &err)) {
441
0
      ProtoExceptionUtil::throwProtoValidationException(err, *reflectable_message);
442
0
    }
443
0
  }
444
445
0
  void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) override {}
446
};
447
448
} // namespace
449
450
0
void MessageUtil::recursivePgvCheck(const Protobuf::Message& message) {
451
0
  PgvCheckVisitor visitor;
452
0
  THROW_IF_NOT_OK(ProtobufMessage::traverseMessage(visitor, message, true));
453
0
}
454
455
18
void MessageUtil::packFrom(ProtobufWkt::Any& any_message, const Protobuf::Message& message) {
456
18
#if defined(ENVOY_ENABLE_FULL_PROTOS)
457
18
  any_message.PackFrom(message);
458
#else
459
  any_message.set_type_url(message.GetTypeName());
460
  any_message.set_value(message.SerializeAsString());
461
#endif
462
18
}
463
464
128k
void MessageUtil::unpackToOrThrow(const ProtobufWkt::Any& any_message, Protobuf::Message& message) {
465
128k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
466
128k
  if (!any_message.UnpackTo(&message)) {
467
65
    throwEnvoyExceptionOrPanic(fmt::format("Unable to unpack as {}: {}",
468
65
                                           message.GetDescriptor()->full_name(),
469
65
                                           any_message.DebugString()));
470
#else
471
  if (!message.ParseFromString(any_message.value())) {
472
    throwEnvoyExceptionOrPanic(
473
        fmt::format("Unable to unpack as {}: {}", message.GetTypeName(), any_message.type_url()));
474
#endif
475
65
  }
476
128k
}
477
478
absl::Status MessageUtil::unpackTo(const ProtobufWkt::Any& any_message,
479
81.4k
                                   Protobuf::Message& message) {
480
81.4k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
481
81.4k
  if (!any_message.UnpackTo(&message)) {
482
10
    return absl::InternalError(absl::StrCat("Unable to unpack as ",
483
10
                                            message.GetDescriptor()->full_name(), ": ",
484
10
                                            any_message.DebugString()));
485
#else
486
  if (!message.ParseFromString(any_message.value())) {
487
    return absl::InternalError(
488
        absl::StrCat("Unable to unpack as ", message.GetTypeName(), ": ", any_message.type_url()));
489
#endif
490
10
  }
491
  // Ok Status is returned if `UnpackTo` succeeded.
492
81.4k
  return absl::OkStatus();
493
81.4k
}
494
495
std::string MessageUtil::convertToStringForLogs(const Protobuf::Message& message, bool pretty_print,
496
0
                                                bool always_print_primitive_fields) {
497
0
#ifdef ENVOY_ENABLE_YAML
498
0
  return getJsonStringFromMessageOrError(message, pretty_print, always_print_primitive_fields);
499
#else
500
  UNREFERENCED_PARAMETER(pretty_print);
501
  UNREFERENCED_PARAMETER(always_print_primitive_fields);
502
  return message.DebugString();
503
#endif
504
0
}
505
506
0
ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::string& key, const std::string& value) {
507
0
  ProtobufWkt::Struct struct_obj;
508
0
  ProtobufWkt::Value val;
509
0
  val.set_string_value(value);
510
0
  (*struct_obj.mutable_fields())[key] = val;
511
0
  return struct_obj;
512
0
}
513
514
13
ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::map<std::string, std::string>& fields) {
515
13
  ProtobufWkt::Struct struct_obj;
516
13
  ProtobufWkt::Value val;
517
65
  for (const auto& pair : fields) {
518
65
    val.set_string_value(pair.second);
519
65
    (*struct_obj.mutable_fields())[pair.first] = val;
520
65
  }
521
13
  return struct_obj;
522
13
}
523
524
7
std::string MessageUtil::codeEnumToString(absl::StatusCode code) {
525
7
  std::string result = absl::StatusCodeToString(code);
526
  // This preserves the behavior of the `ProtobufUtil::Status(code, "").ToString();`
527
7
  return !result.empty() ? result : "UNKNOWN: ";
528
7
}
529
530
namespace {
531
532
// Forward declaration for mutually-recursive helper functions.
533
void redact(Protobuf::Message* message, bool ancestor_is_sensitive);
534
535
using Transform = std::function<void(Protobuf::Message*, const Protobuf::Reflection*,
536
                                     const Protobuf::FieldDescriptor*)>;
537
538
// To redact opaque types, namely `Any` and `TypedStruct`, we have to reify them to the concrete
539
// message types specified by their `type_url` before we can redact their contents. This is mostly
540
// identical between `Any` and `TypedStruct`, the only difference being how they are packed and
541
// unpacked. Note that we have to use reflection on the opaque type here, rather than downcasting
542
// to `Any` or `TypedStruct`, because any message we might be handling could have originated from
543
// a `DynamicMessageFactory`.
544
bool redactOpaque(Protobuf::Message* message, bool ancestor_is_sensitive,
545
0
                  absl::string_view opaque_type_name, Transform unpack, Transform repack) {
546
  // Ensure this message has the opaque type we're expecting.
547
0
  Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
548
0
  const auto* opaque_descriptor = reflectable_message->GetDescriptor();
549
0
  if (opaque_descriptor->full_name() != opaque_type_name) {
550
0
    return false;
551
0
  }
552
553
  // Find descriptors for the `type_url` and `value` fields. The `type_url` field must not be
554
  // empty, but `value` may be (in which case our work is done).
555
0
  const auto* reflection = reflectable_message->GetReflection();
556
0
  const auto* type_url_field_descriptor = opaque_descriptor->FindFieldByName("type_url");
557
0
  const auto* value_field_descriptor = opaque_descriptor->FindFieldByName("value");
558
0
  ASSERT(type_url_field_descriptor != nullptr && value_field_descriptor != nullptr);
559
0
  if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) &&
560
0
      !reflection->HasField(*reflectable_message, value_field_descriptor)) {
561
0
    return true;
562
0
  }
563
0
  if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) ||
564
0
      !reflection->HasField(*reflectable_message, value_field_descriptor)) {
565
0
    return false;
566
0
  }
567
568
  // Try to find a descriptor for `type_url` in the pool and instantiate a new message of the
569
  // correct concrete type.
570
0
  const std::string type_url(
571
0
      reflection->GetString(*reflectable_message, type_url_field_descriptor));
572
0
  const std::string concrete_type_name(TypeUtil::typeUrlToDescriptorFullName(type_url));
573
0
  const auto* concrete_descriptor =
574
0
      Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(concrete_type_name);
575
0
  if (concrete_descriptor == nullptr) {
576
    // If the type URL doesn't correspond to a known proto, don't try to reify it, just treat it
577
    // like any other message. See the documented limitation on `MessageUtil::redact()` for more
578
    // context.
579
0
    ENVOY_LOG_MISC(warn, "Could not reify {} with unknown type URL {}", opaque_type_name, type_url);
580
0
    return false;
581
0
  }
582
0
  Protobuf::DynamicMessageFactory message_factory;
583
0
  std::unique_ptr<Protobuf::Message> typed_message(
584
0
      message_factory.GetPrototype(concrete_descriptor)->New());
585
586
  // Finally we can unpack, redact, and repack the opaque message using the provided callbacks.
587
588
  // Note: the content of opaque types may contain illegal content that mismatches the type_url
589
  // which may cause unpacking to fail. We catch the exception here to avoid crashing Envoy.
590
0
  TRY_ASSERT_MAIN_THREAD { unpack(typed_message.get(), reflection, value_field_descriptor); }
591
0
  END_TRY CATCH(const EnvoyException& e, {
592
0
    ENVOY_LOG_MISC(warn, "Could not unpack {} with type URL {}: {}", opaque_type_name, type_url,
593
0
                   e.what());
594
0
    return false;
595
0
  });
596
0
  redact(typed_message.get(), ancestor_is_sensitive);
597
0
  repack(typed_message.get(), reflection, value_field_descriptor);
598
0
  return true;
599
0
}
600
601
0
bool redactAny(Protobuf::Message* message, bool ancestor_is_sensitive) {
602
0
  return redactOpaque(
603
0
      message, ancestor_is_sensitive, "google.protobuf.Any",
604
0
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
605
0
                const Protobuf::FieldDescriptor* field_descriptor) {
606
0
        Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
607
        // To unpack an `Any`, parse the serialized proto.
608
0
        typed_message->ParseFromString(
609
0
            reflection->GetString(*reflectable_message, field_descriptor));
610
0
      },
611
0
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
612
0
                const Protobuf::FieldDescriptor* field_descriptor) {
613
0
        Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
614
        // To repack an `Any`, reserialize its proto.
615
0
        reflection->SetString(&(*reflectable_message), field_descriptor,
616
0
                              typed_message->SerializeAsString());
617
0
      });
618
0
}
619
620
// To redact a `TypedStruct`, we have to reify it based on its `type_url` to redact it.
621
bool redactTypedStruct(Protobuf::Message* message, const char* typed_struct_type,
622
0
                       bool ancestor_is_sensitive) {
623
0
  return redactOpaque(
624
0
      message, ancestor_is_sensitive, typed_struct_type,
625
0
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
626
0
                const Protobuf::FieldDescriptor* field_descriptor) {
627
0
#ifdef ENVOY_ENABLE_YAML
628
        // To unpack a `TypedStruct`, convert the struct from JSON.
629
0
        MessageUtil::jsonConvert(reflection->GetMessage(*message, field_descriptor),
630
0
                                 *typed_message);
631
#else
632
        UNREFERENCED_PARAMETER(message);
633
        UNREFERENCED_PARAMETER(typed_message);
634
        UNREFERENCED_PARAMETER(reflection);
635
        UNREFERENCED_PARAMETER(field_descriptor);
636
        IS_ENVOY_BUG("redaction requested with JSON/YAML support removed");
637
#endif
638
0
      },
639
0
      [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection,
640
0
                const Protobuf::FieldDescriptor* field_descriptor) {
641
  // To repack a `TypedStruct`, convert the message back to JSON.
642
0
#ifdef ENVOY_ENABLE_YAML
643
0
        MessageUtil::jsonConvert(*typed_message,
644
0
                                 *(reflection->MutableMessage(message, field_descriptor)));
645
#else
646
        UNREFERENCED_PARAMETER(message);
647
        UNREFERENCED_PARAMETER(typed_message);
648
        UNREFERENCED_PARAMETER(reflection);
649
        UNREFERENCED_PARAMETER(field_descriptor);
650
        IS_ENVOY_BUG("redaction requested with JSON/YAML support removed");
651
#endif
652
0
      });
653
0
}
654
655
// Recursive helper method for MessageUtil::redact() below.
656
0
void redact(Protobuf::Message* message, bool ancestor_is_sensitive) {
657
0
  if (redactAny(message, ancestor_is_sensitive) ||
658
0
      redactTypedStruct(message, "xds.type.v3.TypedStruct", ancestor_is_sensitive) ||
659
0
      redactTypedStruct(message, "udpa.type.v1.TypedStruct", ancestor_is_sensitive)) {
660
0
    return;
661
0
  }
662
663
0
  Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message);
664
0
  const auto* descriptor = reflectable_message->GetDescriptor();
665
0
  const auto* reflection = reflectable_message->GetReflection();
666
0
  for (int i = 0; i < descriptor->field_count(); ++i) {
667
0
    const auto* field_descriptor = descriptor->field(i);
668
669
    // Redact if this field or any of its ancestors have the `sensitive` option set.
670
0
    const bool sensitive = ancestor_is_sensitive ||
671
0
                           field_descriptor->options().GetExtension(udpa::annotations::sensitive);
672
673
0
    if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) {
674
      // Recursive case: traverse message fields.
675
0
      if (field_descriptor->is_map()) {
676
        // Redact values of maps only. Redacting both leaves the map with multiple "[redacted]"
677
        // keys.
678
0
        const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor);
679
0
        for (int i = 0; i < field_size; ++i) {
680
0
          Protobuf::Message* map_pair_base =
681
0
              reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i);
682
0
          Protobuf::ReflectableMessage map_pair = createReflectableMessage(*map_pair_base);
683
0
          auto* value_field_desc = map_pair->GetDescriptor()->FindFieldByName("value");
684
0
          if (sensitive && (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_STRING ||
685
0
                            value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_BYTES)) {
686
0
            map_pair->GetReflection()->SetString(&(*map_pair), value_field_desc, "[redacted]");
687
0
          } else if (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) {
688
0
            redact(map_pair->GetReflection()->MutableMessage(&(*map_pair), value_field_desc),
689
0
                   sensitive);
690
0
          } else if (sensitive) {
691
0
            map_pair->GetReflection()->ClearField(&(*map_pair), value_field_desc);
692
0
          }
693
0
        }
694
0
      } else if (field_descriptor->is_repeated()) {
695
0
        const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor);
696
0
        for (int i = 0; i < field_size; ++i) {
697
0
          redact(reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i),
698
0
                 sensitive);
699
0
        }
700
0
      } else if (reflection->HasField(*reflectable_message, field_descriptor)) {
701
0
        redact(reflection->MutableMessage(&(*reflectable_message), field_descriptor), sensitive);
702
0
      }
703
0
    } else if (sensitive) {
704
      // Base case: replace strings and bytes with "[redacted]" and clear all others.
705
0
      if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_STRING ||
706
0
          field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_BYTES) {
707
0
        if (field_descriptor->is_repeated()) {
708
0
          const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor);
709
0
          for (int i = 0; i < field_size; ++i) {
710
0
            reflection->SetRepeatedString(&(*reflectable_message), field_descriptor, i,
711
0
                                          "[redacted]");
712
0
          }
713
0
        } else if (reflection->HasField(*reflectable_message, field_descriptor)) {
714
0
          reflection->SetString(&(*reflectable_message), field_descriptor, "[redacted]");
715
0
        }
716
0
      } else {
717
0
        reflection->ClearField(&(*reflectable_message), field_descriptor);
718
0
      }
719
0
    }
720
0
  }
721
0
}
722
723
} // namespace
724
725
0
void MessageUtil::redact(Protobuf::Message& message) {
726
0
  ::Envoy::redact(&message, /* ancestor_is_sensitive = */ false);
727
0
}
728
729
65
void MessageUtil::wireCast(const Protobuf::Message& src, Protobuf::Message& dst) {
730
  // This should should generally succeed, but if there are malformed UTF-8 strings in a message,
731
  // this can fail.
732
65
  if (!dst.ParseFromString(src.SerializeAsString())) {
733
0
    throwEnvoyExceptionOrPanic("Unable to deserialize during wireCast()");
734
0
  }
735
65
}
736
737
1
std::string MessageUtil::toTextProto(const Protobuf::Message& message) {
738
1
#if defined(ENVOY_ENABLE_FULL_PROTOS)
739
1
  std::string text_format;
740
1
  Protobuf::TextFormat::Printer printer;
741
1
  printer.SetExpandAny(true);
742
1
  printer.SetHideUnknownFields(true);
743
1
  bool result = printer.PrintToString(message, &text_format);
744
1
  ASSERT(result);
745
1
  return text_format;
746
#else
747
  // Note that MessageLite::DebugString never had guarantees of producing
748
  // serializable text proto representation.
749
  return message.DebugString();
750
#endif
751
1
}
752
753
0
bool ValueUtil::equal(const ProtobufWkt::Value& v1, const ProtobufWkt::Value& v2) {
754
0
  ProtobufWkt::Value::KindCase kind = v1.kind_case();
755
0
  if (kind != v2.kind_case()) {
756
0
    return false;
757
0
  }
758
759
0
  switch (kind) {
760
0
  case ProtobufWkt::Value::KIND_NOT_SET:
761
0
    return v2.kind_case() == ProtobufWkt::Value::KIND_NOT_SET;
762
763
0
  case ProtobufWkt::Value::kNullValue:
764
0
    return true;
765
766
0
  case ProtobufWkt::Value::kNumberValue:
767
0
    return v1.number_value() == v2.number_value();
768
769
0
  case ProtobufWkt::Value::kStringValue:
770
0
    return v1.string_value() == v2.string_value();
771
772
0
  case ProtobufWkt::Value::kBoolValue:
773
0
    return v1.bool_value() == v2.bool_value();
774
775
0
  case ProtobufWkt::Value::kStructValue: {
776
0
    const ProtobufWkt::Struct& s1 = v1.struct_value();
777
0
    const ProtobufWkt::Struct& s2 = v2.struct_value();
778
0
    if (s1.fields_size() != s2.fields_size()) {
779
0
      return false;
780
0
    }
781
0
    for (const auto& it1 : s1.fields()) {
782
0
      const auto& it2 = s2.fields().find(it1.first);
783
0
      if (it2 == s2.fields().end()) {
784
0
        return false;
785
0
      }
786
787
0
      if (!equal(it1.second, it2->second)) {
788
0
        return false;
789
0
      }
790
0
    }
791
0
    return true;
792
0
  }
793
794
0
  case ProtobufWkt::Value::kListValue: {
795
0
    const ProtobufWkt::ListValue& l1 = v1.list_value();
796
0
    const ProtobufWkt::ListValue& l2 = v2.list_value();
797
0
    if (l1.values_size() != l2.values_size()) {
798
0
      return false;
799
0
    }
800
0
    for (int i = 0; i < l1.values_size(); i++) {
801
0
      if (!equal(l1.values(i), l2.values(i))) {
802
0
        return false;
803
0
      }
804
0
    }
805
0
    return true;
806
0
  }
807
0
  }
808
0
  return false;
809
0
}
810
811
157
const ProtobufWkt::Value& ValueUtil::nullValue() {
812
157
  static const auto* v = []() -> ProtobufWkt::Value* {
813
1
    auto* vv = new ProtobufWkt::Value();
814
1
    vv->set_null_value(ProtobufWkt::NULL_VALUE);
815
1
    return vv;
816
1
  }();
817
157
  return *v;
818
157
}
819
820
483k
ProtobufWkt::Value ValueUtil::stringValue(const std::string& str) {
821
483k
  ProtobufWkt::Value val;
822
483k
  val.set_string_value(str);
823
483k
  return val;
824
483k
}
825
826
0
ProtobufWkt::Value ValueUtil::optionalStringValue(const absl::optional<std::string>& str) {
827
0
  if (str.has_value()) {
828
0
    return ValueUtil::stringValue(str.value());
829
0
  }
830
0
  return ValueUtil::nullValue();
831
0
}
832
833
1.99k
ProtobufWkt::Value ValueUtil::boolValue(bool b) {
834
1.99k
  ProtobufWkt::Value val;
835
1.99k
  val.set_bool_value(b);
836
1.99k
  return val;
837
1.99k
}
838
839
8.51k
ProtobufWkt::Value ValueUtil::structValue(const ProtobufWkt::Struct& obj) {
840
8.51k
  ProtobufWkt::Value val;
841
8.51k
  (*val.mutable_struct_value()) = obj;
842
8.51k
  return val;
843
8.51k
}
844
845
4.66k
ProtobufWkt::Value ValueUtil::listValue(const std::vector<ProtobufWkt::Value>& values) {
846
4.66k
  auto list = std::make_unique<ProtobufWkt::ListValue>();
847
9.38k
  for (const auto& value : values) {
848
9.38k
    *list->add_values() = value;
849
9.38k
  }
850
4.66k
  ProtobufWkt::Value val;
851
4.66k
  val.set_allocated_list_value(list.release());
852
4.66k
  return val;
853
4.66k
}
854
855
85.4k
uint64_t DurationUtil::durationToMilliseconds(const ProtobufWkt::Duration& duration) {
856
85.4k
  validateDurationAsMilliseconds(duration);
857
85.4k
  return Protobuf::util::TimeUtil::DurationToMilliseconds(duration);
858
85.4k
}
859
860
absl::StatusOr<uint64_t>
861
598k
DurationUtil::durationToMillisecondsNoThrow(const ProtobufWkt::Duration& duration) {
862
598k
  const auto result = validateDurationAsMillisecondsNoThrow(duration);
863
598k
  if (!result.ok()) {
864
0
    return result;
865
0
  }
866
598k
  return Protobuf::util::TimeUtil::DurationToMilliseconds(duration);
867
598k
}
868
869
64
uint64_t DurationUtil::durationToSeconds(const ProtobufWkt::Duration& duration) {
870
64
  validateDuration(duration);
871
64
  return Protobuf::util::TimeUtil::DurationToSeconds(duration);
872
64
}
873
874
void TimestampUtil::systemClockToTimestamp(const SystemTime system_clock_time,
875
923
                                           ProtobufWkt::Timestamp& timestamp) {
876
  // Converts to millisecond-precision Timestamp by explicitly casting to millisecond-precision
877
  // time_point.
878
923
  timestamp.MergeFrom(Protobuf::util::TimeUtil::MillisecondsToTimestamp(
879
923
      std::chrono::time_point_cast<std::chrono::milliseconds>(system_clock_time)
880
923
          .time_since_epoch()
881
923
          .count()));
882
923
}
883
884
311k
absl::string_view TypeUtil::typeUrlToDescriptorFullName(absl::string_view type_url) {
885
311k
  const size_t pos = type_url.rfind('/');
886
311k
  if (pos != absl::string_view::npos) {
887
283k
    type_url = type_url.substr(pos + 1);
888
283k
  }
889
311k
  return type_url;
890
311k
}
891
892
2
std::string TypeUtil::descriptorFullNameToTypeUrl(absl::string_view type) {
893
2
  return "type.googleapis.com/" + std::string(type);
894
2
}
895
896
0
void StructUtil::update(ProtobufWkt::Struct& obj, const ProtobufWkt::Struct& with) {
897
0
  auto& obj_fields = *obj.mutable_fields();
898
899
0
  for (const auto& [key, val] : with.fields()) {
900
0
    auto& obj_key = obj_fields[key];
901
902
    // If the types are different, the last one wins.
903
0
    const auto val_kind = val.kind_case();
904
0
    if (val_kind != obj_key.kind_case()) {
905
0
      obj_key = val;
906
0
      continue;
907
0
    }
908
909
    // Otherwise, the strategy depends on the value kind.
910
0
    switch (val.kind_case()) {
911
    // For scalars, the last one wins.
912
0
    case ProtobufWkt::Value::kNullValue:
913
0
    case ProtobufWkt::Value::kNumberValue:
914
0
    case ProtobufWkt::Value::kStringValue:
915
0
    case ProtobufWkt::Value::kBoolValue:
916
0
      obj_key = val;
917
0
      break;
918
    // If we got a structure, recursively update.
919
0
    case ProtobufWkt::Value::kStructValue:
920
0
      update(*obj_key.mutable_struct_value(), val.struct_value());
921
0
      break;
922
    // For lists, append the new values.
923
0
    case ProtobufWkt::Value::kListValue: {
924
0
      auto& obj_key_vec = *obj_key.mutable_list_value()->mutable_values();
925
0
      const auto& vals = val.list_value().values();
926
0
      obj_key_vec.MergeFrom(vals);
927
0
      break;
928
0
    }
929
0
    case ProtobufWkt::Value::KIND_NOT_SET:
930
0
      break;
931
0
    }
932
0
  }
933
0
}
934
935
void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& message,
936
                               ProtobufMessage::ValidationVisitor& validation_visitor,
937
12.5k
                               Api::Api& api) {
938
12.5k
  auto file_or_error = api.fileSystem().fileReadToEnd(path);
939
12.5k
  THROW_IF_NOT_OK_REF(file_or_error.status());
940
12.5k
  const std::string contents = file_or_error.value();
941
  // If the filename ends with .pb, attempt to parse it as a binary proto.
942
12.5k
  if (absl::EndsWithIgnoreCase(path, FileExtensions::get().ProtoBinary)) {
943
    // Attempt to parse the binary format.
944
1.97k
    if (message.ParseFromString(contents)) {
945
1.97k
      MessageUtil::checkForUnexpectedFields(message, validation_visitor);
946
1.97k
    }
947
    // Ideally this would throw an error if ParseFromString fails for consistency
948
    // but instead it will silently fail.
949
1.97k
    return;
950
1.97k
  }
951
952
  // If the filename ends with .pb_text, attempt to parse it as a text proto.
953
10.5k
  if (absl::EndsWithIgnoreCase(path, FileExtensions::get().ProtoText)) {
954
8.53k
#if defined(ENVOY_ENABLE_FULL_PROTOS)
955
8.53k
    if (Protobuf::TextFormat::ParseFromString(contents, &message)) {
956
8.49k
      return;
957
8.49k
    }
958
38
#endif
959
8.53k
    throwEnvoyExceptionOrPanic("Unable to parse file \"" + path + "\" as a text protobuf (type " +
960
8.53k
                               message.GetTypeName() + ")");
961
8.53k
  }
962
1.99k
#ifdef ENVOY_ENABLE_YAML
963
1.99k
  if (absl::EndsWithIgnoreCase(path, FileExtensions::get().Yaml) ||
964
1.99k
      absl::EndsWithIgnoreCase(path, FileExtensions::get().Yml)) {
965
    // loadFromYaml throws an error if parsing fails.
966
0
    loadFromYaml(contents, message, validation_visitor);
967
1.99k
  } else {
968
    // loadFromJson does not consistently trow an error if parsing fails.
969
    // Ideally we would handle that case here.
970
1.99k
    loadFromJson(contents, message, validation_visitor);
971
1.99k
  }
972
#else
973
  throwEnvoyExceptionOrPanic("Unable to parse file \"" + path + "\" (type " +
974
                             message.GetTypeName() + ")");
975
#endif
976
1.99k
}
977
978
} // namespace Envoy