aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/util/internal
diff options
context:
space:
mode:
authorGravatar Jisi Liu <jisi.liu@gmail.com>2016-03-30 11:39:59 -0700
committerGravatar Jisi Liu <jisi.liu@gmail.com>2016-03-30 11:39:59 -0700
commit3b3c8abb9635eb3ea078a821a99c9ef29d66dff7 (patch)
tree7d2ec154f15c9f9153d890e76b6cf30e471ea488 /src/google/protobuf/util/internal
parent78105897a8f01c7be9cf8502b6c58d47eb1ccdd7 (diff)
Integrate google internal changes.
Diffstat (limited to 'src/google/protobuf/util/internal')
-rw-r--r--src/google/protobuf/util/internal/constants.h20
-rw-r--r--src/google/protobuf/util/internal/datapiece.cc46
-rw-r--r--src/google/protobuf/util/internal/datapiece.h41
-rw-r--r--src/google/protobuf/util/internal/default_value_objectwriter.cc64
-rw-r--r--src/google/protobuf/util/internal/default_value_objectwriter.h50
-rw-r--r--src/google/protobuf/util/internal/json_objectwriter.cc20
-rw-r--r--src/google/protobuf/util/internal/json_objectwriter.h15
-rw-r--r--src/google/protobuf/util/internal/json_objectwriter_test.cc26
-rw-r--r--src/google/protobuf/util/internal/json_stream_parser.cc62
-rw-r--r--src/google/protobuf/util/internal/json_stream_parser_test.cc119
-rw-r--r--src/google/protobuf/util/internal/object_writer.h19
-rw-r--r--src/google/protobuf/util/internal/proto_writer.cc124
-rw-r--r--src/google/protobuf/util/internal/proto_writer.h21
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource.cc55
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource.h31
-rw-r--r--src/google/protobuf/util/internal/protostream_objectsource_test.cc150
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter.cc54
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter.h25
-rw-r--r--src/google/protobuf/util/internal/protostream_objectwriter_test.cc196
-rw-r--r--src/google/protobuf/util/internal/testdata/books.proto7
-rw-r--r--src/google/protobuf/util/internal/type_info_test_helper.cc6
-rw-r--r--src/google/protobuf/util/internal/type_info_test_helper.h8
-rw-r--r--src/google/protobuf/util/internal/utility.cc7
23 files changed, 980 insertions, 186 deletions
diff --git a/src/google/protobuf/util/internal/constants.h b/src/google/protobuf/util/internal/constants.h
index 0cb8f6e1..e556888c 100644
--- a/src/google/protobuf/util/internal/constants.h
+++ b/src/google/protobuf/util/internal/constants.h
@@ -43,13 +43,23 @@ namespace converter {
const char kTypeServiceBaseUrl[] = "type.googleapis.com";
// Format string for RFC3339 timestamp formatting.
-const char kRfc3339TimeFormat[] = "%Y-%m-%dT%H:%M:%S";
+const char kRfc3339TimeFormat[] = "%E4Y-%m-%dT%H:%M:%S";
-// Minimum seconds allowed in a google.protobuf.TimeStamp or Duration value.
-const int64 kMinSeconds = -315576000000;
+// Same as above, but the year value is not zero-padded i.e. this accepts
+// timestamps like "1-01-0001T23:59:59Z" instead of "0001-01-0001T23:59:59Z".
+const char kRfc3339TimeFormatNoPadding[] = "%Y-%m-%dT%H:%M:%S";
-// Maximum seconds allowed in a google.protobuf.TimeStamp or Duration value.
-const int64 kMaxSeconds = 315576000000;
+// Minimun seconds allowed in a google.protobuf.Timestamp value.
+const int64 kTimestampMinSeconds = -62135596800;
+
+// Maximum seconds allowed in a google.protobuf.Timestamp value.
+const int64 kTimestampMaxSeconds = 253402300799;
+
+// Minimum seconds allowed in a google.protobuf.Duration value.
+const int64 kDurationMinSeconds = -315576000000;
+
+// Maximum seconds allowed in a google.protobuf.Duration value.
+const int64 kDurationMaxSeconds = 315576000000;
// Nano seconds in a second.
const int32 kNanosPerSecond = 1000000000;
diff --git a/src/google/protobuf/util/internal/datapiece.cc b/src/google/protobuf/util/internal/datapiece.cc
index b557429f..72c0aca6 100644
--- a/src/google/protobuf/util/internal/datapiece.cc
+++ b/src/google/protobuf/util/internal/datapiece.cc
@@ -47,6 +47,7 @@ using google::protobuf::EnumDescriptor;
using google::protobuf::EnumValueDescriptor;
;
;
+;
using util::error::Code;
using util::Status;
using util::StatusOr;
@@ -248,11 +249,8 @@ StatusOr<string> DataPiece::ToBytes() const {
if (type_ == TYPE_BYTES) return str_.ToString();
if (type_ == TYPE_STRING) {
string decoded;
- if (!WebSafeBase64Unescape(str_, &decoded)) {
- if (!Base64Unescape(str_, &decoded)) {
- return InvalidArgument(
- ValueAsStringOrDefault("Invalid data in input."));
- }
+ if (!DecodeBase64(str_, &decoded)) {
+ return InvalidArgument(ValueAsStringOrDefault("Invalid data in input."));
}
return decoded;
} else {
@@ -313,11 +311,49 @@ StatusOr<To> DataPiece::GenericConvert() const {
template <typename To>
StatusOr<To> DataPiece::StringToNumber(bool (*func)(StringPiece, To*)) const {
+ if (str_.size() > 0 && (str_[0] == ' ' || str_[str_.size() - 1] == ' ')) {
+ return InvalidArgument(StrCat("\"", str_, "\""));
+ }
To result;
if (func(str_, &result)) return result;
return InvalidArgument(StrCat("\"", str_.ToString(), "\""));
}
+bool DataPiece::DecodeBase64(StringPiece src, string* dest) const {
+ // Try web-safe decode first, if it fails, try the non-web-safe decode.
+ if (WebSafeBase64Unescape(src, dest)) {
+ if (use_strict_base64_decoding_) {
+ // In strict mode, check if the escaped version gives us the same value as
+ // unescaped.
+ string encoded;
+ // WebSafeBase64Escape does no padding by default.
+ WebSafeBase64Escape(*dest, &encoded);
+ // Remove trailing padding '=' characters before comparison.
+ StringPiece src_no_padding(src, 0, src.ends_with("=")
+ ? src.find_last_not_of('=') + 1
+ : src.length());
+ return encoded == src_no_padding;
+ }
+ return true;
+ }
+
+ if (Base64Unescape(src, dest)) {
+ if (use_strict_base64_decoding_) {
+ string encoded;
+ Base64Escape(
+ reinterpret_cast<const unsigned char*>(dest->data()), dest->length(),
+ &encoded, false);
+ StringPiece src_no_padding(src, 0, src.ends_with("=")
+ ? src.find_last_not_of('=') + 1
+ : src.length());
+ return encoded == src_no_padding;
+ }
+ return true;
+ }
+
+ return false;
+}
+
} // namespace converter
} // namespace util
} // namespace protobuf
diff --git a/src/google/protobuf/util/internal/datapiece.h b/src/google/protobuf/util/internal/datapiece.h
index f22bfe70..8b2e35d3 100644
--- a/src/google/protobuf/util/internal/datapiece.h
+++ b/src/google/protobuf/util/internal/datapiece.h
@@ -83,12 +83,15 @@ class LIBPROTOBUF_EXPORT DataPiece {
explicit DataPiece(const double value) : type_(TYPE_DOUBLE), double_(value) {}
explicit DataPiece(const float value) : type_(TYPE_FLOAT), float_(value) {}
explicit DataPiece(const bool value) : type_(TYPE_BOOL), bool_(value) {}
- explicit DataPiece(StringPiece value)
+ DataPiece(StringPiece value, bool use_strict_base64_decoding)
: type_(TYPE_STRING),
- str_(StringPiecePod::CreateFromStringPiece(value)) {}
+ str_(StringPiecePod::CreateFromStringPiece(value)),
+ use_strict_base64_decoding_(use_strict_base64_decoding) {}
// Constructor for bytes. The second parameter is not used.
- explicit DataPiece(StringPiece value, bool dummy)
- : type_(TYPE_BYTES), str_(StringPiecePod::CreateFromStringPiece(value)) {}
+ DataPiece(StringPiece value, bool dummy, bool use_strict_base64_decoding)
+ : type_(TYPE_BYTES),
+ str_(StringPiecePod::CreateFromStringPiece(value)),
+ use_strict_base64_decoding_(use_strict_base64_decoding) {}
DataPiece(const DataPiece& r) : type_(r.type_), str_(r.str_) {}
DataPiece& operator=(const DataPiece& x) {
type_ = x.type_;
@@ -165,32 +168,13 @@ class LIBPROTOBUF_EXPORT DataPiece {
template <typename To>
util::StatusOr<To> StringToNumber(bool (*func)(StringPiece, To*)) const;
+ // Decodes a base64 string. Returns true on success.
+ bool DecodeBase64(StringPiece src, string* dest) const;
+
// Data type for this piece of data.
Type type_;
- // StringPiece is not a POD and can not be used in an union (pre C++11). We
- // need a POD version of it.
- struct StringPiecePod {
- const char* data;
- int size;
-
- // Create from a StringPiece.
- static StringPiecePod CreateFromStringPiece(StringPiece str) {
- StringPiecePod pod;
- pod.data = str.data();
- pod.size = str.size();
- return pod;
- }
-
- // Cast to StringPiece.
- operator StringPiece() const { return StringPiece(data, size); }
-
- bool operator==(const char* value) const {
- return StringPiece(data, size) == StringPiece(value);
- }
-
- string ToString() const { return string(data, size); }
- };
+ typedef ::google::protobuf::internal::StringPiecePod StringPiecePod;
// Stored piece of data.
union {
@@ -203,6 +187,9 @@ class LIBPROTOBUF_EXPORT DataPiece {
bool bool_;
StringPiecePod str_;
};
+
+ // Uses a stricter version of base64 decoding for byte fields.
+ bool use_strict_base64_decoding_;
};
} // namespace converter
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.cc b/src/google/protobuf/util/internal/default_value_objectwriter.cc
index a63e560d..21d7a2e4 100644
--- a/src/google/protobuf/util/internal/default_value_objectwriter.cc
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.cc
@@ -51,7 +51,7 @@ template <typename T>
T ConvertTo(StringPiece value, StatusOr<T> (DataPiece::*converter_fn)() const,
T default_value) {
if (value.empty()) return default_value;
- StatusOr<T> result = (DataPiece(value).*converter_fn)();
+ StatusOr<T> result = (DataPiece(value, true).*converter_fn)();
return result.ok() ? result.ValueOrDie() : default_value;
}
} // namespace
@@ -64,6 +64,7 @@ DefaultValueObjectWriter::DefaultValueObjectWriter(
type_(type),
current_(NULL),
root_(NULL),
+ field_scrub_callback_(NULL),
ow_(ow) {}
DefaultValueObjectWriter::~DefaultValueObjectWriter() {
@@ -153,7 +154,7 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::RenderString(
// Since StringPiece is essentially a pointer, takes a copy of "value" to
// avoid ownership issues.
string_values_.push_back(new string(value.ToString()));
- RenderDataPiece(name, DataPiece(*string_values_.back()));
+ RenderDataPiece(name, DataPiece(*string_values_.back(), true));
}
return this;
}
@@ -163,7 +164,7 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::RenderBytes(
if (current_ == NULL) {
ow_->RenderBytes(name, value);
} else {
- RenderDataPiece(name, DataPiece(value));
+ RenderDataPiece(name, DataPiece(value, false, true));
}
return this;
}
@@ -178,16 +179,25 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::RenderNull(
return this;
}
+void DefaultValueObjectWriter::RegisterFieldScrubCallBack(
+ FieldScrubCallBackPtr field_scrub_callback) {
+ field_scrub_callback_.reset(field_scrub_callback.release());
+}
+
DefaultValueObjectWriter::Node::Node(const string& name,
const google::protobuf::Type* type,
NodeKind kind, const DataPiece& data,
- bool is_placeholder)
+ bool is_placeholder,
+ const vector<string>& path,
+ FieldScrubCallBack* field_scrub_callback)
: name_(name),
type_(type),
kind_(kind),
is_any_(false),
data_(data),
- is_placeholder_(is_placeholder) {}
+ is_placeholder_(is_placeholder),
+ path_(path),
+ field_scrub_callback_(field_scrub_callback) {}
DefaultValueObjectWriter::Node* DefaultValueObjectWriter::Node::FindChild(
StringPiece name) {
@@ -291,6 +301,19 @@ void DefaultValueObjectWriter::Node::PopulateChildren(
for (int i = 0; i < type_->fields_size(); ++i) {
const google::protobuf::Field& field = type_->fields(i);
+
+ // This code is checking if the field to be added to the tree should be
+ // scrubbed or not by calling the field_scrub_callback_ callback function.
+ vector<string> path;
+ if (!path_.empty()) {
+ path.insert(path.begin(), path_.begin(), path_.end());
+ }
+ path.push_back(field.name());
+ if (field_scrub_callback_ != NULL &&
+ field_scrub_callback_->Run(path, &field)) {
+ continue;
+ }
+
hash_map<string, int>::iterator found =
orig_children_map.find(field.name());
// If the child field has already been set, we just add it to the new list
@@ -343,7 +366,7 @@ void DefaultValueObjectWriter::Node::PopulateChildren(
field.json_name(), field_type, kind,
kind == PRIMITIVE ? CreateDefaultDataPieceForField(field, typeinfo)
: DataPiece::NullData(),
- true));
+ true, path, field_scrub_callback_));
new_children.push_back(child.release());
}
// Adds all leftover nodes in children_ to the beginning of new_child.
@@ -368,7 +391,8 @@ void DefaultValueObjectWriter::MaybePopulateChildrenOfAny(Node* node) {
DataPiece DefaultValueObjectWriter::FindEnumDefault(
const google::protobuf::Field& field, const TypeInfo* typeinfo) {
- if (!field.default_value().empty()) return DataPiece(field.default_value());
+ if (!field.default_value().empty())
+ return DataPiece(field.default_value(), true);
const google::protobuf::Enum* enum_type =
typeinfo->GetEnumByTypeUrl(field.type_url());
@@ -379,7 +403,7 @@ DataPiece DefaultValueObjectWriter::FindEnumDefault(
}
// We treat the first value as the default if none is specified.
return enum_type->enumvalue_size() > 0
- ? DataPiece(enum_type->enumvalue(0).name())
+ ? DataPiece(enum_type->enumvalue(0).name(), true)
: DataPiece::NullData();
}
@@ -416,10 +440,10 @@ DataPiece DefaultValueObjectWriter::CreateDefaultDataPieceForField(
ConvertTo<bool>(field.default_value(), &DataPiece::ToBool, false));
}
case google::protobuf::Field_Kind_TYPE_STRING: {
- return DataPiece(field.default_value());
+ return DataPiece(field.default_value(), true);
}
case google::protobuf::Field_Kind_TYPE_BYTES: {
- return DataPiece(field.default_value(), false);
+ return DataPiece(field.default_value(), false, true);
}
case google::protobuf::Field_Kind_TYPE_UINT32:
case google::protobuf::Field_Kind_TYPE_FIXED32: {
@@ -436,8 +460,9 @@ DataPiece DefaultValueObjectWriter::CreateDefaultDataPieceForField(
DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject(
StringPiece name) {
if (current_ == NULL) {
+ vector<string> path;
root_.reset(new Node(name.ToString(), &type_, OBJECT, DataPiece::NullData(),
- false));
+ false, path, field_scrub_callback_.get()));
root_->PopulateChildren(typeinfo_);
current_ = root_.get();
return this;
@@ -451,7 +476,9 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::StartObject(
name.ToString(), ((current_->kind() == LIST || current_->kind() == MAP)
? current_->type()
: NULL),
- OBJECT, DataPiece::NullData(), false));
+ OBJECT, DataPiece::NullData(), false,
+ child == NULL ? current_->path() : child->path(),
+ field_scrub_callback_.get()));
child = node.get();
current_->AddChild(node.release());
}
@@ -480,8 +507,9 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::EndObject() {
DefaultValueObjectWriter* DefaultValueObjectWriter::StartList(
StringPiece name) {
if (current_ == NULL) {
- root_.reset(
- new Node(name.ToString(), &type_, LIST, DataPiece::NullData(), false));
+ vector<string> path;
+ root_.reset(new Node(name.ToString(), &type_, LIST, DataPiece::NullData(),
+ false, path, field_scrub_callback_.get()));
current_ = root_.get();
return this;
}
@@ -489,7 +517,9 @@ DefaultValueObjectWriter* DefaultValueObjectWriter::StartList(
Node* child = current_->FindChild(name);
if (child == NULL || child->kind() != LIST) {
google::protobuf::scoped_ptr<Node> node(
- new Node(name.ToString(), NULL, LIST, DataPiece::NullData(), false));
+ new Node(name.ToString(), NULL, LIST, DataPiece::NullData(), false,
+ child == NULL ? current_->path() : child->path(),
+ field_scrub_callback_.get()));
child = node.get();
current_->AddChild(node.release());
}
@@ -545,7 +575,9 @@ void DefaultValueObjectWriter::RenderDataPiece(StringPiece name,
if (child == NULL || child->kind() != PRIMITIVE) {
// No children are found, creates a new child.
google::protobuf::scoped_ptr<Node> node(
- new Node(name.ToString(), NULL, PRIMITIVE, data, false));
+ new Node(name.ToString(), NULL, PRIMITIVE, data, false,
+ child == NULL ? current_->path() : child->path(),
+ field_scrub_callback_.get()));
child = node.get();
current_->AddChild(node.release());
} else {
diff --git a/src/google/protobuf/util/internal/default_value_objectwriter.h b/src/google/protobuf/util/internal/default_value_objectwriter.h
index 695b9dd8..1d85bed8 100644
--- a/src/google/protobuf/util/internal/default_value_objectwriter.h
+++ b/src/google/protobuf/util/internal/default_value_objectwriter.h
@@ -38,6 +38,7 @@
#include <stack>
#include <vector>
+#include <google/protobuf/stubs/callback.h>
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/util/internal/type_info.h>
#include <google/protobuf/util/internal/datapiece.h>
@@ -59,6 +60,25 @@ namespace converter {
// with their default values (0 for numbers, "" for strings, etc).
class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
public:
+ // A Callback function to check whether a field needs to be scrubbed.
+ //
+ // Returns true if the field should not be present in the output. Returns
+ // false otherwise.
+ //
+ // The 'path' parameter is a vector of path to the field from root. For
+ // example: if a nested field "a.b.c" (b is the parent message field of c and
+ // a is the parent message field of b), then the vector should contain { "a",
+ // "b", "c" }.
+ //
+ // The Field* should point to the google::protobuf::Field of "c".
+ typedef ResultCallback2<bool /*return*/,
+ const std::vector<string>& /*path of the field*/,
+ const google::protobuf::Field* /*field*/>
+ FieldScrubCallBack;
+
+ // A unique pointer to a DefaultValueObjectWriter::FieldScrubCallBack.
+ typedef google::protobuf::scoped_ptr<FieldScrubCallBack> FieldScrubCallBackPtr;
+
DefaultValueObjectWriter(TypeResolver* type_resolver,
const google::protobuf::Type& type,
ObjectWriter* ow);
@@ -98,6 +118,10 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
virtual DefaultValueObjectWriter* RenderNull(StringPiece name);
+ // Register the callback for scrubbing of fields. Owership of
+ // field_scrub_callback pointer is also transferred to this class
+ void RegisterFieldScrubCallBack(FieldScrubCallBackPtr field_scrub_callback);
+
private:
enum NodeKind {
PRIMITIVE = 0,
@@ -111,7 +135,8 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
class LIBPROTOBUF_EXPORT Node {
public:
Node(const string& name, const google::protobuf::Type* type, NodeKind kind,
- const DataPiece& data, bool is_placeholder);
+ const DataPiece& data, bool is_placeholder, const vector<string>& path,
+ FieldScrubCallBack* field_scrub_callback);
virtual ~Node() {
for (int i = 0; i < children_.size(); ++i) {
delete children_[i];
@@ -137,17 +162,19 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
// Accessors
const string& name() const { return name_; }
- const google::protobuf::Type* type() { return type_; }
+ const vector<string>& path() const { return path_; }
+
+ const google::protobuf::Type* type() const { return type_; }
void set_type(const google::protobuf::Type* type) { type_ = type; }
- NodeKind kind() { return kind_; }
+ NodeKind kind() const { return kind_; }
- int number_of_children() { return children_.size(); }
+ int number_of_children() const { return children_.size(); }
void set_data(const DataPiece& data) { data_ = data; }
- bool is_any() { return is_any_; }
+ bool is_any() const { return is_any_; }
void set_is_any(bool is_any) { is_any_ = is_any; }
@@ -181,6 +208,15 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
// the parent node's StartObject()/StartList() method is called with this
// node's name.
bool is_placeholder_;
+
+ // Path of the field of this node
+ std::vector<string> path_;
+
+ // Pointer to function for determining whether a field needs to be scrubbed
+ // or not. This callback is owned by the creator of this node.
+ FieldScrubCallBack* field_scrub_callback_;
+
+ GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Node);
};
// Populates children of "node" if it is an "any" Node and its real type has
@@ -221,6 +257,10 @@ class LIBPROTOBUF_EXPORT DefaultValueObjectWriter : public ObjectWriter {
// The stack to hold the path of Nodes from current_ to root_;
std::stack<Node*> stack_;
+ // Unique Pointer to function for determining whether a field needs to be
+ // scrubbed or not.
+ FieldScrubCallBackPtr field_scrub_callback_;
+
ObjectWriter* ow_;
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DefaultValueObjectWriter);
diff --git a/src/google/protobuf/util/internal/json_objectwriter.cc b/src/google/protobuf/util/internal/json_objectwriter.cc
index 94d2ab7a..b84210c1 100644
--- a/src/google/protobuf/util/internal/json_objectwriter.cc
+++ b/src/google/protobuf/util/internal/json_objectwriter.cc
@@ -46,6 +46,7 @@ namespace util {
namespace converter {
using strings::ArrayByteSource;
+;
JsonObjectWriter::~JsonObjectWriter() {
if (!element_->is_root()) {
@@ -81,13 +82,11 @@ JsonObjectWriter* JsonObjectWriter::EndList() {
return this;
}
-JsonObjectWriter* JsonObjectWriter::RenderBool(StringPiece name,
- bool value) {
+JsonObjectWriter* JsonObjectWriter::RenderBool(StringPiece name, bool value) {
return RenderSimple(name, value ? "true" : "false");
}
-JsonObjectWriter* JsonObjectWriter::RenderInt32(StringPiece name,
- int32 value) {
+JsonObjectWriter* JsonObjectWriter::RenderInt32(StringPiece name, int32 value) {
return RenderSimple(name, SimpleItoa(value));
}
@@ -96,8 +95,7 @@ JsonObjectWriter* JsonObjectWriter::RenderUint32(StringPiece name,
return RenderSimple(name, SimpleItoa(value));
}
-JsonObjectWriter* JsonObjectWriter::RenderInt64(StringPiece name,
- int64 value) {
+JsonObjectWriter* JsonObjectWriter::RenderInt64(StringPiece name, int64 value) {
WritePrefix(name);
WriteChar('"');
stream_->WriteString(SimpleItoa(value));
@@ -124,8 +122,7 @@ JsonObjectWriter* JsonObjectWriter::RenderDouble(StringPiece name,
return RenderString(name, DoubleAsString(value));
}
-JsonObjectWriter* JsonObjectWriter::RenderFloat(StringPiece name,
- float value) {
+JsonObjectWriter* JsonObjectWriter::RenderFloat(StringPiece name, float value) {
if (MathLimits<float>::IsFinite(value)) {
return RenderSimple(name, SimpleFtoa(value));
}
@@ -148,7 +145,12 @@ JsonObjectWriter* JsonObjectWriter::RenderBytes(StringPiece name,
StringPiece value) {
WritePrefix(name);
string base64;
- Base64Escape(value, &base64);
+
+ if (use_websafe_base64_for_bytes_)
+ WebSafeBase64Escape(value.ToString(), &base64);
+ else
+ Base64Escape(value, &base64);
+
WriteChar('"');
// TODO(wpoon): Consider a ByteSink solution that writes the base64 bytes
// directly to the stream, rather than first putting them
diff --git a/src/google/protobuf/util/internal/json_objectwriter.h b/src/google/protobuf/util/internal/json_objectwriter.h
index 761a0a10..cb7e2fb3 100644
--- a/src/google/protobuf/util/internal/json_objectwriter.h
+++ b/src/google/protobuf/util/internal/json_objectwriter.h
@@ -90,9 +90,10 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
JsonObjectWriter(StringPiece indent_string,
google::protobuf::io::CodedOutputStream* out)
: element_(new Element(NULL)),
- stream_(out), sink_(out),
- indent_string_(indent_string.ToString()) {
- }
+ stream_(out),
+ sink_(out),
+ indent_string_(indent_string.ToString()),
+ use_websafe_base64_for_bytes_(false) {}
virtual ~JsonObjectWriter();
// ObjectWriter methods.
@@ -111,6 +112,10 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
virtual JsonObjectWriter* RenderBytes(StringPiece name, StringPiece value);
virtual JsonObjectWriter* RenderNull(StringPiece name);
+ void set_use_websafe_base64_for_bytes(bool value) {
+ use_websafe_base64_for_bytes_ = value;
+ }
+
protected:
class LIBPROTOBUF_EXPORT Element : public BaseElement {
public:
@@ -195,6 +200,10 @@ class LIBPROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
ByteSinkWrapper sink_;
const string indent_string_;
+ // Whether to use regular or websafe base64 encoding for byte fields. Defaults
+ // to regular base64 encoding.
+ bool use_websafe_base64_for_bytes_;
+
GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonObjectWriter);
};
diff --git a/src/google/protobuf/util/internal/json_objectwriter_test.cc b/src/google/protobuf/util/internal/json_objectwriter_test.cc
index 9d820162..b87b06ac 100644
--- a/src/google/protobuf/util/internal/json_objectwriter_test.cc
+++ b/src/google/protobuf/util/internal/json_objectwriter_test.cc
@@ -58,7 +58,7 @@ class JsonObjectWriterTest : public ::testing::Test {
string output_;
StringOutputStream* const str_stream_;
CodedOutputStream* const out_stream_;
- ObjectWriter* ow_;
+ JsonObjectWriter* ow_;
};
TEST_F(JsonObjectWriterTest, EmptyRootObject) {
@@ -283,6 +283,30 @@ TEST_F(JsonObjectWriterTest, Stringification) {
output_.substr(0, out_stream_->ByteCount()));
}
+TEST_F(JsonObjectWriterTest, TestRegularByteEncoding) {
+ ow_ = new JsonObjectWriter("", out_stream_);
+ ow_->StartObject("")
+ ->RenderBytes("bytes", "\x03\xef\xc0")
+ ->EndObject();
+
+ // Test that we get regular (non websafe) base64 encoding on byte fields by
+ // default.
+ EXPECT_EQ("{\"bytes\":\"A+/A\"}",
+ output_.substr(0, out_stream_->ByteCount()));
+}
+
+TEST_F(JsonObjectWriterTest, TestWebsafeByteEncoding) {
+ ow_ = new JsonObjectWriter("", out_stream_);
+ ow_->set_use_websafe_base64_for_bytes(true);
+ ow_->StartObject("")
+ ->RenderBytes("bytes", "\x03\xef\xc0")
+ ->EndObject();
+
+ // Test that we get websafe base64 encoding when explicitly asked.
+ EXPECT_EQ("{\"bytes\":\"A-_A\"}",
+ output_.substr(0, out_stream_->ByteCount()));
+}
+
} // namespace converter
} // namespace util
} // namespace protobuf
diff --git a/src/google/protobuf/util/internal/json_stream_parser.cc b/src/google/protobuf/util/internal/json_stream_parser.cc
index df916751..39be7b03 100644
--- a/src/google/protobuf/util/internal/json_stream_parser.cc
+++ b/src/google/protobuf/util/internal/json_stream_parser.cc
@@ -42,8 +42,9 @@
#include <google/protobuf/stubs/logging.h>
#include <google/protobuf/stubs/common.h>
-#include <google/protobuf/stubs/strutil.h>
#include <google/protobuf/util/internal/object_writer.h>
+#include <google/protobuf/util/internal/json_escaping.h>
+#include <google/protobuf/stubs/strutil.h>
namespace google {
namespace protobuf {
@@ -59,7 +60,7 @@ using util::error::INVALID_ARGUMENT;
namespace converter {
-// Number of digits in a unicode escape sequence (/uXXXX)
+// Number of digits in an escaped UTF-16 code unit ('\\' 'u' X X X X)
static const int kUnicodeEscapedLength = 6;
// Length of the true, false, and null literals.
@@ -419,9 +420,45 @@ util::Status JsonStreamParser::ParseUnicodeEscape() {
}
code = (code << 4) + hex_digit_to_int(p_.data()[i]);
}
+ if (code >= JsonEscaping::kMinHighSurrogate &&
+ code <= JsonEscaping::kMaxHighSurrogate) {
+ if (p_.length() < 2 * kUnicodeEscapedLength) {
+ if (!finishing_) {
+ return util::Status::CANCELLED;
+ }
+ if (!coerce_to_utf8_) {
+ return ReportFailure("Missing low surrogate.");
+ }
+ } else if (p_.data()[kUnicodeEscapedLength] == '\\' &&
+ p_.data()[kUnicodeEscapedLength + 1] == 'u') {
+ uint32 low_code = 0;
+ for (int i = kUnicodeEscapedLength + 2; i < 2 * kUnicodeEscapedLength;
+ ++i) {
+ if (!isxdigit(p_.data()[i])) {
+ return ReportFailure("Invalid escape sequence.");
+ }
+ low_code = (low_code << 4) + hex_digit_to_int(p_.data()[i]);
+ }
+ if (low_code >= JsonEscaping::kMinLowSurrogate &&
+ low_code <= JsonEscaping::kMaxLowSurrogate) {
+ // Convert UTF-16 surrogate pair to 21-bit Unicode codepoint.
+ code = (((code & 0x3FF) << 10) | (low_code & 0x3FF)) +
+ JsonEscaping::kMinSupplementaryCodePoint;
+ // Advance past the first code unit escape.
+ p_.remove_prefix(kUnicodeEscapedLength);
+ } else if (!coerce_to_utf8_) {
+ return ReportFailure("Invalid low surrogate.");
+ }
+ } else if (!coerce_to_utf8_) {
+ return ReportFailure("Missing low surrogate.");
+ }
+ }
+ if (!coerce_to_utf8_ && !IsValidCodePoint(code)) {
+ return ReportFailure("Invalid unicode code point.");
+ }
char buf[UTFmax];
int len = EncodeAsUTF8Char(code, buf);
- // Advance past the unicode escape.
+ // Advance past the [final] code unit escape.
p_.remove_prefix(kUnicodeEscapedLength);
parsed_storage_.append(buf, len);
return util::Status::OK;
@@ -473,7 +510,7 @@ util::Status JsonStreamParser::ParseNumberHelper(NumberResult* result) {
floating = true;
continue;
}
- if (c == '+' || c == '-') continue;
+ if (c == '+' || c == '-' || c == 'x') continue;
// Not a valid number character, break out.
break;
}
@@ -499,6 +536,10 @@ util::Status JsonStreamParser::ParseNumberHelper(NumberResult* result) {
// Positive non-floating point number, parse as a uint64.
if (!negative) {
+ // Octal/Hex numbers are not valid JSON values.
+ if (number.length() >= 2 && number[0] == '0') {
+ return ReportFailure("Octal/hex numbers are not valid JSON values.");
+ }
if (!safe_strtou64(number, &result->uint_val)) {
return ReportFailure("Unable to parse number.");
}
@@ -507,6 +548,10 @@ util::Status JsonStreamParser::ParseNumberHelper(NumberResult* result) {
return util::Status::OK;
}
+ // Octal/Hex numbers are not valid JSON values.
+ if (number.length() >= 3 && number[1] == '0') {
+ return ReportFailure("Octal/hex numbers are not valid JSON values.");
+ }
// Negative non-floating point number, parse as an int64.
if (!safe_strto64(number, &result->int_val)) {
return ReportFailure("Unable to parse number.");
@@ -677,8 +722,9 @@ util::Status JsonStreamParser::ReportFailure(StringPiece message) {
static const int kContextLength = 20;
const char* p_start = p_.data();
const char* json_start = json_.data();
- const char* begin = max(p_start - kContextLength, json_start);
- const char* end = min(p_start + kContextLength, json_start + json_.size());
+ const char* begin = std::max(p_start - kContextLength, json_start);
+ const char* end =
+ std::min(p_start + kContextLength, json_start + json_.size());
StringPiece segment(begin, end - begin);
string location(p_start - begin, ' ');
location.push_back('^');
@@ -706,8 +752,8 @@ void JsonStreamParser::SkipWhitespace() {
void JsonStreamParser::Advance() {
// Advance by moving one UTF8 character while making sure we don't go beyond
// the length of StringPiece.
- p_.remove_prefix(
- min<int>(p_.length(), UTF8FirstLetterNumBytes(p_.data(), p_.length())));
+ p_.remove_prefix(std::min<int>(
+ p_.length(), UTF8FirstLetterNumBytes(p_.data(), p_.length())));
}
util::Status JsonStreamParser::ParseKey() {
diff --git a/src/google/protobuf/util/internal/json_stream_parser_test.cc b/src/google/protobuf/util/internal/json_stream_parser_test.cc
index 3414826e..4b691107 100644
--- a/src/google/protobuf/util/internal/json_stream_parser_test.cc
+++ b/src/google/protobuf/util/internal/json_stream_parser_test.cc
@@ -124,8 +124,9 @@ class JsonStreamParserTest : public ::testing::Test {
EXPECT_OK(result);
}
- void DoErrorTest(StringPiece json, int split, StringPiece error_prefix) {
- util::Status result = RunTest(json, split);
+ void DoErrorTest(StringPiece json, int split, StringPiece error_prefix,
+ bool coerce_utf8 = false) {
+ util::Status result = RunTest(json, split, coerce_utf8);
EXPECT_EQ(util::error::INVALID_ARGUMENT, result.error_code());
StringPiece error_message(result.error_message());
EXPECT_EQ(error_prefix, error_message.substr(0, error_prefix.size()));
@@ -230,6 +231,32 @@ TEST_F(JsonStreamParserTest, SimpleUnsignedInt) {
}
}
+TEST_F(JsonStreamParserTest, OctalNumberIsInvalid) {
+ StringPiece str = "01234";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.");
+ }
+ str = "-01234";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.");
+ }
+}
+
+TEST_F(JsonStreamParserTest, HexNumberIsInvalid) {
+ StringPiece str = "0x1234";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.");
+ }
+ str = "-0x1234";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Octal/hex numbers are not valid JSON values.");
+ }
+ str = "12x34";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Unable to parse number.");
+ }
+}
+
// - single and double quoted strings
TEST_F(JsonStreamParserTest, EmptyDoubleQuotedString) {
StringPiece str = "\"\"";
@@ -351,19 +378,62 @@ TEST_F(JsonStreamParserTest, RejectNonUtf8WhenNotCoerced) {
DoErrorTest("\xFF{}", 0, "Encountered non UTF-8 code points.");
}
-#ifndef _MSC_VER
// - unicode handling in strings
TEST_F(JsonStreamParserTest, UnicodeEscaping) {
StringPiece str = "[\"\\u0639\\u0631\\u0628\\u0649\"]";
for (int i = 0; i <= str.length(); ++i) {
- // TODO(xiaofeng): Figure out what default encoding to use for JSON strings.
- // In protobuf we use UTF-8 for strings, but for JSON we probably should
- // allow different encodings?
- ow_.StartList("")->RenderString("", "\u0639\u0631\u0628\u0649")->EndList();
+ ow_.StartList("")
+ ->RenderString("", "\xD8\xB9\xD8\xB1\xD8\xA8\xD9\x89")
+ ->EndList();
+ DoTest(str, i);
+ }
+}
+
+// - unicode UTF-16 surrogate pair handling in strings
+TEST_F(JsonStreamParserTest, UnicodeSurrogatePairEscaping) {
+ StringPiece str =
+ "[\"\\u0bee\\ud800\\uddf1\\uD80C\\uDDA4\\uD83d\\udC1D\U0001F36F\"]";
+ for (int i = 0; i <= str.length(); ++i) {
+ ow_.StartList("")
+ ->RenderString("",
+ "\xE0\xAF\xAE\xF0\x90\x87\xB1\xF0\x93\x86\xA4\xF0"
+ "\x9F\x90\x9D\xF0\x9F\x8D\xAF")
+ ->EndList();
DoTest(str, i);
}
}
-#endif
+
+
+TEST_F(JsonStreamParserTest, UnicodeEscapingInvalidCodePointWhenNotCoerced) {
+ // A low surrogate alone.
+ StringPiece str = "[\"\\ude36\"]";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Invalid unicode code point.");
+ }
+}
+
+TEST_F(JsonStreamParserTest, UnicodeEscapingMissingLowSurrogateWhenNotCoerced) {
+ // A high surrogate alone.
+ StringPiece str = "[\"\\ud83d\"]";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Missing low surrogate.");
+ }
+ // A high surrogate with some trailing characters.
+ str = "[\"\\ud83d|ude36\"]";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Missing low surrogate.");
+ }
+ // A high surrogate with half a low surrogate.
+ str = "[\"\\ud83d\\ude--\"]";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Invalid escape sequence.");
+ }
+ // Two high surrogates.
+ str = "[\"\\ud83d\\ud83d\"]";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Invalid low surrogate.");
+ }
+}
// - ascii escaping (\b, \f, \n, \r, \t, \v)
TEST_F(JsonStreamParserTest, AsciiEscaping) {
@@ -629,6 +699,22 @@ TEST_F(JsonStreamParserTest, DoubleTooBig) {
}
*/
+// invalid bare backslash.
+TEST_F(JsonStreamParserTest, UnfinishedEscape) {
+ StringPiece str = "\"\\";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Closing quote expected in string.");
+ }
+}
+
+// invalid bare backslash u.
+TEST_F(JsonStreamParserTest, UnfinishedUnicodeEscape) {
+ StringPiece str = "\"\\u";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Illegal hex string.");
+ }
+}
+
// invalid unicode sequence.
TEST_F(JsonStreamParserTest, UnicodeEscapeCutOff) {
StringPiece str = "\"\\u12";
@@ -637,6 +723,15 @@ TEST_F(JsonStreamParserTest, UnicodeEscapeCutOff) {
}
}
+// invalid unicode sequence (valid in modern EcmaScript but not in JSON).
+TEST_F(JsonStreamParserTest, BracketedUnicodeEscape) {
+ StringPiece str = "\"\\u{1f36f}\"";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Invalid escape sequence.");
+ }
+}
+
+
TEST_F(JsonStreamParserTest, UnicodeEscapeInvalidCharacters) {
StringPiece str = "\"\\u12$4hello";
for (int i = 0; i <= str.length(); ++i) {
@@ -644,6 +739,14 @@ TEST_F(JsonStreamParserTest, UnicodeEscapeInvalidCharacters) {
}
}
+// invalid unicode sequence in low half surrogate: g is not a hex digit.
+TEST_F(JsonStreamParserTest, UnicodeEscapeLowHalfSurrogateInvalidCharacters) {
+ StringPiece str = "\"\\ud800\\udcfg\"";
+ for (int i = 0; i <= str.length(); ++i) {
+ DoErrorTest(str, i, "Invalid escape sequence.");
+ }
+}
+
// Extra commas with an object or array.
TEST_F(JsonStreamParserTest, ExtraCommaInObject) {
StringPiece str = "{'k1': true,,'k2': false}";
diff --git a/src/google/protobuf/util/internal/object_writer.h b/src/google/protobuf/util/internal/object_writer.h
index e695f45e..9f07363d 100644
--- a/src/google/protobuf/util/internal/object_writer.h
+++ b/src/google/protobuf/util/internal/object_writer.h
@@ -105,10 +105,27 @@ class LIBPROTOBUF_EXPORT ObjectWriter {
static void RenderDataPieceTo(const DataPiece& data, StringPiece name,
ObjectWriter* ow);
+ // Indicates whether this ObjectWriter has completed writing the root message,
+ // usually this means writing of one complete object. Subclasses must override
+ // this behavior appropriately.
+ virtual bool done() { return false; }
+
+ void set_use_strict_base64_decoding(bool value) {
+ use_strict_base64_decoding_ = value;
+ }
+
+ bool use_strict_base64_decoding() const {
+ return use_strict_base64_decoding_;
+ }
+
protected:
- ObjectWriter() {}
+ ObjectWriter() : use_strict_base64_decoding_(true) {}
private:
+ // If set to true, we use the stricter version of base64 decoding for byte
+ // fields by making sure decoded version encodes back to the original string.
+ bool use_strict_base64_decoding_;
+
// Do not add any data members to this class.
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ObjectWriter);
};
diff --git a/src/google/protobuf/util/internal/proto_writer.cc b/src/google/protobuf/util/internal/proto_writer.cc
index 47e0009e..36b79410 100644
--- a/src/google/protobuf/util/internal/proto_writer.cc
+++ b/src/google/protobuf/util/internal/proto_writer.cc
@@ -447,9 +447,7 @@ ProtoWriter* ProtoWriter::StartObject(StringPiece name) {
return this;
}
- WriteTag(*field);
- element_.reset(new ProtoElement(element_.release(), field, *type, false));
- return this;
+ return StartObjectField(*field, *type);
}
ProtoWriter* ProtoWriter::EndObject() {
@@ -488,8 +486,7 @@ ProtoWriter* ProtoWriter::StartList(StringPiece name) {
return this;
}
- element_.reset(new ProtoElement(element_.release(), field, *type, true));
- return this;
+ return StartListField(*field, *type);
}
ProtoWriter* ProtoWriter::EndList() {
@@ -518,84 +515,128 @@ ProtoWriter* ProtoWriter::RenderDataPiece(StringPiece name,
return this;
}
+ return RenderPrimitiveField(*field, *type, data);
+}
+
+bool ProtoWriter::ValidOneof(const google::protobuf::Field& field,
+ StringPiece unnormalized_name) {
+ if (element_ == NULL) return true;
+
+ if (field.oneof_index() > 0) {
+ if (element_->IsOneofIndexTaken(field.oneof_index())) {
+ InvalidValue(
+ "oneof",
+ StrCat("oneof field '",
+ element_->type().oneofs(field.oneof_index() - 1),
+ "' is already set. Cannot set '", unnormalized_name, "'"));
+ return false;
+ }
+ element_->TakeOneofIndex(field.oneof_index());
+ }
+ return true;
+}
+
+bool ProtoWriter::IsRepeated(const google::protobuf::Field& field) {
+ return field.cardinality() ==
+ google::protobuf::Field_Cardinality_CARDINALITY_REPEATED;
+}
+
+ProtoWriter* ProtoWriter::StartObjectField(const google::protobuf::Field& field,
+ const google::protobuf::Type& type) {
+ WriteTag(field);
+ element_.reset(new ProtoElement(element_.release(), &field, type, false));
+ return this;
+}
+
+ProtoWriter* ProtoWriter::StartListField(const google::protobuf::Field& field,
+ const google::protobuf::Type& type) {
+ element_.reset(new ProtoElement(element_.release(), &field, type, true));
+ return this;
+}
+
+ProtoWriter* ProtoWriter::RenderPrimitiveField(
+ const google::protobuf::Field& field, const google::protobuf::Type& type,
+ const DataPiece& data) {
+ Status status;
+
// Pushing a ProtoElement and then pop it off at the end for 2 purposes:
// error location reporting and required field accounting.
- element_.reset(new ProtoElement(element_.release(), field, *type, false));
+ element_.reset(new ProtoElement(element_.release(), &field, type, false));
- if (field->kind() == google::protobuf::Field_Kind_TYPE_UNKNOWN ||
- field->kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) {
- InvalidValue(field->type_url().empty()
- ? google::protobuf::Field_Kind_Name(field->kind())
- : field->type_url(),
+ if (field.kind() == google::protobuf::Field_Kind_TYPE_UNKNOWN ||
+ field.kind() == google::protobuf::Field_Kind_TYPE_MESSAGE) {
+ InvalidValue(field.type_url().empty()
+ ? google::protobuf::Field_Kind_Name(field.kind())
+ : field.type_url(),
data.ValueAsStringOrDefault(""));
element_.reset(element()->pop());
return this;
}
- switch (field->kind()) {
+ switch (field.kind()) {
case google::protobuf::Field_Kind_TYPE_INT32: {
- status = WriteInt32(field->number(), data, stream_.get());
+ status = WriteInt32(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_SFIXED32: {
- status = WriteSFixed32(field->number(), data, stream_.get());
+ status = WriteSFixed32(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_SINT32: {
- status = WriteSInt32(field->number(), data, stream_.get());
+ status = WriteSInt32(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_FIXED32: {
- status = WriteFixed32(field->number(), data, stream_.get());
+ status = WriteFixed32(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_UINT32: {
- status = WriteUInt32(field->number(), data, stream_.get());
+ status = WriteUInt32(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_INT64: {
- status = WriteInt64(field->number(), data, stream_.get());
+ status = WriteInt64(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_SFIXED64: {
- status = WriteSFixed64(field->number(), data, stream_.get());
+ status = WriteSFixed64(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_SINT64: {
- status = WriteSInt64(field->number(), data, stream_.get());
+ status = WriteSInt64(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_FIXED64: {
- status = WriteFixed64(field->number(), data, stream_.get());
+ status = WriteFixed64(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_UINT64: {
- status = WriteUInt64(field->number(), data, stream_.get());
+ status = WriteUInt64(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_DOUBLE: {
- status = WriteDouble(field->number(), data, stream_.get());
+ status = WriteDouble(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_FLOAT: {
- status = WriteFloat(field->number(), data, stream_.get());
+ status = WriteFloat(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_BOOL: {
- status = WriteBool(field->number(), data, stream_.get());
+ status = WriteBool(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_BYTES: {
- status = WriteBytes(field->number(), data, stream_.get());
+ status = WriteBytes(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_STRING: {
- status = WriteString(field->number(), data, stream_.get());
+ status = WriteString(field.number(), data, stream_.get());
break;
}
case google::protobuf::Field_Kind_TYPE_ENUM: {
- status = WriteEnum(field->number(), data,
- typeinfo_->GetEnumByTypeUrl(field->type_url()),
+ status = WriteEnum(field.number(), data,
+ typeinfo_->GetEnumByTypeUrl(field.type_url()),
stream_.get());
break;
}
@@ -604,7 +645,7 @@ ProtoWriter* ProtoWriter::RenderDataPiece(StringPiece name,
}
if (!status.ok()) {
- InvalidValue(google::protobuf::Field_Kind_Name(field->kind()),
+ InvalidValue(google::protobuf::Field_Kind_Name(field.kind()),
status.error_message());
}
@@ -612,29 +653,6 @@ ProtoWriter* ProtoWriter::RenderDataPiece(StringPiece name,
return this;
}
-bool ProtoWriter::ValidOneof(const google::protobuf::Field& field,
- StringPiece unnormalized_name) {
- if (element_ == NULL) return true;
-
- if (field.oneof_index() > 0) {
- if (element_->IsOneofIndexTaken(field.oneof_index())) {
- InvalidValue(
- "oneof",
- StrCat("oneof field '",
- element_->type().oneofs(field.oneof_index() - 1),
- "' is already set. Cannot set '", unnormalized_name, "'"));
- return false;
- }
- element_->TakeOneofIndex(field.oneof_index());
- }
- return true;
-}
-
-bool ProtoWriter::IsRepeated(const google::protobuf::Field& field) {
- return field.cardinality() ==
- google::protobuf::Field_Cardinality_CARDINALITY_REPEATED;
-}
-
const google::protobuf::Field* ProtoWriter::BeginNamed(StringPiece name,
bool is_list) {
if (invalid_depth_ > 0) {
diff --git a/src/google/protobuf/util/internal/proto_writer.h b/src/google/protobuf/util/internal/proto_writer.h
index e631e56f..957565e7 100644
--- a/src/google/protobuf/util/internal/proto_writer.h
+++ b/src/google/protobuf/util/internal/proto_writer.h
@@ -106,10 +106,12 @@ class LIBPROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter {
return RenderDataPiece(name, DataPiece(value));
}
virtual ProtoWriter* RenderString(StringPiece name, StringPiece value) {
- return RenderDataPiece(name, DataPiece(value));
+ return RenderDataPiece(name,
+ DataPiece(value, use_strict_base64_decoding()));
}
virtual ProtoWriter* RenderBytes(StringPiece name, StringPiece value) {
- return RenderDataPiece(name, DataPiece(value, false));
+ return RenderDataPiece(
+ name, DataPiece(value, false, use_strict_base64_decoding()));
}
virtual ProtoWriter* RenderNull(StringPiece name) {
return RenderDataPiece(name, DataPiece::NullData());
@@ -126,7 +128,7 @@ class LIBPROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter {
}
// When true, we finished writing to output a complete message.
- bool done() const { return done_; }
+ bool done() { return done_; }
// Returns the proto stream object.
google::protobuf::io::CodedOutputStream* stream() { return stream_.get(); }
@@ -266,6 +268,19 @@ class LIBPROTOBUF_EXPORT ProtoWriter : public StructuredObjectWriter {
// Returns true if the field is repeated.
bool IsRepeated(const google::protobuf::Field& field);
+ // Starts an object given the field and the enclosing type.
+ ProtoWriter* StartObjectField(const google::protobuf::Field& field,
+ const google::protobuf::Type& type);
+
+ // Starts a list given the field and the enclosing type.
+ ProtoWriter* StartListField(const google::protobuf::Field& field,
+ const google::protobuf::Type& type);
+
+ // Renders a primitve field given the field and the enclosing type.
+ ProtoWriter* RenderPrimitiveField(const google::protobuf::Field& field,
+ const google::protobuf::Type& type,
+ const DataPiece& value);
+
private:
// Variables for describing the structure of the input tree:
// master_type_: descriptor for the whole protobuf message.
diff --git a/src/google/protobuf/util/internal/protostream_objectsource.cc b/src/google/protobuf/util/internal/protostream_objectsource.cc
index 034d616f..297c011a 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource.cc
+++ b/src/google/protobuf/util/internal/protostream_objectsource.cc
@@ -83,6 +83,29 @@ const google::protobuf::EnumValue* FindEnumValueByNumber(
// Utility function to format nanos.
const string FormatNanos(uint32 nanos);
+
+StatusOr<string> MapKeyDefaultValueAsString(
+ const google::protobuf::Field& field) {
+ switch (field.kind()) {
+ case google::protobuf::Field_Kind_TYPE_BOOL:
+ return string("false");
+ case google::protobuf::Field_Kind_TYPE_INT32:
+ case google::protobuf::Field_Kind_TYPE_INT64:
+ case google::protobuf::Field_Kind_TYPE_UINT32:
+ case google::protobuf::Field_Kind_TYPE_UINT64:
+ case google::protobuf::Field_Kind_TYPE_SINT32:
+ case google::protobuf::Field_Kind_TYPE_SINT64:
+ case google::protobuf::Field_Kind_TYPE_SFIXED32:
+ case google::protobuf::Field_Kind_TYPE_SFIXED64:
+ case google::protobuf::Field_Kind_TYPE_FIXED32:
+ case google::protobuf::Field_Kind_TYPE_FIXED64:
+ return string("0");
+ case google::protobuf::Field_Kind_TYPE_STRING:
+ return string();
+ default:
+ return Status(util::error::INTERNAL, "Invalid map key type.");
+ }
+}
} // namespace
@@ -92,14 +115,19 @@ ProtoStreamObjectSource::ProtoStreamObjectSource(
: stream_(stream),
typeinfo_(TypeInfo::NewTypeInfo(type_resolver)),
own_typeinfo_(true),
- type_(type) {
+ type_(type),
+ use_lower_camel_for_enums_(false) {
GOOGLE_LOG_IF(DFATAL, stream == NULL) << "Input stream is NULL.";
}
ProtoStreamObjectSource::ProtoStreamObjectSource(
google::protobuf::io::CodedInputStream* stream, const TypeInfo* typeinfo,
const google::protobuf::Type& type)
- : stream_(stream), typeinfo_(typeinfo), own_typeinfo_(false), type_(type) {
+ : stream_(stream),
+ typeinfo_(typeinfo),
+ own_typeinfo_(false),
+ type_(type),
+ use_lower_camel_for_enums_(false) {
GOOGLE_LOG_IF(DFATAL, stream == NULL) << "Input stream is NULL.";
}
@@ -238,9 +266,21 @@ StatusOr<uint32> ProtoStreamObjectSource::RenderMap(
map_key = ReadFieldValueAsString(*field);
} else if (field->number() == 2) {
if (map_key.empty()) {
- return Status(util::error::INTERNAL, "Map key must be non-empty");
+ // An absent map key is treated as the default.
+ const google::protobuf::Field* key_field =
+ FindFieldByNumber(*field_type, 1);
+ if (key_field == NULL) {
+ // The Type info for this map entry is incorrect. It should always
+ // have a field named "key" and with field number 1.
+ return Status(util::error::INTERNAL, "Invalid map entry.");
+ }
+ ASSIGN_OR_RETURN(map_key, MapKeyDefaultValueAsString(*key_field));
}
RETURN_IF_ERROR(RenderField(field, map_key, ow));
+ } else {
+ // The Type info for this map entry is incorrect. It should contain
+ // exactly two fields with field number 1 and 2.
+ return Status(util::error::INTERNAL, "Invalid map entry.");
}
}
stream_->PopLimit(old_limit);
@@ -266,7 +306,7 @@ Status ProtoStreamObjectSource::RenderTimestamp(
pair<int64, int32> p = os->ReadSecondsAndNanos(type);
int64 seconds = p.first;
int32 nanos = p.second;
- if (seconds > kMaxSeconds || seconds < kMinSeconds) {
+ if (seconds > kTimestampMaxSeconds || seconds < kTimestampMinSeconds) {
return Status(
util::error::INTERNAL,
StrCat("Timestamp seconds exceeds limit for field: ", field_name));
@@ -290,7 +330,7 @@ Status ProtoStreamObjectSource::RenderDuration(
pair<int64, int32> p = os->ReadSecondsAndNanos(type);
int64 seconds = p.first;
int32 nanos = p.second;
- if (seconds > kMaxSeconds || seconds < kMinSeconds) {
+ if (seconds > kDurationMaxSeconds || seconds < kDurationMinSeconds) {
return Status(
util::error::INTERNAL,
StrCat("Duration seconds exceeds limit for field: ", field_name));
@@ -807,7 +847,10 @@ Status ProtoStreamObjectSource::RenderNonMessageField(
const google::protobuf::EnumValue* enum_value =
FindEnumValueByNumber(*en, buffer32);
if (enum_value != NULL) {
- ow->RenderString(field_name, enum_value->name());
+ if (use_lower_camel_for_enums_)
+ ow->RenderString(field_name, ToCamelCase(enum_value->name()));
+ else
+ ow->RenderString(field_name, enum_value->name());
}
} else {
GOOGLE_LOG(INFO) << "Unknown enum skipped: " << field->type_url();
diff --git a/src/google/protobuf/util/internal/protostream_objectsource.h b/src/google/protobuf/util/internal/protostream_objectsource.h
index 78defa1d..17e03b73 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource.h
+++ b/src/google/protobuf/util/internal/protostream_objectsource.h
@@ -82,6 +82,34 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
virtual util::Status NamedWriteTo(StringPiece name, ObjectWriter* ow) const;
+ // Sets whether or not to use lowerCamelCase casing for enum values. If set to
+ // false, enum values are output without any case conversions.
+ //
+ // For example, if we have an enum:
+ // enum Type {
+ // ACTION_AND_ADVENTURE = 1;
+ // }
+ // Type type = 20;
+ //
+ // And this option is set to true. Then the rendered "type" field will have
+ // the string "actionAndAdventure".
+ // {
+ // ...
+ // "type": "actionAndAdventure",
+ // ...
+ // }
+ //
+ // If set to false, the rendered "type" field will have the string
+ // "ACTION_AND_ADVENTURE".
+ // {
+ // ...
+ // "type": "ACTION_AND_ADVENTURE",
+ // ...
+ // }
+ void set_use_lower_camel_for_enums(bool value) {
+ use_lower_camel_for_enums_ = value;
+ }
+
protected:
// Writes a proto2 Message to the ObjectWriter. When the given end_tag is
// found this method will complete, allowing it to be used for parsing both
@@ -237,6 +265,9 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectSource : public ObjectSource {
const google::protobuf::Type& type_;
+ // Whether to render enums using lowerCamelCase. Defaults to false.
+ bool use_lower_camel_for_enums_;
+
GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoStreamObjectSource);
};
diff --git a/src/google/protobuf/util/internal/protostream_objectsource_test.cc b/src/google/protobuf/util/internal/protostream_objectsource_test.cc
index 561f6763..1b32c803 100644
--- a/src/google/protobuf/util/internal/protostream_objectsource_test.cc
+++ b/src/google/protobuf/util/internal/protostream_objectsource_test.cc
@@ -50,6 +50,7 @@
#include <google/protobuf/util/internal/testdata/anys.pb.h>
#include <google/protobuf/util/internal/testdata/maps.pb.h>
#include <google/protobuf/util/internal/testdata/struct.pb.h>
+#include <google/protobuf/util/internal/testdata/timestamp_duration.pb.h>
#include <gtest/gtest.h>
@@ -75,6 +76,8 @@ using google::protobuf::testing::PackedPrimitive;
using google::protobuf::testing::Primitive;
using google::protobuf::testing::more_author;
using google::protobuf::testing::maps::MapOut;
+using google::protobuf::testing::maps::MapOutWireFormat;
+using google::protobuf::testing::timestampduration::TimestampDuration;
using google::protobuf::testing::anys::AnyOut;
using google::protobuf::testing::anys::AnyM;
using google::protobuf::testing::FieldMaskTest;
@@ -92,7 +95,11 @@ string GetTypeUrl(const Descriptor* descriptor) {
class ProtostreamObjectSourceTest
: public ::testing::TestWithParam<testing::TypeInfoSource> {
protected:
- ProtostreamObjectSourceTest() : helper_(GetParam()), mock_(), ow_(&mock_) {
+ ProtostreamObjectSourceTest()
+ : helper_(GetParam()),
+ mock_(),
+ ow_(&mock_),
+ use_lower_camel_for_enums_(false) {
helper_.ResetTypeInfo(Book::descriptor());
}
@@ -112,6 +119,7 @@ class ProtostreamObjectSourceTest
google::protobuf::scoped_ptr<ProtoStreamObjectSource> os(
helper_.NewProtoSource(&in_stream, GetTypeUrl(descriptor)));
+ if (use_lower_camel_for_enums_) os->set_use_lower_camel_for_enums(true);
return os->WriteTo(&mock_);
}
@@ -256,10 +264,13 @@ class ProtostreamObjectSourceTest
return primitive;
}
+ void UseLowerCamelForEnums() { use_lower_camel_for_enums_ = true; }
+
testing::TypeInfoTestHelper helper_;
::testing::NiceMock<MockObjectWriter> mock_;
ExpectingObjectWriter ow_;
+ bool use_lower_camel_for_enums_;
};
INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest,
@@ -461,6 +472,25 @@ TEST_P(ProtostreamObjectSourceTest,
DoTest(book, Book::descriptor());
}
+TEST_P(ProtostreamObjectSourceTest, LowerCamelEnumOutputTest) {
+ Book book;
+ book.set_type(Book::ACTION_AND_ADVENTURE);
+
+ UseLowerCamelForEnums();
+
+ ow_.StartObject("")->RenderString("type", "actionAndAdventure")->EndObject();
+ DoTest(book, Book::descriptor());
+}
+
+TEST_P(ProtostreamObjectSourceTest, EnumCaseIsUnchangedByDefault) {
+ Book book;
+ book.set_type(Book::ACTION_AND_ADVENTURE);
+ ow_.StartObject("")
+ ->RenderString("type", "ACTION_AND_ADVENTURE")
+ ->EndObject();
+ DoTest(book, Book::descriptor());
+}
+
class ProtostreamObjectSourceMapsTest : public ProtostreamObjectSourceTest {
protected:
ProtostreamObjectSourceMapsTest() {
@@ -541,6 +571,67 @@ TEST_P(ProtostreamObjectSourceMapsTest, MapsTest) {
DoTest(out, MapOut::descriptor());
}
+TEST_P(ProtostreamObjectSourceMapsTest, MissingKeysTest) {
+ // MapOutWireFormat has the same wire representation with MapOut but uses
+ // repeated message fields to represent map fields so we can intentionally
+ // leave out the key field or the value field of a map entry.
+ MapOutWireFormat out;
+ // Create some map entries without keys. They will be rendered with the
+ // default values ("" for strings, "0" for integers, etc.).
+ // {
+ // "map1": {
+ // "": {
+ // "foo": "foovalue"
+ // }
+ // },
+ // "map2": {
+ // "": {
+ // "map1": {
+ // "nested_key1": {
+ // "foo": "nested_foo"
+ // }
+ // }
+ // }
+ // },
+ // "map3": {
+ // "0": "one one one"
+ // },
+ // "map4": {
+ // "false": "bool"
+ // }
+ // }
+ out.add_map1()->mutable_value()->set_foo("foovalue");
+ MapOut* nested = out.add_map2()->mutable_value();
+ (*nested->mutable_map1())["nested_key1"].set_foo("nested_foo");
+ out.add_map3()->set_value("one one one");
+ out.add_map4()->set_value("bool");
+
+ ow_.StartObject("")
+ ->StartObject("map1")
+ ->StartObject("")
+ ->RenderString("foo", "foovalue")
+ ->EndObject()
+ ->EndObject()
+ ->StartObject("map2")
+ ->StartObject("")
+ ->StartObject("map1")
+ ->StartObject("nested_key1")
+ ->RenderString("foo", "nested_foo")
+ ->EndObject()
+ ->EndObject()
+ ->EndObject()
+ ->EndObject()
+ ->StartObject("map3")
+ ->RenderString("0", "one one one")
+ ->EndObject()
+ ->StartObject("map4")
+ ->RenderString("false", "bool")
+ ->EndObject()
+ ->EndObject();
+
+ DoTest(out, MapOut::descriptor());
+}
+
class ProtostreamObjectSourceAnysTest : public ProtostreamObjectSourceTest {
protected:
ProtostreamObjectSourceAnysTest() {
@@ -824,6 +915,63 @@ TEST_P(ProtostreamObjectSourceFieldMaskTest, FieldMaskRenderSuccess) {
DoTest(out, FieldMaskTest::descriptor());
}
+class ProtostreamObjectSourceTimestampTest
+ : public ProtostreamObjectSourceTest {
+ protected:
+ ProtostreamObjectSourceTimestampTest() {
+ helper_.ResetTypeInfo(TimestampDuration::descriptor());
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest,
+ ProtostreamObjectSourceTimestampTest,
+ ::testing::Values(
+ testing::USE_TYPE_RESOLVER));
+
+TEST_P(ProtostreamObjectSourceTimestampTest, InvalidTimestampBelowMinTest) {
+ TimestampDuration out;
+ google::protobuf::Timestamp* ts = out.mutable_ts();
+ // Min allowed seconds - 1
+ ts->set_seconds(kTimestampMinSeconds - 1);
+ ow_.StartObject("");
+
+ Status status = ExecuteTest(out, TimestampDuration::descriptor());
+ EXPECT_EQ(util::error::INTERNAL, status.error_code());
+}
+
+TEST_P(ProtostreamObjectSourceTimestampTest, InvalidTimestampAboveMaxTest) {
+ TimestampDuration out;
+ google::protobuf::Timestamp* ts = out.mutable_ts();
+ // Max allowed seconds + 1
+ ts->set_seconds(kTimestampMaxSeconds + 1);
+ ow_.StartObject("");
+
+ Status status = ExecuteTest(out, TimestampDuration::descriptor());
+ EXPECT_EQ(util::error::INTERNAL, status.error_code());
+}
+
+TEST_P(ProtostreamObjectSourceTimestampTest, InvalidDurationBelowMinTest) {
+ TimestampDuration out;
+ google::protobuf::Duration* dur = out.mutable_dur();
+ // Min allowed seconds - 1
+ dur->set_seconds(kDurationMinSeconds - 1);
+ ow_.StartObject("");
+
+ Status status = ExecuteTest(out, TimestampDuration::descriptor());
+ EXPECT_EQ(util::error::INTERNAL, status.error_code());
+}
+
+TEST_P(ProtostreamObjectSourceTimestampTest, InvalidDurationAboveMaxTest) {
+ TimestampDuration out;
+ google::protobuf::Duration* dur = out.mutable_dur();
+ // Min allowed seconds + 1
+ dur->set_seconds(kDurationMaxSeconds + 1);
+ ow_.StartObject("");
+
+ Status status = ExecuteTest(out, TimestampDuration::descriptor());
+ EXPECT_EQ(util::error::INTERNAL, status.error_code());
+}
+
} // namespace converter
} // namespace util
} // namespace protobuf
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.cc b/src/google/protobuf/util/internal/protostream_objectwriter.cc
index 786bf0be..94ddb428 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter.cc
+++ b/src/google/protobuf/util/internal/protostream_objectwriter.cc
@@ -58,17 +58,20 @@ using util::StatusOr;
ProtoStreamObjectWriter::ProtoStreamObjectWriter(
TypeResolver* type_resolver, const google::protobuf::Type& type,
- strings::ByteSink* output, ErrorListener* listener)
+ strings::ByteSink* output, ErrorListener* listener,
+ const ProtoStreamObjectWriter::Options& options)
: ProtoWriter(type_resolver, type, output, listener),
master_type_(type),
- current_(NULL) {}
+ current_(NULL),
+ options_(options) {}
ProtoStreamObjectWriter::ProtoStreamObjectWriter(
const TypeInfo* typeinfo, const google::protobuf::Type& type,
strings::ByteSink* output, ErrorListener* listener)
: ProtoWriter(typeinfo, type, output, listener),
master_type_(type),
- current_(NULL) {}
+ current_(NULL),
+ options_(ProtoStreamObjectWriter::Options::Defaults()) {}
ProtoStreamObjectWriter::~ProtoStreamObjectWriter() {
if (current_ == NULL) return;
@@ -439,7 +442,8 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartObject(
// name):
// { "key": "<name>", "value": {
Push("", Item::MESSAGE, false, false);
- ProtoWriter::RenderDataPiece("key", DataPiece(name));
+ ProtoWriter::RenderDataPiece("key",
+ DataPiece(name, use_strict_base64_decoding()));
Push("value", Item::MESSAGE, true, false);
// Make sure we are valid so far after starting map fields.
@@ -604,7 +608,8 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::StartList(StringPiece name) {
// Render
// { "key": "<name>", "value": {
Push("", Item::MESSAGE, false, false);
- ProtoWriter::RenderDataPiece("key", DataPiece(name));
+ ProtoWriter::RenderDataPiece("key",
+ DataPiece(name, use_strict_base64_decoding()));
Push("value", Item::MESSAGE, true, false);
// Make sure we are valid after pushing all above items.
@@ -758,8 +763,36 @@ Status ProtoStreamObjectWriter::RenderStructValue(ProtoStreamObjectWriter* ow,
string struct_field_name;
switch (data.type()) {
// Our JSON parser parses numbers as either int64, uint64, or double.
- case DataPiece::TYPE_INT64:
- case DataPiece::TYPE_UINT64:
+ case DataPiece::TYPE_INT64: {
+ // If the option to treat integers as strings is set, then render them as
+ // strings. Otherwise, fallback to rendering them as double.
+ if (ow->options_.struct_integers_as_strings) {
+ StatusOr<int64> int_value = data.ToInt64();
+ if (int_value.ok()) {
+ ow->ProtoWriter::RenderDataPiece(
+ "string_value",
+ DataPiece(SimpleItoa(int_value.ValueOrDie()), true));
+ return Status::OK;
+ }
+ }
+ struct_field_name = "number_value";
+ break;
+ }
+ case DataPiece::TYPE_UINT64: {
+ // If the option to treat integers as strings is set, then render them as
+ // strings. Otherwise, fallback to rendering them as double.
+ if (ow->options_.struct_integers_as_strings) {
+ StatusOr<uint64> int_value = data.ToUint64();
+ if (int_value.ok()) {
+ ow->ProtoWriter::RenderDataPiece(
+ "string_value",
+ DataPiece(SimpleItoa(int_value.ValueOrDie()), true));
+ return Status::OK;
+ }
+ }
+ struct_field_name = "number_value";
+ break;
+ }
case DataPiece::TYPE_DOUBLE: {
struct_field_name = "number_value";
break;
@@ -812,7 +845,7 @@ Status ProtoStreamObjectWriter::RenderTimestamp(ProtoStreamObjectWriter* ow,
static inline util::Status RenderOneFieldPath(ProtoStreamObjectWriter* ow,
StringPiece path) {
ow->ProtoWriter::RenderDataPiece(
- "paths", DataPiece(ConvertFieldMaskPath(path, &ToSnakeCase)));
+ "paths", DataPiece(ConvertFieldMaskPath(path, &ToSnakeCase), true));
return Status::OK;
}
@@ -871,7 +904,7 @@ Status ProtoStreamObjectWriter::RenderDuration(ProtoStreamObjectWriter* ow,
nanos = sign * nanos;
int64 seconds = sign * unsigned_seconds;
- if (seconds > kMaxSeconds || seconds < kMinSeconds ||
+ if (seconds > kDurationMaxSeconds || seconds < kDurationMinSeconds ||
nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) {
return Status(INVALID_ARGUMENT, "Duration value exceeds limits");
}
@@ -925,7 +958,8 @@ ProtoStreamObjectWriter* ProtoStreamObjectWriter::RenderDataPiece(
// Render an item in repeated map list.
// { "key": "<name>", "value":
Push("", Item::MESSAGE, false, false);
- ProtoWriter::RenderDataPiece("key", DataPiece(name));
+ ProtoWriter::RenderDataPiece("key",
+ DataPiece(name, use_strict_base64_decoding()));
field = Lookup("value");
if (field == NULL) {
GOOGLE_LOG(DFATAL) << "Map does not have a value field.";
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter.h b/src/google/protobuf/util/internal/protostream_objectwriter.h
index 08ac6e33..96ea3f2b 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter.h
+++ b/src/google/protobuf/util/internal/protostream_objectwriter.h
@@ -74,10 +74,30 @@ class ObjectLocationTracker;
// It also supports streaming.
class LIBPROTOBUF_EXPORT ProtoStreamObjectWriter : public ProtoWriter {
public:
+ // Options that control ProtoStreamObjectWriter class's behavior.
+ struct Options {
+ // Treats integer inputs in google.protobuf.Struct as strings. Normally,
+ // integer values are returned in double field "number_value" of
+ // google.protobuf.Struct. However, this can cause precision loss for
+ // int64/uint64 inputs. This option is provided for cases that want to
+ // preserve integer precision.
+ bool struct_integers_as_strings;
+
+ Options() : struct_integers_as_strings(false) {}
+
+ // Default instance of Options with all options set to defaults.
+ static const Options& Defaults() {
+ static Options defaults;
+ return defaults;
+ }
+ };
+
// Constructor. Does not take ownership of any parameter passed in.
ProtoStreamObjectWriter(TypeResolver* type_resolver,
const google::protobuf::Type& type,
- strings::ByteSink* output, ErrorListener* listener);
+ strings::ByteSink* output, ErrorListener* listener,
+ const ProtoStreamObjectWriter::Options& options =
+ ProtoStreamObjectWriter::Options::Defaults());
virtual ~ProtoStreamObjectWriter();
// ObjectWriter methods.
@@ -301,6 +321,9 @@ class LIBPROTOBUF_EXPORT ProtoStreamObjectWriter : public ProtoWriter {
// The current element, variable for internal state processing.
google::protobuf::scoped_ptr<Item> current_;
+ // Reference to the options that control this class's behavior.
+ const ProtoStreamObjectWriter::Options options_;
+
GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(ProtoStreamObjectWriter);
};
diff --git a/src/google/protobuf/util/internal/protostream_objectwriter_test.cc b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
index 5f9ffb95..41eaebc0 100644
--- a/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
+++ b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
@@ -90,6 +90,12 @@ string GetTypeUrl(const Descriptor* descriptor) {
}
} // namespace
+#if __cplusplus >= 201103L
+ using std::get;
+#else
+ using std::tr1::get;
+#endif
+
class BaseProtoStreamObjectWriterTest
: public ::testing::TestWithParam<testing::TypeInfoSource> {
protected:
@@ -122,7 +128,13 @@ class BaseProtoStreamObjectWriterTest
GOOGLE_CHECK(!descriptors.empty()) << "Must have at least one descriptor!";
helper_.ResetTypeInfo(descriptors);
ow_.reset(helper_.NewProtoWriter(GetTypeUrl(descriptors[0]), output_.get(),
- &listener_));
+ &listener_, options_));
+ }
+
+ void ResetTypeInfo(const Descriptor* descriptor) {
+ vector<const Descriptor*> descriptors;
+ descriptors.push_back(descriptor);
+ ResetTypeInfo(descriptors);
}
virtual ~BaseProtoStreamObjectWriterTest() {}
@@ -155,16 +167,12 @@ class BaseProtoStreamObjectWriterTest
MockErrorListener listener_;
google::protobuf::scoped_ptr<GrowingArrayByteSink> output_;
google::protobuf::scoped_ptr<ProtoStreamObjectWriter> ow_;
+ ProtoStreamObjectWriter::Options options_;
};
MATCHER_P(HasObjectLocation, expected,
"Verifies the expected object location") {
- string actual;
-#if __cplusplus >= 201103L
- actual = std::get<0>(arg).ToString();
-#else
- actual = std::tr1::get<0>(arg).ToString();
-#endif
+ string actual = get<0>(arg).ToString();
if (actual.compare(expected) == 0) return true;
*result_listener << "actual location is: " << actual;
return false;
@@ -289,8 +297,7 @@ TEST_P(ProtoStreamObjectWriterTest, PrimitiveFromStringConversion) {
full.add_rep_double(-8.05L);
full.add_rep_bool(false);
- ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()),
- output_.get(), &listener_));
+ ResetTypeInfo(Primitive::descriptor());
ow_->StartObject("")
->RenderString("fix32", "101")
@@ -363,8 +370,7 @@ TEST_P(ProtoStreamObjectWriterTest, InfinityInputTest) {
full.set_float_(std::numeric_limits<float>::infinity());
full.set_str("-Infinity");
- ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()),
- output_.get(), &listener_));
+ ResetTypeInfo(Primitive::descriptor());
EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"),
StringPiece("\"Infinity\"")))
@@ -397,8 +403,7 @@ TEST_P(ProtoStreamObjectWriterTest, NaNInputTest) {
full.set_float_(std::numeric_limits<float>::quiet_NaN());
full.set_str("NaN");
- ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()),
- output_.get(), &listener_));
+ ResetTypeInfo(Primitive::descriptor());
EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"),
StringPiece("\"NaN\"")))
@@ -887,6 +892,124 @@ TEST_P(ProtoStreamObjectWriterTimestampDurationTest, ParseTimestamp) {
CheckOutput(timestamp);
}
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ ParseTimestampYearNotZeroPadded) {
+ TimestampDuration timestamp;
+ google::protobuf::Timestamp* ts = timestamp.mutable_ts();
+ ts->set_seconds(-61665654145);
+ ts->set_nanos(33155000);
+
+ ow_->StartObject("")
+ ->RenderString("ts", "15-11-23T03:37:35.033155Z")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ ParseTimestampYearZeroPadded) {
+ TimestampDuration timestamp;
+ google::protobuf::Timestamp* ts = timestamp.mutable_ts();
+ ts->set_seconds(-61665654145);
+ ts->set_nanos(33155000);
+
+ ow_->StartObject("")
+ ->RenderString("ts", "0015-11-23T03:37:35.033155Z")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ ParseTimestampWithPositiveOffset) {
+ TimestampDuration timestamp;
+ google::protobuf::Timestamp* ts = timestamp.mutable_ts();
+ ts->set_seconds(1448249855);
+ ts->set_nanos(33155000);
+
+ ow_->StartObject("")
+ ->RenderString("ts", "2015-11-23T11:47:35.033155+08:10")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ ParseTimestampWithNegativeOffset) {
+ TimestampDuration timestamp;
+ google::protobuf::Timestamp* ts = timestamp.mutable_ts();
+ ts->set_seconds(1448249855);
+ ts->set_nanos(33155000);
+
+ ow_->StartObject("")
+ ->RenderString("ts", "2015-11-22T19:47:35.033155-07:50")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ TimestampWithInvalidOffset1) {
+ TimestampDuration timestamp;
+
+ EXPECT_CALL(
+ listener_,
+ InvalidValue(_,
+ StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
+ StringPiece("Field 'ts', Invalid time format: "
+ "2016-03-07T15:14:23+")));
+
+ ow_->StartObject("")->RenderString("ts", "2016-03-07T15:14:23+")->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ TimestampWithInvalidOffset2) {
+ TimestampDuration timestamp;
+
+ EXPECT_CALL(
+ listener_,
+ InvalidValue(_,
+ StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
+ StringPiece("Field 'ts', Invalid time format: "
+ "2016-03-07T15:14:23+08-10")));
+
+ ow_->StartObject("")
+ ->RenderString("ts", "2016-03-07T15:14:23+08-10")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ TimestampWithInvalidOffset3) {
+ TimestampDuration timestamp;
+
+ EXPECT_CALL(
+ listener_,
+ InvalidValue(_,
+ StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
+ StringPiece("Field 'ts', Invalid time format: "
+ "2016-03-07T15:14:23+24:10")));
+
+ ow_->StartObject("")
+ ->RenderString("ts", "2016-03-07T15:14:23+24:10")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
+ TimestampWithInvalidOffset4) {
+ TimestampDuration timestamp;
+
+ EXPECT_CALL(
+ listener_,
+ InvalidValue(_,
+ StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
+ StringPiece("Field 'ts', Invalid time format: "
+ "2016-03-07T15:14:23+04:60")));
+
+ ow_->StartObject("")
+ ->RenderString("ts", "2016-03-07T15:14:23+04:60")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError1) {
TimestampDuration timestamp;
@@ -937,10 +1060,10 @@ TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError4) {
InvalidValue(_,
StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
StringPiece("Field 'ts', Invalid time format: "
- "-8032-10-18T00:00:00.000Z")));
+ "-8031-10-18T00:00:00.000Z")));
ow_->StartObject("")
- ->RenderString("ts", "-8032-10-18T00:00:00.000Z")
+ ->RenderString("ts", "-8031-10-18T00:00:00.000Z")
->EndObject();
CheckOutput(timestamp);
}
@@ -996,6 +1119,22 @@ TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError7) {
CheckOutput(timestamp);
}
+TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError8) {
+ TimestampDuration timestamp;
+
+ EXPECT_CALL(
+ listener_,
+ InvalidValue(_,
+ StringPiece("type.googleapis.com/google.protobuf.Timestamp"),
+ StringPiece("Field 'ts', Invalid time format: "
+ "0-12-31T23:59:59.000Z")));
+
+ ow_->StartObject("")
+ ->RenderString("ts", "0-12-31T23:59:59.000Z")
+ ->EndObject();
+ CheckOutput(timestamp);
+}
+
TEST_P(ProtoStreamObjectWriterTimestampDurationTest, ParseDuration) {
TimestampDuration duration;
google::protobuf::Duration* dur = duration.mutable_dur();
@@ -1105,7 +1244,10 @@ TEST_P(ProtoStreamObjectWriterTimestampDurationTest,
class ProtoStreamObjectWriterStructTest
: public BaseProtoStreamObjectWriterTest {
protected:
- ProtoStreamObjectWriterStructTest() {
+ ProtoStreamObjectWriterStructTest() { ResetProtoWriter(); }
+
+ // Resets ProtoWriter with current set of options and other state.
+ void ResetProtoWriter() {
vector<const Descriptor*> descriptors;
descriptors.push_back(StructType::descriptor());
descriptors.push_back(google::protobuf::Struct::descriptor());
@@ -1201,6 +1343,28 @@ TEST_P(ProtoStreamObjectWriterStructTest, RepeatedStructMapObjectKeyTest) {
->EndObject();
}
+TEST_P(ProtoStreamObjectWriterStructTest, OptionStructIntAsStringsTest) {
+ StructType struct_type;
+ google::protobuf::Struct* s = struct_type.mutable_object();
+ s->mutable_fields()->operator[]("k1").set_number_value(123);
+ s->mutable_fields()->operator[]("k2").set_bool_value(true);
+ s->mutable_fields()->operator[]("k3").set_string_value("-222222222");
+ s->mutable_fields()->operator[]("k4").set_string_value("33333333");
+
+ options_.struct_integers_as_strings = true;
+ ResetProtoWriter();
+
+ ow_->StartObject("")
+ ->StartObject("object")
+ ->RenderDouble("k1", 123)
+ ->RenderBool("k2", true)
+ ->RenderInt64("k3", -222222222)
+ ->RenderUint64("k4", 33333333)
+ ->EndObject()
+ ->EndObject();
+ CheckOutput(struct_type);
+}
+
class ProtoStreamObjectWriterMapTest : public BaseProtoStreamObjectWriterTest {
protected:
ProtoStreamObjectWriterMapTest()
diff --git a/src/google/protobuf/util/internal/testdata/books.proto b/src/google/protobuf/util/internal/testdata/books.proto
index 82b81760..101a2bf0 100644
--- a/src/google/protobuf/util/internal/testdata/books.proto
+++ b/src/google/protobuf/util/internal/testdata/books.proto
@@ -56,6 +56,13 @@ message Book {
optional Publisher publisher = 9;
repeated Label labels = 10;
+ enum Type {
+ FICTION = 1;
+ KIDS = 2;
+ ACTION_AND_ADVENTURE = 3;
+ }
+ optional Type type = 11;
+
extensions 200 to 499;
}
diff --git a/src/google/protobuf/util/internal/type_info_test_helper.cc b/src/google/protobuf/util/internal/type_info_test_helper.cc
index 1b9c5154..49e18ed0 100644
--- a/src/google/protobuf/util/internal/type_info_test_helper.cc
+++ b/src/google/protobuf/util/internal/type_info_test_helper.cc
@@ -102,13 +102,13 @@ ProtoStreamObjectSource* TypeInfoTestHelper::NewProtoSource(
}
ProtoStreamObjectWriter* TypeInfoTestHelper::NewProtoWriter(
- const string& type_url, strings::ByteSink* output,
- ErrorListener* listener) {
+ const string& type_url, strings::ByteSink* output, ErrorListener* listener,
+ const ProtoStreamObjectWriter::Options& options) {
const google::protobuf::Type* type = typeinfo_->GetTypeByTypeUrl(type_url);
switch (type_) {
case USE_TYPE_RESOLVER: {
return new ProtoStreamObjectWriter(type_resolver_.get(), *type, output,
- listener);
+ listener, options);
}
}
GOOGLE_LOG(FATAL) << "Can not reach here.";
diff --git a/src/google/protobuf/util/internal/type_info_test_helper.h b/src/google/protobuf/util/internal/type_info_test_helper.h
index 6916a73b..1a279849 100644
--- a/src/google/protobuf/util/internal/type_info_test_helper.h
+++ b/src/google/protobuf/util/internal/type_info_test_helper.h
@@ -39,8 +39,8 @@
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/descriptor.h>
-#include <google/protobuf/util/internal/type_info.h>
#include <google/protobuf/util/internal/default_value_objectwriter.h>
+#include <google/protobuf/util/internal/type_info.h>
#include <google/protobuf/util/internal/protostream_objectsource.h>
#include <google/protobuf/util/internal/protostream_objectwriter.h>
#include <google/protobuf/util/type_resolver.h>
@@ -77,9 +77,9 @@ class TypeInfoTestHelper {
ProtoStreamObjectSource* NewProtoSource(io::CodedInputStream* coded_input,
const string& type_url);
- ProtoStreamObjectWriter* NewProtoWriter(const string& type_url,
- strings::ByteSink* output,
- ErrorListener* listener);
+ ProtoStreamObjectWriter* NewProtoWriter(
+ const string& type_url, strings::ByteSink* output,
+ ErrorListener* listener, const ProtoStreamObjectWriter::Options& options);
DefaultValueObjectWriter* NewDefaultValueWriter(const string& type_url,
ObjectWriter* writer);
diff --git a/src/google/protobuf/util/internal/utility.cc b/src/google/protobuf/util/internal/utility.cc
index 1ddf2487..ee7a51fc 100644
--- a/src/google/protobuf/util/internal/utility.cc
+++ b/src/google/protobuf/util/internal/utility.cc
@@ -222,6 +222,7 @@ string ToCamelCase(const StringPiece input) {
if (!result.empty() && is_cap &&
(!was_cap || (i + 1 < input.size() && ascii_islower(input[i + 1])))) {
first_word = false;
+ result.push_back(input[i]);
} else {
result.push_back(ascii_tolower(input[i]));
continue;
@@ -231,9 +232,13 @@ string ToCamelCase(const StringPiece input) {
if (ascii_islower(input[i])) {
result.push_back(ascii_toupper(input[i]));
continue;
+ } else {
+ result.push_back(input[i]);
+ continue;
}
+ } else {
+ result.push_back(ascii_tolower(input[i]));
}
- result.push_back(input[i]);
}
return result;
}