@@ -633,7 +633,8 @@ def Parse(text,
633633 allow_unknown_extension = False ,
634634 allow_field_number = False ,
635635 descriptor_pool = None ,
636- allow_unknown_field = False ):
636+ allow_unknown_field = False ,
637+ max_recursion_depth = None ):
637638 """Parses a text representation of a protocol message into a message.
638639
639640 NOTE: for historical reasons this function does not clear the input
@@ -671,6 +672,9 @@ def Parse(text,
671672 allow_unknown_field: if True, skip over unknown field and keep
672673 parsing. Avoid to use this option if possible. It may hide some
673674 errors (e.g. spelling error on field name)
675+ max_recursion_depth: Optional maximum recursion depth of a text proto
676+ message to be deserialized. Text proto messages over this depth will
677+ fail to parse. ``None`` keeps the historical unbounded behavior.
674678
675679 Returns:
676680 Message: The same message passed as argument.
@@ -683,15 +687,17 @@ def Parse(text,
683687 allow_unknown_extension ,
684688 allow_field_number ,
685689 descriptor_pool = descriptor_pool ,
686- allow_unknown_field = allow_unknown_field )
690+ allow_unknown_field = allow_unknown_field ,
691+ max_recursion_depth = max_recursion_depth )
687692
688693
689694def Merge (text ,
690695 message ,
691696 allow_unknown_extension = False ,
692697 allow_field_number = False ,
693698 descriptor_pool = None ,
694- allow_unknown_field = False ):
699+ allow_unknown_field = False ,
700+ max_recursion_depth = None ):
695701 """Parses a text representation of a protocol message into a message.
696702
697703 Like Parse(), but allows repeated values for a non-repeated field, and uses
@@ -708,6 +714,9 @@ def Merge(text,
708714 allow_unknown_field: if True, skip over unknown field and keep
709715 parsing. Avoid to use this option if possible. It may hide some
710716 errors (e.g. spelling error on field name)
717+ max_recursion_depth: Optional maximum recursion depth of a text proto
718+ message to be deserialized. Text proto messages over this depth will
719+ fail to parse. ``None`` keeps the historical unbounded behavior.
711720
712721 Returns:
713722 Message: The same message passed as argument.
@@ -721,15 +730,17 @@ def Merge(text,
721730 allow_unknown_extension ,
722731 allow_field_number ,
723732 descriptor_pool = descriptor_pool ,
724- allow_unknown_field = allow_unknown_field )
733+ allow_unknown_field = allow_unknown_field ,
734+ max_recursion_depth = max_recursion_depth )
725735
726736
727737def ParseLines (lines ,
728738 message ,
729739 allow_unknown_extension = False ,
730740 allow_field_number = False ,
731741 descriptor_pool = None ,
732- allow_unknown_field = False ):
742+ allow_unknown_field = False ,
743+ max_recursion_depth = None ):
733744 """Parses a text representation of a protocol message into a message.
734745
735746 See Parse() for caveats.
@@ -744,6 +755,9 @@ def ParseLines(lines,
744755 allow_unknown_field: if True, skip over unknown field and keep
745756 parsing. Avoid to use this option if possible. It may hide some
746757 errors (e.g. spelling error on field name)
758+ max_recursion_depth: Optional maximum recursion depth of a text proto
759+ message to be deserialized. Text proto messages over this depth will
760+ fail to parse. ``None`` keeps the historical unbounded behavior.
747761
748762 Returns:
749763 The same message passed as argument.
@@ -754,7 +768,8 @@ def ParseLines(lines,
754768 parser = _Parser (allow_unknown_extension ,
755769 allow_field_number ,
756770 descriptor_pool = descriptor_pool ,
757- allow_unknown_field = allow_unknown_field )
771+ allow_unknown_field = allow_unknown_field ,
772+ max_recursion_depth = max_recursion_depth )
758773 return parser .ParseLines (lines , message )
759774
760775
@@ -763,7 +778,8 @@ def MergeLines(lines,
763778 allow_unknown_extension = False ,
764779 allow_field_number = False ,
765780 descriptor_pool = None ,
766- allow_unknown_field = False ):
781+ allow_unknown_field = False ,
782+ max_recursion_depth = None ):
767783 """Parses a text representation of a protocol message into a message.
768784
769785 See Merge() for more details.
@@ -778,6 +794,9 @@ def MergeLines(lines,
778794 allow_unknown_field: if True, skip over unknown field and keep
779795 parsing. Avoid to use this option if possible. It may hide some
780796 errors (e.g. spelling error on field name)
797+ max_recursion_depth: Optional maximum recursion depth of a text proto
798+ message to be deserialized. Text proto messages over this depth will
799+ fail to parse. ``None`` keeps the historical unbounded behavior.
781800
782801 Returns:
783802 The same message passed as argument.
@@ -788,7 +807,8 @@ def MergeLines(lines,
788807 parser = _Parser (allow_unknown_extension ,
789808 allow_field_number ,
790809 descriptor_pool = descriptor_pool ,
791- allow_unknown_field = allow_unknown_field )
810+ allow_unknown_field = allow_unknown_field ,
811+ max_recursion_depth = max_recursion_depth )
792812 return parser .MergeLines (lines , message )
793813
794814
@@ -799,11 +819,14 @@ def __init__(self,
799819 allow_unknown_extension = False ,
800820 allow_field_number = False ,
801821 descriptor_pool = None ,
802- allow_unknown_field = False ):
822+ allow_unknown_field = False ,
823+ max_recursion_depth = None ):
803824 self .allow_unknown_extension = allow_unknown_extension
804825 self .allow_field_number = allow_field_number
805826 self .descriptor_pool = descriptor_pool
806827 self .allow_unknown_field = allow_unknown_field
828+ self .max_recursion_depth = max_recursion_depth
829+ self .recursion_depth = 0
807830
808831 def ParseLines (self , lines , message ):
809832 """Parses a text representation of a protocol message into a message."""
@@ -837,8 +860,38 @@ def _ParseOrMerge(self, lines, message):
837860 raise ParseError from e
838861 if message :
839862 self .root_type = message .DESCRIPTOR .full_name
863+ self .recursion_depth += 1
864+ if (
865+ self .max_recursion_depth is not None
866+ and self .recursion_depth > self .max_recursion_depth
867+ ):
868+ raise ParseError (
869+ 'Message too deep. Max recursion depth is {0}' .format (
870+ self .max_recursion_depth
871+ )
872+ )
840873 while not tokenizer .AtEnd ():
841874 self ._MergeField (tokenizer , message )
875+ self .recursion_depth -= 1
876+
877+ def _MergeMessage (self , tokenizer , message , end_token ):
878+ self .recursion_depth += 1
879+ if (
880+ self .max_recursion_depth is not None
881+ and self .recursion_depth > self .max_recursion_depth
882+ ):
883+ raise ParseError (
884+ 'Message too deep. Max recursion depth is {0}' .format (
885+ self .max_recursion_depth
886+ )
887+ )
888+ while not tokenizer .TryConsume (end_token ):
889+ if tokenizer .AtEnd ():
890+ raise tokenizer .ParseErrorPreviousToken (
891+ 'Expected "%s".' % (end_token ,)
892+ )
893+ self ._MergeField (tokenizer , message )
894+ self .recursion_depth -= 1
842895
843896 def _MergeField (self , tokenizer , message ):
844897 """Merges a single protocol message field into a message.
@@ -873,11 +926,9 @@ def _MergeField(self, tokenizer, message):
873926 if expanded_any_sub_message is None :
874927 raise ParseError ('Type %s not found in descriptor pool' %
875928 packed_type_name )
876- while not tokenizer .TryConsume (expanded_any_end_token ):
877- if tokenizer .AtEnd ():
878- raise tokenizer .ParseErrorPreviousToken ('Expected "%s".' %
879- (expanded_any_end_token ,))
880- self ._MergeField (tokenizer , expanded_any_sub_message )
929+ self ._MergeMessage (
930+ tokenizer , expanded_any_sub_message , expanded_any_end_token
931+ )
881932 deterministic = False
882933
883934 message .Pack (
@@ -1095,10 +1146,7 @@ def _MergeMessageField(self, tokenizer, message, field):
10951146 sub_message = getattr (message , field .name )
10961147 sub_message .SetInParent ()
10971148
1098- while not tokenizer .TryConsume (end_token ):
1099- if tokenizer .AtEnd ():
1100- raise tokenizer .ParseErrorPreviousToken ('Expected "%s".' % (end_token ,))
1101- self ._MergeField (tokenizer , sub_message )
1149+ self ._MergeMessage (tokenizer , sub_message , end_token )
11021150
11031151 if is_map_entry :
11041152 value_cpptype = field .message_type .fields_by_name ['value' ].cpp_type
0 commit comments