Skip to content

Commit

Permalink
Message.decode: Add max_recursion_depth option
Browse files Browse the repository at this point in the history
This allows increasing the recursing depth from the default of 64, by
setting the "max_recursion_depth" to the desired integer value. This is
useful to parse complex nested protobuf messages that otherwise error out
with "Error occurred during parsing".

Fixes #1493
  • Loading branch information
lfittl committed Nov 13, 2021
1 parent 0ca4c1a commit c1d3b1b
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 7 deletions.
32 changes: 27 additions & 5 deletions ruby/ext/google/protobuf_c/message.c
Expand Up @@ -924,17 +924,39 @@ static VALUE Message_index_set(VALUE _self, VALUE field_name, VALUE value) {
* format) under the interpretration given by this message class's definition
* and returns a message object with the corresponding field values.
*/
static VALUE Message_decode(VALUE klass, VALUE data) {
static VALUE Message_decode(int argc, VALUE* argv, VALUE klass) {
VALUE data = argv[0];
int options = 0;

if (argc < 1 || argc > 2) {
rb_raise(rb_eArgError, "Expected 1 or 2 arguments.");
}

if (argc == 2) {
VALUE hash_args = argv[1];
if (TYPE(hash_args) != T_HASH) {
rb_raise(rb_eArgError, "Expected hash arguments.");
}

VALUE depth = rb_hash_lookup(hash_args, ID2SYM(rb_intern("max_recursion_depth")));

if (depth != Qnil && TYPE(depth) == T_FIXNUM) {
options = FIX2INT(depth) << 16;
}
}

if (TYPE(data) != T_STRING) {
rb_raise(rb_eArgError, "Expected string for binary protobuf data.");
}

VALUE msg_rb = initialize_rb_class_with_no_args(klass);
Message* msg = ruby_to_Message(msg_rb);

if (!upb_decode(RSTRING_PTR(data), RSTRING_LEN(data), (upb_msg*)msg->msg,
upb_msgdef_layout(msg->msgdef),
Arena_get(msg->arena))) {
if (!_upb_decode(RSTRING_PTR(data), RSTRING_LEN(data), (upb_msg*)msg->msg,
upb_msgdef_layout(msg->msgdef),
NULL,
options,
Arena_get(msg->arena))) {
rb_raise(cParseError, "Error occurred during parsing");
}

Expand Down Expand Up @@ -1149,7 +1171,7 @@ VALUE build_class_from_descriptor(VALUE descriptor) {
rb_define_method(klass, "to_s", Message_inspect, 0);
rb_define_method(klass, "[]", Message_index, 1);
rb_define_method(klass, "[]=", Message_index_set, 2);
rb_define_singleton_method(klass, "decode", Message_decode, 1);
rb_define_singleton_method(klass, "decode", Message_decode, -1);
rb_define_singleton_method(klass, "encode", Message_encode, 1);
rb_define_singleton_method(klass, "decode_json", Message_decode_json, -1);
rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1);
Expand Down
4 changes: 2 additions & 2 deletions ruby/lib/google/protobuf.rb
Expand Up @@ -67,8 +67,8 @@ def self.encode_json(msg, options = {})
msg.to_json(options)
end

def self.decode(klass, proto)
klass.decode(proto)
def self.decode(klass, proto, options = {})
klass.decode(proto, options)
end

def self.decode_json(klass, json, options = {})
Expand Down
25 changes: 25 additions & 0 deletions ruby/tests/encode_decode_test.rb
Expand Up @@ -101,4 +101,29 @@ def test_json_name
assert_match json, "{\"CustomJsonName\":42}"
end

def test_decode_depth_limit
msg = A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
)
)
)
)
)
)
msg_encoded = A::B::C::TestMessage.encode(msg)
msg_out = A::B::C::TestMessage.decode(msg_encoded)
assert_match msg.to_json, msg_out.to_json

assert_raise Google::Protobuf::ParseError do
A::B::C::TestMessage.decode(msg_encoded, { max_recursion_depth: 4 })
end

msg_out = A::B::C::TestMessage.decode(msg_encoded, { max_recursion_depth: 5 })
assert_match msg.to_json, msg_out.to_json
end

end

0 comments on commit c1d3b1b

Please sign in to comment.