| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "google/protobuf/json/internal/unparser.h" |
| |
| #include <cfloat> |
| #include <complex> |
| #include <cstdint> |
| #include <cstring> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #include "google/protobuf/stubs/logging.h" |
| #include "google/protobuf/stubs/common.h" |
| #include "google/protobuf/descriptor.h" |
| #include "google/protobuf/dynamic_message.h" |
| #include "google/protobuf/message.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| #include "google/protobuf/io/coded_stream.h" |
| #include "google/protobuf/io/zero_copy_stream.h" |
| #include "google/protobuf/json/internal/descriptor_traits.h" |
| #include "google/protobuf/json/internal/unparser_traits.h" |
| #include "google/protobuf/json/internal/writer.h" |
| #include "google/protobuf/stubs/status_macros.h" |
| |
| // Must be included last. |
| #include "google/protobuf/port_def.inc" |
| |
| namespace google { |
| namespace protobuf { |
| namespace json_internal { |
| namespace { |
| template <typename Traits> |
| bool IsEmpty(const Msg<Traits>& msg, const Desc<Traits>& desc) { |
| size_t count = Traits::FieldCount(desc); |
| for (size_t i = 0; i < count; ++i) { |
| if (Traits::GetSize(Traits::FieldByIndex(desc, i), msg) > 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| enum class IntegerEnumStyle { |
| kQuoted, |
| kUnquoted, |
| }; |
| |
| template <typename Traits> |
| void WriteEnum(JsonWriter& writer, Field<Traits> field, int32_t value, |
| IntegerEnumStyle int_style = IntegerEnumStyle::kUnquoted) { |
| if (ClassifyMessage(Traits::FieldTypeName(field)) == MessageType::kNull) { |
| writer.Write("null"); |
| return; |
| } |
| |
| if (!writer.options().always_print_enums_as_ints) { |
| auto name = Traits::EnumNameByNumber(field, value); |
| if (name.ok()) { |
| writer.Write("\"", *name, "\""); |
| return; |
| } |
| } |
| |
| if (int_style == IntegerEnumStyle::kQuoted) { |
| writer.Write("\"", value, "\""); |
| } else { |
| writer.Write(value); |
| } |
| } |
| |
| // Mutually recursive with functions that follow. |
| template <typename Traits> |
| absl::Status WriteMessage(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc, bool is_top_level = false); |
| |
| // This is templatized so that defaults, singular, and repeated fields can both |
| // use the same enormous switch-case. |
| template <typename Traits, typename... Args> |
| absl::Status WriteSingular(JsonWriter& writer, Field<Traits> field, |
| Args&&... args) { |
| // When the pack `args` is empty, the caller has requested printing the |
| // default value. |
| bool is_default = sizeof...(Args) == 0; |
| switch (Traits::FieldType(field)) { |
| case FieldDescriptor::TYPE_FLOAT: { |
| auto x = Traits::GetFloat(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| if (writer.options().allow_legacy_syntax && is_default && |
| !std::isfinite(*x)) { |
| *x = 0; |
| } |
| writer.Write(*x); |
| break; |
| } |
| case FieldDescriptor::TYPE_DOUBLE: { |
| auto x = Traits::GetDouble(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| if (writer.options().allow_legacy_syntax && is_default && |
| !std::isfinite(*x)) { |
| *x = 0; |
| } |
| writer.Write(*x); |
| break; |
| } |
| case FieldDescriptor::TYPE_SFIXED64: |
| case FieldDescriptor::TYPE_SINT64: |
| case FieldDescriptor::TYPE_INT64: { |
| auto x = Traits::GetInt64(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_FIXED64: |
| case FieldDescriptor::TYPE_UINT64: { |
| auto x = Traits::GetUInt64(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_SFIXED32: |
| case FieldDescriptor::TYPE_SINT32: |
| case FieldDescriptor::TYPE_INT32: { |
| auto x = Traits::GetInt32(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(*x); |
| break; |
| } |
| case FieldDescriptor::TYPE_FIXED32: |
| case FieldDescriptor::TYPE_UINT32: { |
| auto x = Traits::GetUInt32(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(*x); |
| break; |
| } |
| case FieldDescriptor::TYPE_BOOL: { |
| auto x = Traits::GetBool(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(*x ? "true" : "false"); |
| break; |
| } |
| case FieldDescriptor::TYPE_STRING: { |
| auto x = Traits::GetString(field, writer.ScratchBuf(), |
| std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_BYTES: { |
| auto x = Traits::GetString(field, writer.ScratchBuf(), |
| std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| if (writer.options().allow_legacy_syntax && is_default) { |
| // Although difficult to verify, it appears that the original ESF parser |
| // fails to unescape the contents of a |
| // google.protobuf.Field.default_value, which may potentially be |
| // escaped if it is for a `bytes` field (note that default_value is a |
| // `string` regardless of what type the field is). |
| // |
| // However, our parser's type.proto guts actually know to do this |
| // correctly, so this bug must be manually re-introduced. |
| writer.WriteBase64(absl::CEscape(*x)); |
| } else { |
| writer.WriteBase64(*x); |
| } |
| break; |
| } |
| case FieldDescriptor::TYPE_ENUM: { |
| auto x = Traits::GetEnumValue(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| WriteEnum<Traits>(writer, field, *x); |
| break; |
| } |
| case FieldDescriptor::TYPE_MESSAGE: |
| case FieldDescriptor::TYPE_GROUP: { |
| auto x = Traits::GetMessage(field, std::forward<Args>(args)...); |
| RETURN_IF_ERROR(x.status()); |
| return WriteMessage<Traits>(writer, **x, Traits::GetDesc(**x)); |
| } |
| default: |
| return absl::InvalidArgumentError( |
| absl::StrCat("unsupported field type: ", Traits::FieldType(field))); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteRepeated(JsonWriter& writer, const Msg<Traits>& msg, |
| Field<Traits> field) { |
| writer.Write("["); |
| writer.Push(); |
| |
| size_t count = Traits::GetSize(field, msg); |
| bool first = true; |
| for (size_t i = 0; i < count; ++i) { |
| if (ClassifyMessage(Traits::FieldTypeName(field)) == MessageType::kValue) { |
| bool empty = false; |
| RETURN_IF_ERROR(Traits::WithFieldType( |
| field, [&](const Desc<Traits>& desc) -> absl::Status { |
| auto inner = Traits::GetMessage(field, msg, i); |
| RETURN_IF_ERROR(inner.status()); |
| empty = IsEmpty<Traits>(**inner, desc); |
| return absl::OkStatus(); |
| })); |
| |
| // Empty google.protobuf.Values are silently discarded. |
| if (empty) { |
| continue; |
| } |
| } |
| writer.WriteComma(first); |
| writer.NewLine(); |
| RETURN_IF_ERROR(WriteSingular<Traits>(writer, field, msg, i)); |
| } |
| |
| writer.Pop(); |
| if (!first) { |
| writer.NewLine(); |
| } |
| writer.Write("]"); |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteMapKey(JsonWriter& writer, const Msg<Traits>& entry, |
| Field<Traits> field) { |
| switch (Traits::FieldType(field)) { |
| case FieldDescriptor::TYPE_SFIXED64: |
| case FieldDescriptor::TYPE_SINT64: |
| case FieldDescriptor::TYPE_INT64: { |
| auto x = Traits::GetInt64(field, entry); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_FIXED64: |
| case FieldDescriptor::TYPE_UINT64: { |
| auto x = Traits::GetUInt64(field, entry); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_SFIXED32: |
| case FieldDescriptor::TYPE_SINT32: |
| case FieldDescriptor::TYPE_INT32: { |
| auto x = Traits::GetInt32(field, entry); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_FIXED32: |
| case FieldDescriptor::TYPE_UINT32: { |
| auto x = Traits::GetUInt32(field, entry); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_BOOL: { |
| auto x = Traits::GetBool(field, entry); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x ? "true" : "false")); |
| break; |
| } |
| case FieldDescriptor::TYPE_STRING: { |
| auto x = Traits::GetString(field, writer.ScratchBuf(), entry); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| break; |
| } |
| case FieldDescriptor::TYPE_ENUM: { |
| auto x = Traits::GetEnumValue(field, entry); |
| RETURN_IF_ERROR(x.status()); |
| WriteEnum<Traits>(writer, field, *x, IntegerEnumStyle::kQuoted); |
| break; |
| } |
| default: |
| return absl::InvalidArgumentError( |
| absl::StrCat("unsupported map key type: ", Traits::FieldType(field))); |
| } |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::StatusOr<bool> IsEmptyValue(const Msg<Traits>& msg, Field<Traits> field) { |
| if (ClassifyMessage(Traits::FieldTypeName(field)) != MessageType::kValue) { |
| return false; |
| } |
| bool empty = false; |
| RETURN_IF_ERROR(Traits::WithFieldType( |
| field, [&](const Desc<Traits>& desc) -> absl::Status { |
| auto inner = Traits::GetMessage(field, msg); |
| RETURN_IF_ERROR(inner.status()); |
| empty = IsEmpty<Traits>(**inner, desc); |
| return absl::OkStatus(); |
| })); |
| return empty; |
| } |
| |
| template <typename Traits> |
| absl::Status WriteMap(JsonWriter& writer, const Msg<Traits>& msg, |
| Field<Traits> field) { |
| writer.Write("{"); |
| writer.Push(); |
| |
| size_t count = Traits::GetSize(field, msg); |
| bool first = true; |
| for (size_t i = 0; i < count; ++i) { |
| absl::StatusOr<const Msg<Traits>*> entry = |
| Traits::GetMessage(field, msg, i); |
| RETURN_IF_ERROR(entry.status()); |
| const Desc<Traits>& type = Traits::GetDesc(**entry); |
| |
| auto is_empty = IsEmptyValue<Traits>(**entry, Traits::ValueField(type)); |
| RETURN_IF_ERROR(is_empty.status()); |
| if (*is_empty) { |
| // Empty google.protobuf.Values are silently discarded. |
| continue; |
| } |
| |
| writer.WriteComma(first); |
| writer.NewLine(); |
| RETURN_IF_ERROR( |
| WriteMapKey<Traits>(writer, **entry, Traits::KeyField(type))); |
| writer.Write(":"); |
| writer.Whitespace(" "); |
| RETURN_IF_ERROR( |
| WriteSingular<Traits>(writer, Traits::ValueField(type), **entry)); |
| } |
| |
| writer.Pop(); |
| if (!first) { |
| writer.NewLine(); |
| } |
| writer.Write("}"); |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteField(JsonWriter& writer, const Msg<Traits>& msg, |
| Field<Traits> field, bool& first) { |
| if (!Traits::IsRepeated(field)) { // Repeated case is handled in |
| // WriteRepeated. |
| auto is_empty = IsEmptyValue<Traits>(msg, field); |
| RETURN_IF_ERROR(is_empty.status()); |
| if (*is_empty) { |
| // Empty google.protobuf.Values are silently discarded. |
| return absl::OkStatus(); |
| } |
| } |
| |
| writer.WriteComma(first); |
| writer.NewLine(); |
| |
| if (Traits::IsExtension(field)) { |
| writer.Write(MakeQuoted("[", Traits::FieldFullName(field), "]"), ":"); |
| } else if (writer.options().preserve_proto_field_names) { |
| writer.Write(MakeQuoted(Traits::FieldName(field)), ":"); |
| } else { |
| // The generator for type.proto and the internals of descriptor.cc disagree |
| // on what the json name of a PascalCase field is supposed to be; type.proto |
| // seems to (incorrectly?) capitalize the first letter, which is the |
| // behavior ESF defaults to. To fix this, if the original field name starts |
| // with an uppercase letter, and the Json name does not, we uppercase it. |
| absl::string_view original_name = Traits::FieldName(field); |
| absl::string_view json_name = Traits::FieldJsonName(field); |
| if (writer.options().allow_legacy_syntax && |
| absl::ascii_isupper(original_name[0]) && |
| !absl::ascii_isupper(json_name[0])) { |
| writer.Write(MakeQuoted(absl::ascii_toupper(original_name[0]), |
| original_name.substr(1)), |
| ":"); |
| } else { |
| writer.Write(MakeQuoted(json_name), ":"); |
| } |
| } |
| writer.Whitespace(" "); |
| |
| if (Traits::IsMap(field)) { |
| return WriteMap<Traits>(writer, msg, field); |
| } else if (Traits::IsRepeated(field)) { |
| return WriteRepeated<Traits>(writer, msg, field); |
| } else if (Traits::GetSize(field, msg) == 0) { |
| // We can only get here if always_print_primitive_fields is true. |
| GOOGLE_DCHECK(writer.options().always_print_primitive_fields); |
| |
| if (Traits::FieldType(field) == FieldDescriptor::TYPE_GROUP) { |
| // We do not yet have full group support, but this is required so that we |
| // pass the same tests as the ESF parser. |
| writer.Write("null"); |
| return absl::OkStatus(); |
| } |
| return WriteSingular<Traits>(writer, field); |
| } |
| |
| return WriteSingular<Traits>(writer, field, msg); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteFields(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc, bool& first) { |
| std::vector<Field<Traits>> fields; |
| size_t total = Traits::FieldCount(desc); |
| fields.reserve(total); |
| for (size_t i = 0; i < total; ++i) { |
| Field<Traits> field = Traits::FieldByIndex(desc, i); |
| |
| bool has = Traits::GetSize(field, msg) > 0; |
| if (writer.options().always_print_primitive_fields) { |
| bool is_singular_message = |
| !Traits::IsRepeated(field) && |
| Traits::FieldType(field) == FieldDescriptor::TYPE_MESSAGE; |
| has |= !is_singular_message && !Traits::IsOneof(field); |
| } |
| |
| if (has) { |
| fields.push_back(field); |
| } |
| } |
| |
| // Add extensions *before* sorting. |
| Traits::FindAndAppendExtensions(msg, fields); |
| |
| // Fields are guaranteed to be serialized in field number order. |
| absl::c_sort(fields, [](const auto& a, const auto& b) { |
| return Traits::FieldNumber(a) < Traits::FieldNumber(b); |
| }); |
| |
| for (auto field : fields) { |
| RETURN_IF_ERROR(WriteField<Traits>(writer, msg, field, first)); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteStructValue(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc); |
| template <typename Traits> |
| absl::Status WriteListValue(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc); |
| |
| template <typename Traits> |
| absl::Status WriteValue(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc, bool is_top_level) { |
| // NOTE: The field numbers 1 through 6 are the numbers of the oneof fields in |
| // google.protobuf.Value. Conformance tests verify the correctness of these |
| // numbers. |
| if (Traits::GetSize(Traits::MustHaveField(desc, 1), msg) > 0) { |
| writer.Write("null"); |
| return absl::OkStatus(); |
| } |
| |
| auto number_field = Traits::MustHaveField(desc, 2); |
| if (Traits::GetSize(number_field, msg) > 0) { |
| auto x = Traits::GetDouble(number_field, msg); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(*x); |
| return absl::OkStatus(); |
| } |
| |
| auto string_field = Traits::MustHaveField(desc, 3); |
| if (Traits::GetSize(string_field, msg) > 0) { |
| auto x = Traits::GetString(string_field, writer.ScratchBuf(), msg); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(MakeQuoted(*x)); |
| return absl::OkStatus(); |
| } |
| |
| auto bool_field = Traits::MustHaveField(desc, 4); |
| if (Traits::GetSize(bool_field, msg) > 0) { |
| auto x = Traits::GetBool(bool_field, msg); |
| RETURN_IF_ERROR(x.status()); |
| writer.Write(*x ? "true" : "false"); |
| return absl::OkStatus(); |
| } |
| |
| auto struct_field = Traits::MustHaveField(desc, 5); |
| if (Traits::GetSize(struct_field, msg) > 0) { |
| auto x = Traits::GetMessage(struct_field, msg); |
| RETURN_IF_ERROR(x.status()); |
| return Traits::WithFieldType(struct_field, [&](const Desc<Traits>& type) { |
| return WriteStructValue<Traits>(writer, **x, type); |
| }); |
| } |
| |
| auto list_field = Traits::MustHaveField(desc, 6); |
| if (Traits::GetSize(list_field, msg) > 0) { |
| auto x = Traits::GetMessage(list_field, msg); |
| RETURN_IF_ERROR(x.status()); |
| return Traits::WithFieldType(list_field, [&](const Desc<Traits>& type) { |
| return WriteListValue<Traits>(writer, **x, type); |
| }); |
| } |
| |
| GOOGLE_CHECK(is_top_level) << "empty, non-top-level Value must be handled one layer " |
| "up, since it prints an empty string; reaching this " |
| "statement is always a bug"; |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteStructValue(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc) { |
| return WriteMap<Traits>(writer, msg, Traits::MustHaveField(desc, 1)); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteListValue(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc) { |
| return WriteRepeated<Traits>(writer, msg, Traits::MustHaveField(desc, 1)); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteTimestamp(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc) { |
| auto secs_field = Traits::MustHaveField(desc, 1); |
| auto secs = Traits::GetSize(secs_field, msg) > 0 |
| ? Traits::GetInt64(secs_field, msg) |
| : 0; |
| RETURN_IF_ERROR(secs.status()); |
| |
| if (*secs < -62135596800) { |
| return absl::InvalidArgumentError( |
| "minimum acceptable time value is 0001-01-01T00:00:00Z"); |
| } else if (*secs > 253402300799) { |
| return absl::InvalidArgumentError( |
| "maximum acceptable time value is 9999-12-31T23:59:59Z"); |
| } |
| |
| // Ensure seconds is positive. |
| *secs += 62135596800; |
| |
| auto nanos_field = Traits::MustHaveField(desc, 2); |
| auto nanos = Traits::GetSize(nanos_field, msg) > 0 |
| ? Traits::GetInt32(nanos_field, msg) |
| : 0; |
| RETURN_IF_ERROR(nanos.status()); |
| |
| // Julian Day -> Y/M/D, Algorithm from: |
| // Fliegel, H. F., and Van Flandern, T. C., "A Machine Algorithm for |
| // Processing Calendar Dates," Communications of the Association of |
| // Computing Machines, vol. 11 (1968), p. 657. |
| int32_t L, N, I, J, K; |
| L = static_cast<int32_t>(*secs / 86400) - 719162 + 68569 + 2440588; |
| N = 4 * L / 146097; |
| L = L - (146097 * N + 3) / 4; |
| I = 4000 * (L + 1) / 1461001; |
| L = L - 1461 * I / 4 + 31; |
| J = 80 * L / 2447; |
| K = L - 2447 * J / 80; |
| L = J / 11; |
| J = J + 2 - 12 * L; |
| I = 100 * (N - 49) + I + L; |
| |
| int32_t sec = *secs % 60; |
| int32_t min = (*secs / 60) % 60; |
| int32_t hour = (*secs / 3600) % 24; |
| |
| if (*nanos == 0) { |
| writer.Write(absl::StrFormat(R"("%04d-%02d-%02dT%02d:%02d:%02dZ")", I, J, K, |
| hour, min, sec)); |
| return absl::OkStatus(); |
| } |
| |
| size_t digits = 9; |
| uint32_t frac_seconds = std::abs(*nanos); |
| while (frac_seconds % 1000 == 0) { |
| frac_seconds /= 1000; |
| digits -= 3; |
| } |
| |
| writer.Write(absl::StrFormat(R"("%04d-%02d-%02dT%02d:%02d:%02d.%.*dZ")", I, J, |
| K, hour, min, sec, digits, frac_seconds)); |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteDuration(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc) { |
| constexpr int64_t kMaxSeconds = int64_t{3652500} * 86400; |
| constexpr int64_t kMaxNanos = 999999999; |
| |
| auto secs_field = Traits::MustHaveField(desc, 1); |
| auto secs = Traits::GetSize(secs_field, msg) > 0 |
| ? Traits::GetInt64(secs_field, msg) |
| : 0; |
| RETURN_IF_ERROR(secs.status()); |
| |
| if (*secs > kMaxSeconds || *secs < -kMaxSeconds) { |
| return absl::InvalidArgumentError("duration out of range"); |
| } |
| |
| auto nanos_field = Traits::MustHaveField(desc, 2); |
| auto nanos = Traits::GetSize(nanos_field, msg) > 0 |
| ? Traits::GetInt32(nanos_field, msg) |
| : 0; |
| RETURN_IF_ERROR(nanos.status()); |
| |
| if (*nanos > kMaxNanos || *nanos < -kMaxNanos) { |
| return absl::InvalidArgumentError("duration out of range"); |
| } |
| if ((*secs != 0) && (*nanos != 0) && ((*secs < 0) != (*nanos < 0))) { |
| return absl::InvalidArgumentError("nanos and seconds signs do not match"); |
| } |
| |
| if (*nanos == 0) { |
| writer.Write(absl::StrFormat(R"("%ds")", *secs)); |
| return absl::OkStatus(); |
| } |
| |
| size_t digits = 9; |
| uint32_t frac_seconds = std::abs(*nanos); |
| while (frac_seconds % 1000 == 0) { |
| frac_seconds /= 1000; |
| digits -= 3; |
| } |
| |
| absl::string_view sign = ((*secs < 0) || (*nanos < 0)) ? "-" : ""; |
| writer.Write(absl::StrFormat(R"("%s%d.%.*ds")", sign, std::abs(*secs), digits, |
| frac_seconds)); |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteFieldMask(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc) { |
| // google.protobuf.FieldMask has a single field with number 1. |
| auto paths_field = Traits::MustHaveField(desc, 1); |
| size_t paths = Traits::GetSize(paths_field, msg); |
| writer.Write('"'); |
| |
| bool first = true; |
| for (size_t i = 0; i < paths; ++i) { |
| writer.WriteComma(first); |
| auto path = Traits::GetString(paths_field, writer.ScratchBuf(), msg, i); |
| RETURN_IF_ERROR(path.status()); |
| bool saw_under = false; |
| for (char c : *path) { |
| if (absl::ascii_islower(c) && saw_under) { |
| writer.Write(absl::ascii_toupper(c)); |
| } else if (absl::ascii_isdigit(c) || absl::ascii_islower(c) || c == '.') { |
| writer.Write(c); |
| } else if (c == '_' && |
| (!saw_under || writer.options().allow_legacy_syntax)) { |
| saw_under = true; |
| continue; |
| } else if (!writer.options().allow_legacy_syntax) { |
| return absl::InvalidArgumentError("unexpected character in FieldMask"); |
| } else { |
| if (saw_under) { |
| writer.Write('_'); |
| } |
| writer.Write(c); |
| } |
| saw_under = false; |
| } |
| } |
| writer.Write('"'); |
| |
| return absl::OkStatus(); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteAny(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc) { |
| auto type_url_field = Traits::MustHaveField(desc, 1); |
| auto value_field = Traits::MustHaveField(desc, 2); |
| |
| bool has_type_url = Traits::GetSize(type_url_field, msg) > 0; |
| bool has_value = Traits::GetSize(value_field, msg) > 0; |
| if (!has_type_url && !has_value) { |
| writer.Write("{}"); |
| return absl::OkStatus(); |
| } else if (!has_type_url) { |
| return absl::InvalidArgumentError("broken Any: missing type URL"); |
| } else if (!has_value && !writer.options().allow_legacy_syntax) { |
| return absl::InvalidArgumentError("broken Any: missing value"); |
| } |
| |
| writer.Write("{"); |
| writer.Push(); |
| |
| auto type_url = Traits::GetString(type_url_field, writer.ScratchBuf(), msg); |
| RETURN_IF_ERROR(type_url.status()); |
| writer.NewLine(); |
| writer.Write("\"@type\":"); |
| writer.Whitespace(" "); |
| writer.Write(MakeQuoted(*type_url)); |
| |
| return Traits::WithDynamicType( |
| desc, std::string(*type_url), |
| [&](const Desc<Traits>& any_desc) -> absl::Status { |
| absl::string_view any_bytes; |
| if (has_value) { |
| absl::StatusOr<absl::string_view> bytes = |
| Traits::GetString(value_field, writer.ScratchBuf(), msg); |
| RETURN_IF_ERROR(bytes.status()); |
| any_bytes = *bytes; |
| } |
| |
| return Traits::WithDecodedMessage( |
| any_desc, any_bytes, |
| [&](const Msg<Traits>& unerased) -> absl::Status { |
| bool first = false; |
| if (ClassifyMessage(Traits::TypeName(any_desc)) != |
| MessageType::kNotWellKnown) { |
| writer.WriteComma(first); |
| writer.NewLine(); |
| writer.Write("\"value\":"); |
| writer.Whitespace(" "); |
| RETURN_IF_ERROR( |
| WriteMessage<Traits>(writer, unerased, any_desc)); |
| } else { |
| RETURN_IF_ERROR( |
| WriteFields<Traits>(writer, unerased, any_desc, first)); |
| } |
| writer.Pop(); |
| if (!first) { |
| writer.NewLine(); |
| } |
| writer.Write("}"); |
| return absl::OkStatus(); |
| }); |
| }); |
| } |
| |
| template <typename Traits> |
| absl::Status WriteMessage(JsonWriter& writer, const Msg<Traits>& msg, |
| const Desc<Traits>& desc, bool is_top_level) { |
| switch (ClassifyMessage(Traits::TypeName(desc))) { |
| case MessageType::kAny: |
| return WriteAny<Traits>(writer, msg, desc); |
| case MessageType::kWrapper: { |
| auto field = Traits::MustHaveField(desc, 1); |
| if (Traits::GetSize(field, msg) == 0) { |
| return WriteSingular<Traits>(writer, field); |
| } |
| return WriteSingular<Traits>(writer, field, msg); |
| } |
| case MessageType::kValue: |
| return WriteValue<Traits>(writer, msg, desc, is_top_level); |
| case MessageType::kStruct: |
| return WriteStructValue<Traits>(writer, msg, desc); |
| case MessageType::kList: |
| return WriteListValue<Traits>(writer, msg, desc); |
| case MessageType::kTimestamp: |
| return WriteTimestamp<Traits>(writer, msg, desc); |
| case MessageType::kDuration: |
| return WriteDuration<Traits>(writer, msg, desc); |
| case MessageType::kFieldMask: |
| return WriteFieldMask<Traits>(writer, msg, desc); |
| default: { |
| writer.Write("{"); |
| writer.Push(); |
| bool first = true; |
| RETURN_IF_ERROR(WriteFields<Traits>(writer, msg, desc, first)); |
| writer.Pop(); |
| if (!first) { |
| writer.NewLine(); |
| } |
| writer.Write("}"); |
| return absl::OkStatus(); |
| } |
| } |
| } |
| } // namespace |
| |
| absl::Status MessageToJsonString(const Message& message, std::string* output, |
| json_internal::WriterOptions options) { |
| PROTOBUF_DLOG(INFO) << "json2/input: " << message.DebugString(); |
| io::StringOutputStream out(output); |
| JsonWriter writer(&out, options); |
| absl::Status s = WriteMessage<UnparseProto2Descriptor>( |
| writer, message, *message.GetDescriptor(), /*is_top_level=*/true); |
| PROTOBUF_DLOG(INFO) << "json2/status: " << s; |
| RETURN_IF_ERROR(s); |
| |
| writer.NewLine(); |
| PROTOBUF_DLOG(INFO) << "json2/output: " << absl::CHexEscape(*output); |
| return absl::OkStatus(); |
| } |
| |
| absl::Status BinaryToJsonStream(google::protobuf::util::TypeResolver* resolver, |
| const std::string& type_url, |
| io::ZeroCopyInputStream* binary_input, |
| io::ZeroCopyOutputStream* json_output, |
| json_internal::WriterOptions options) { |
| // NOTE: Most of the contortions in this function are to allow for capture of |
| // input and output of the parser in GOOGLE_DLOG mode. Destruction order is very |
| // critical in this function, because io::ZeroCopy*Stream types usually only |
| // flush on destruction. |
| |
| // For GOOGLE_DLOG, we would like to print out the input and output, which requires |
| // buffering both instead of doing "zero copy". This block, and the one at |
| // the end of the function, set up and tear down interception of the input |
| // and output streams. |
| std::string copy; |
| std::string out; |
| absl::optional<io::ArrayInputStream> tee_input; |
| absl::optional<io::StringOutputStream> tee_output; |
| if (PROTOBUF_DEBUG) { |
| const void* data; |
| int len; |
| while (binary_input->Next(&data, &len)) { |
| copy.resize(copy.size() + len); |
| std::memcpy(©[copy.size() - len], data, len); |
| } |
| tee_input.emplace(copy.data(), copy.size()); |
| tee_output.emplace(&out); |
| } |
| |
| PROTOBUF_DLOG(INFO) << "json2/input: " << absl::BytesToHexString(copy); |
| |
| ResolverPool pool(resolver); |
| auto desc = pool.FindMessage(type_url); |
| RETURN_IF_ERROR(desc.status()); |
| |
| io::CodedInputStream stream(tee_input.has_value() ? &*tee_input |
| : binary_input); |
| auto msg = UntypedMessage::ParseFromStream(*desc, stream); |
| RETURN_IF_ERROR(msg.status()); |
| |
| JsonWriter writer(tee_output.has_value() ? &*tee_output : json_output, |
| options); |
| absl::Status s = WriteMessage<UnparseProto3Type>( |
| writer, *msg, UnparseProto3Type::GetDesc(*msg), |
| /*is_top_level=*/true); |
| PROTOBUF_DLOG(INFO) << "json2/status: " << s; |
| RETURN_IF_ERROR(s); |
| |
| if (PROTOBUF_DEBUG) { |
| tee_output.reset(); // Flush the output stream. |
| io::zc_sink_internal::ZeroCopyStreamByteSink(json_output) |
| .Append(out.data(), out.size()); |
| } |
| |
| PROTOBUF_DLOG(INFO) << "json2/output: " << absl::CHexEscape(out); |
| |
| writer.NewLine(); |
| return absl::OkStatus(); |
| } |
| } // namespace json_internal |
| } // namespace protobuf |
| } // namespace google |