@@ -50,17 +50,30 @@ defmodule Protobuf.JSON.Decode do
5050
5151 @ duration_seconds_range - 315_576_000_000 .. 315_576_000_000
5252
53- @ type opts ( ) :: % { ignore_unknown_fields: boolean ( ) }
53+ # Default recursion limit, matching upstream Protobuf
54+ # (ParseOptions::recursion_depth in C++, .recursion_limit in Java/Go).
55+ # Applies to the dynamically-typed wrappers (Value/ListValue/Struct) where
56+ # nesting is bounded only by the input, not the schema.
57+ @ default_recursion_limit 100
5458
55- @ spec from_json_data ( term ( ) , module ( ) , opts ( ) ) :: struct ( )
56- def from_json_data ( term , module , opts \\ % { ignore_unknown_fields: false } )
59+ @ spec from_json_data ( term ( ) , module ( ) , keyword ( ) ) :: struct ( )
60+ def from_json_data ( term , module , opts ) when is_list ( opts ) and is_atom ( module ) do
61+ state = % {
62+ ignore_unknown_fields: Keyword . get ( opts , :ignore_unknown_fields , false ) ,
63+ recursion_limit: Keyword . get ( opts , :recursion_limit , @ default_recursion_limit ) ,
64+ depth: 0
65+ }
66+
67+ internal_from_json_data ( term , module , state )
68+ end
5769
5870 # We start with all the Google built-in types that have specially-defined JSON decoding rules.
5971 # These rules are listed here: https://developers.google.com/protocol-buffers/docs/proto3#json
6072 # Note that we always have to keep the module names for the built-in types dynamic because
6173 # these built-in types **do not ship with our library**.
6274
63- def from_json_data ( string , Google.Protobuf.Duration = mod , _opts ) when is_binary ( string ) do
75+ defp internal_from_json_data ( string , Google.Protobuf.Duration = mod , _state )
76+ when is_binary ( string ) do
6477 # We need to check the sign from the raw string itself and can't rely on Integer.parse/1. This
6578 # is because if seconds is 0, then we couldn't determine whether it was "0" or "-0". For
6679 # example, "-0.5s".
@@ -81,63 +94,70 @@ defmodule Protobuf.JSON.Decode do
8194 end
8295 end
8396
84- def from_json_data ( string , Google.Protobuf.Timestamp = mod , _opts ) when is_binary ( string ) do
97+ defp internal_from_json_data ( string , Google.Protobuf.Timestamp = mod , _state )
98+ when is_binary ( string ) do
8599 case Protobuf.JSON.RFC3339 . decode ( string ) do
86100 { :ok , seconds , nanos } -> struct! ( mod , seconds: seconds , nanos: nanos )
87101 { :error , reason } -> throw ( { :bad_timestamp , string , reason } )
88102 end
89103 end
90104
91- def from_json_data ( map , Google.Protobuf.Empty = mod , _opts ) when map == % { } do
105+ defp internal_from_json_data ( map , Google.Protobuf.Empty = mod , _state ) when map == % { } do
92106 struct! ( mod )
93107 end
94108
95- def from_json_data ( int , Google.Protobuf.Int32Value = mod , _opts ) ,
109+ defp internal_from_json_data ( int , Google.Protobuf.Int32Value = mod , _state ) ,
96110 do: struct! ( mod , value: decode_scalar ( :int32 , :unknown_name , int ) )
97111
98- def from_json_data ( int , Google.Protobuf.UInt32Value = mod , _opts ) ,
112+ defp internal_from_json_data ( int , Google.Protobuf.UInt32Value = mod , _state ) ,
99113 do: struct! ( mod , value: decode_scalar ( :uint32 , :unknown_name , int ) )
100114
101- def from_json_data ( int , Google.Protobuf.UInt64Value = mod , _opts ) ,
115+ defp internal_from_json_data ( int , Google.Protobuf.UInt64Value = mod , _state ) ,
102116 do: struct! ( mod , value: decode_scalar ( :uint64 , :unknown_name , int ) )
103117
104- def from_json_data ( int , Google.Protobuf.Int64Value = mod , _opts ) ,
118+ defp internal_from_json_data ( int , Google.Protobuf.Int64Value = mod , _state ) ,
105119 do: struct! ( mod , value: decode_scalar ( :int64 , :unknown_name , int ) )
106120
107- def from_json_data ( number , mod , _opts )
108- when mod in [
109- Google.Protobuf.FloatValue ,
110- Google.Protobuf.DoubleValue
111- ] and ( is_float ( number ) or is_integer ( number ) ) do
121+ defp internal_from_json_data ( number , mod , _state )
122+ when mod in [
123+ Google.Protobuf.FloatValue ,
124+ Google.Protobuf.DoubleValue
125+ ] and ( is_float ( number ) or is_integer ( number ) ) do
112126 struct! ( mod , value: number * 1.0 )
113127 end
114128
115- def from_json_data ( bool , Google.Protobuf.BoolValue = mod , _opts ) when is_boolean ( bool ) do
129+ defp internal_from_json_data ( bool , Google.Protobuf.BoolValue = mod , _state )
130+ when is_boolean ( bool ) do
116131 struct! ( mod , value: decode_scalar ( :bool , :unknown_field , bool ) )
117132 end
118133
119- def from_json_data ( string , Google.Protobuf.StringValue = mod , _opts ) when is_binary ( string ) do
134+ defp internal_from_json_data ( string , Google.Protobuf.StringValue = mod , _state )
135+ when is_binary ( string ) do
120136 struct! ( mod , value: decode_scalar ( :string , :unknown_field , string ) )
121137 end
122138
123- def from_json_data ( bytes , Google.Protobuf.BytesValue = mod , _opts ) when is_binary ( bytes ) do
139+ defp internal_from_json_data ( bytes , Google.Protobuf.BytesValue = mod , _state )
140+ when is_binary ( bytes ) do
124141 struct! ( mod , value: decode_scalar ( :bytes , :unknown_field , bytes ) )
125142 end
126143
127- def from_json_data ( list , Google.Protobuf.ListValue = mod , opts ) when is_list ( list ) do
128- struct! ( mod , values: Enum . map ( list , & from_json_data ( & 1 , Google.Protobuf.Value , opts ) ) )
144+ defp internal_from_json_data ( list , Google.Protobuf.ListValue = mod , state ) when is_list ( list ) do
145+ state = increase_depth_and_maybe_throw ( state )
146+ struct! ( mod , values: Enum . map ( list , & internal_from_json_data ( & 1 , Google.Protobuf.Value , state ) ) )
129147 end
130148
131- def from_json_data ( struct , Google.Protobuf.Struct = mod , opts ) when is_map ( struct ) do
149+ defp internal_from_json_data ( struct , Google.Protobuf.Struct = mod , state ) when is_map ( struct ) do
150+ state = increase_depth_and_maybe_throw ( state )
151+
132152 fields =
133153 Map . new ( struct , fn { key , val } ->
134- { key , from_json_data ( val , Google.Protobuf.Value , opts ) }
154+ { key , internal_from_json_data ( val , Google.Protobuf.Value , state ) }
135155 end )
136156
137157 struct! ( mod , fields: fields )
138158 end
139159
140- def from_json_data ( term , Google.Protobuf.Value = mod , opts ) do
160+ defp internal_from_json_data ( term , Google.Protobuf.Value = mod , state ) do
141161 cond do
142162 is_nil ( term ) ->
143163 struct! ( mod , kind: { :null_value , :NULL_VALUE } )
@@ -155,17 +175,22 @@ defmodule Protobuf.JSON.Decode do
155175 struct! ( mod , kind: { :bool_value , term } )
156176
157177 is_list ( term ) ->
158- struct! ( mod , kind: { :list_value , from_json_data ( term , Google.Protobuf.ListValue , opts ) } )
178+ struct! ( mod ,
179+ kind: { :list_value , internal_from_json_data ( term , Google.Protobuf.ListValue , state ) }
180+ )
159181
160182 is_map ( term ) ->
161- struct! ( mod , kind: { :struct_value , from_json_data ( term , Google.Protobuf.Struct , opts ) } )
183+ struct! ( mod ,
184+ kind: { :struct_value , internal_from_json_data ( term , Google.Protobuf.Struct , state ) }
185+ )
162186
163187 true ->
164188 throw ( { :bad_message , term , mod } )
165189 end
166190 end
167191
168- def from_json_data ( data , Google.Protobuf.FieldMask = mod , _opts ) when is_binary ( data ) do
192+ defp internal_from_json_data ( data , Google.Protobuf.FieldMask = mod , _state )
193+ when is_binary ( data ) do
169194 paths = String . split ( data , "," )
170195
171196 cond do
@@ -175,7 +200,7 @@ defmodule Protobuf.JSON.Decode do
175200 end
176201 end
177202
178- def from_json_data ( % { "@type" => type_url } = data , Google.Protobuf.Any = mod , opts ) do
203+ defp internal_from_json_data ( % { "@type" => type_url } = data , Google.Protobuf.Any = mod , state ) do
179204 data = Map . delete ( data , "@type" )
180205 message_mod = Protobuf.Any . type_url_to_module ( type_url )
181206
@@ -186,35 +211,41 @@ defmodule Protobuf.JSON.Decode do
186211 # See: https://developers.google.com/protocol-buffers/docs/proto3#json
187212 { :ok , value } ->
188213 value
189- |> from_json_data ( message_mod , opts )
214+ |> internal_from_json_data ( message_mod , state )
190215 |> message_mod . encode ( )
191216
192217 # When a message doesn't have a built-in JSON representation (like
193218 # google.protobuf.Timestamp), then it's encoded as a JSON object and then a @type field is
194219 # added with the type_url for that message.
195220 :error ->
196221 data
197- |> from_json_data ( message_mod , opts )
222+ |> internal_from_json_data ( message_mod , state )
198223 |> message_mod . encode ( )
199224 end
200225
201226 struct! ( mod , type_url: type_url , value: encoded )
202227 end
203228
204- def from_json_data ( data , module , opts ) when is_map ( data ) and is_atom ( module ) do
229+ defp internal_from_json_data ( data , module , state ) when is_map ( data ) and is_atom ( module ) do
205230 message_props = module . __message_props__ ( )
206- regular = decode_regular_fields ( data , message_props , opts )
207- oneofs = decode_oneof_fields ( data , message_props , opts )
231+ regular = decode_regular_fields ( data , message_props , state )
232+ oneofs = decode_oneof_fields ( data , message_props , state )
208233
209234 module
210235 |> struct ( regular )
211236 |> struct ( oneofs )
212237 |> transform_module ( module )
213238 end
214239
215- def from_json_data ( data , module , _opts ) when is_atom ( module ) ,
240+ defp internal_from_json_data ( data , module , _state ) when is_atom ( module ) ,
216241 do: throw ( { :bad_message , data , module } )
217242
243+ defp increase_depth_and_maybe_throw ( % { depth: depth , recursion_limit: limit } = opts ) do
244+ depth = depth + 1
245+ if depth > limit , do: throw ( { :recursion_limit_exceeded , limit } )
246+ % { opts | depth: depth }
247+ end
248+
218249 defp convert_field_mask_to_underscore ( mask ) do
219250 if mask =~ ~r/ ^[a-zA-Z0-9\. ]+$/ do
220251 String . split ( mask , "." )
@@ -353,8 +384,8 @@ defmodule Protobuf.JSON.Decode do
353384 end )
354385 end
355386
356- defp decode_singular ( % { type: module , embedded?: true } , value , opts ) do
357- from_json_data ( value , module , opts )
387+ defp decode_singular ( % { type: module , embedded?: true } , value , state ) do
388+ internal_from_json_data ( value , module , state )
358389 end
359390
360391 defp decode_scalar ( :string , name , value ) do
0 commit comments