/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 |