This is an automated email from the ASF dual-hosted git repository.

Jens-G pushed a commit to branch release/0.25.0
in repository https://gitbox.apache.org/repos/asf/thrift.git


The following commit(s) were added to refs/heads/release/0.25.0 by this push:
     new e6f57e24f Collect JSON strings in chunks; bound numbers and message 
names Client: erl
e6f57e24f is described below

commit e6f57e24f649c7e4f011fdc61185daca46e97626
Author: Jens Geyer <[email protected]>
AuthorDate: Thu Sep 24 00:44:32 2026 +0200

    Collect JSON strings in chunks; bound numbers and message names
    Client: erl
    
    thrift_json_parser collects the characters of a string in lists of up to
    256 and turns each list into a UTF-8 chunk, and it refuses a number of more
    than 1024 characters before collecting its digits. read_all/1 of
    thrift_json_protocol joins what it reads into 64 KiB chunks as it goes, and
    message_begin refuses a name longer than ?MAX_MESSAGE_NAME_SIZE (4096 bytes)
    with {error, {message_name_exceeds_maximum, 4096}}.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 lib/erl/include/thrift_constants.hrl       |   5 +
 lib/erl/src/thrift_json_parser.erl         | 142 ++++++++++++++++++-----------
 lib/erl/src/thrift_json_protocol.erl       |  45 +++++----
 lib/erl/test/test_thrift_json_protocol.erl |  55 +++++++++++
 4 files changed, 178 insertions(+), 69 deletions(-)

diff --git a/lib/erl/include/thrift_constants.hrl 
b/lib/erl/include/thrift_constants.hrl
index af55f22d1..5339131f5 100644
--- a/lib/erl/include/thrift_constants.hrl
+++ b/lib/erl/include/thrift_constants.hrl
@@ -81,3 +81,8 @@
 %% {thrift, [{max_message_size, Bytes}]}, or for one transport or protocol with
 %% its max_message_size option.
 -define(DEFAULT_MAX_MESSAGE_SIZE, (100 * 1024 * 1024)).
+
+%% The longest message name thrift_json_protocol will read. A name the
+%% processor can dispatch is a function name, an atom of at most 255
+%% characters, after a service name and a ':' for a multiplexed service.
+-define(MAX_MESSAGE_NAME_SIZE, 4096).
diff --git a/lib/erl/src/thrift_json_parser.erl 
b/lib/erl/src/thrift_json_parser.erl
index 18e0c0377..30c987e5f 100644
--- a/lib/erl/src/thrift_json_parser.erl
+++ b/lib/erl/src/thrift_json_parser.erl
@@ -79,16 +79,49 @@ handle_event(Event, State) -> [Event] ++ State.
     Symbol =:= ?space; Symbol =:= ?tab; Symbol =:= ?cr; Symbol =:= ?newline
 ).
 
-%% lists are benchmarked to be faster (tho higher in memory usage) than 
binaries
-new_seq() -> [].
+%% The digits of a number, newest first. There are at most
+%% ?MAX_NUMBER_LENGTH of them.
 new_seq(C) -> [C].
 
 acc_seq(Seq, C) when is_list(C) -> lists:reverse(C) ++ Seq;
 acc_seq(Seq, C) -> [C] ++ Seq.
 
-end_seq(Seq) -> unicode:characters_to_binary(lists:reverse(Seq)).
-
-end_seq(Seq, _) -> end_seq(Seq).
+%% The most characters a number may take. A 64-bit integer takes at most 20,
+%% and a double with 17 significant digits about 25, or about 330 written
+%% without an exponent.
+-define(MAX_NUMBER_LENGTH, 1024).
+
+%% The characters of a string: {Count, Characters, Chunks}. The characters,
+%% newest first, are turned into a UTF-8 chunk every ?STRING_CHUNK_LENGTH of
+%% them; the chunks are newest first as well.
+-define(STRING_CHUNK_LENGTH, 256).
+
+new_str() -> {0, [], []}.
+
+acc_str({Count, Chars, Chunks}, C) when Count >= ?STRING_CHUNK_LENGTH ->
+    {1, [C], [unicode:characters_to_binary(lists:reverse(Chars)) | Chunks]};
+acc_str({Count, Chars, Chunks}, C) ->
+    {Count + 1, [C | Chars], Chunks}.
+
+end_str({_Count, Chars, Chunks}) ->
+    iolist_to_binary(lists:reverse(Chunks, 
[unicode:characters_to_binary(lists:reverse(Chars))])).
+
+%% Refuses a number longer than ?MAX_NUMBER_LENGTH characters before its
+%% digits are collected.
+check_number(Bin) ->
+    case number_length(Bin, 0) =< ?MAX_NUMBER_LENGTH of
+        true -> ok;
+        false -> erlang:error(badarg)
+    end.
+
+number_length(<<C, Rest/binary>>, N) when
+    N =< ?MAX_NUMBER_LENGTH,
+    ((C >= $0 andalso C =< $9) orelse C =:= ?decimalpoint orelse C =:= $e 
orelse C =:= $E orelse
+        C =:= ?positive orelse C =:= ?negative)
+->
+    number_length(Rest, N + 1);
+number_length(_Bin, N) ->
+    N.
 
 start(<<16#ef, 16#bb, 16#bf, Rest/binary>>, Handler, Stack, Config) ->
     value(Rest, Handler, Stack, Config);
@@ -96,18 +129,21 @@ start(Bin, Handler, Stack, Config) ->
     value(Bin, Handler, Stack, Config).
 
 value(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
-    string(Rest, Handler, new_seq(), Stack, Config);
+    string(Rest, Handler, new_str(), Stack, Config);
 value(<<$t, Rest/binary>>, Handler, Stack, Config) ->
     true(Rest, Handler, Stack, Config);
 value(<<$f, Rest/binary>>, Handler, Stack, Config) ->
     false(Rest, Handler, Stack, Config);
 value(<<$n, Rest/binary>>, Handler, Stack, Config) ->
     null(Rest, Handler, Stack, Config);
-value(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
+value(<<?negative, Rest/binary>> = Bin, Handler, Stack, Config) ->
+    ok = check_number(Bin),
     negative(Rest, Handler, new_seq($-), Stack, Config);
-value(<<?zero, Rest/binary>>, Handler, Stack, Config) ->
+value(<<?zero, Rest/binary>> = Bin, Handler, Stack, Config) ->
+    ok = check_number(Bin),
     zero(Rest, Handler, new_seq($0), Stack, Config);
-value(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_nonzero(S) ->
+value(<<S, Rest/binary>> = Bin, Handler, Stack, Config) when ?is_nonzero(S) ->
+    ok = check_number(Bin),
     integer(Rest, Handler, new_seq(S), Stack, Config);
 value(<<?start_object, Rest/binary>>, Handler, Stack, Config) ->
     object(Rest, handle_event(start_object, Handler, Config), [key | Stack], 
Config);
@@ -119,7 +155,7 @@ value(_Bin, _Handler, _Stack, _Config) ->
     erlang:error(badarg).
 
 object(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
-    string(Rest, Handler, new_seq(), Stack, Config);
+    string(Rest, Handler, new_str(), Stack, Config);
 object(<<?end_object, Rest/binary>>, Handler, [key | Stack], Config) ->
     maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
 object(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
@@ -142,7 +178,7 @@ colon(_Bin, _Handler, _Stack, _Config) ->
     erlang:error(badarg).
 
 key(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
-    string(Rest, Handler, new_seq(), Stack, Config);
+    string(Rest, Handler, new_str(), Stack, Config);
 key(<<S, Rest/binary>>, Handler, Stack, Config) when ?is_whitespace(S) ->
     key(Rest, Handler, Stack, Config);
 key(_Bin, _Handler, _Stack, _Config) ->
@@ -153,59 +189,59 @@ key(_Bin, _Handler, _Stack, _Config) ->
 string(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
     doublequote(Rest, Handler, Acc, Stack, Config);
 string(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, ?solidus), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, ?solidus), Stack, Config);
 string(<<?rsolidus/utf8, Rest/binary>>, Handler, Acc, Stack, Config) ->
     unescape(Rest, Handler, Acc, Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 16#20, 
X < 16#2028 ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X == 
16#2028; X == 16#2029 ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X > 16#2029, 
X < 16#d800 ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X > 16#dfff, 
X < 16#fdd0 ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X > 16#fdef, 
X < 16#fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#10000, X < 16#1fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#20000, X < 16#2fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#30000, X < 16#3fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#40000, X < 16#4fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#50000, X < 16#5fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#60000, X < 16#6fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#70000, X < 16#7fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#80000, X < 16#8fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#90000, X < 16#9fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#a0000, X < 16#afffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#b0000, X < 16#bfffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#c0000, X < 16#cfffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#d0000, X < 16#dfffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#e0000, X < 16#efffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#f0000, X < 16#ffffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 string(<<X/utf8, Rest/binary>>, Handler, Acc, Stack, Config) when X >= 
16#100000, X < 16#10fffe ->
-    string(Rest, Handler, acc_seq(Acc, X), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, X), Stack, Config);
 %% surrogates
 string(<<237, X, _, Rest/binary>>, Handler, Acc, Stack, Config = 
#config{strict_utf8 = false}) when
     X >= 160
 ->
-    string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config);
 %% u+xfffe, u+xffff, control codes and other noncharacters
 string(<<_/utf8, Rest/binary>>, Handler, Acc, Stack, Config = 
#config{strict_utf8 = false}) ->
-    string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config);
 %% u+fffe and u+ffff for R14BXX (subsequent runtimes will happily match the
 %%  preceding clause
 string(
@@ -213,7 +249,7 @@ string(
 ) when
     X == 190; X == 191
 ->
-    string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config);
 %% overlong encodings and missing continuations of a 2 byte sequence
 string(<<X, Rest/binary>>, Handler, Acc, Stack, Config = #config{strict_utf8 = 
false}) when
     X >= 192, X =< 223
@@ -231,44 +267,44 @@ string(<<X, Rest/binary>>, Handler, Acc, Stack, Config = 
#config{strict_utf8 = f
     strip_continuations(Rest, Handler, Acc, Stack, Config, 3);
 %% incompletes and unexpected bytes, including orphan continuations
 string(<<_, Rest/binary>>, Handler, Acc, Stack, Config = #config{strict_utf8 = 
false}) ->
-    string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config);
 string(_Bin, _Handler, _Acc, _Stack, _Config) ->
     erlang:error(badarg).
 
 doublequote(Rest, Handler, Acc, [key | _] = Stack, Config) ->
-    colon(Rest, handle_event({key, end_seq(Acc, Config)}, Handler, Config), 
Stack, Config);
+    colon(Rest, handle_event({key, end_str(Acc)}, Handler, Config), Stack, 
Config);
 doublequote(Rest, Handler, Acc, Stack, Config) ->
-    maybe_done(Rest, handle_event({string, end_seq(Acc, Config)}, Handler, 
Config), Stack, Config).
+    maybe_done(Rest, handle_event({string, end_str(Acc)}, Handler, Config), 
Stack, Config).
 
 %% strips continuation bytes after bad utf bytes, guards against both too short
 %%  and overlong sequences. N is the maximum number of bytes to strip
 strip_continuations(<<Rest/binary>>, Handler, Acc, Stack, Config, 0) ->
-    string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config);
 strip_continuations(<<X, Rest/binary>>, Handler, Acc, Stack, Config, N) when X 
>= 128, X =< 191 ->
     strip_continuations(Rest, Handler, Acc, Stack, Config, N - 1);
 %% not a continuation byte, insert a replacement character for sequence thus
 %%  far and dispatch back to string
 strip_continuations(<<Rest/binary>>, Handler, Acc, Stack, Config, _) ->
-    string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config).
+    string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config).
 
 %% this all gets really gross and should probably eventually be folded into
 %%  but for now it fakes being part of string on incompletes and errors
 unescape(<<$b, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\b), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\b), Stack, Config);
 unescape(<<$f, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\f), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\f), Stack, Config);
 unescape(<<$n, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\n), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\n), Stack, Config);
 unescape(<<$r, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\r), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\r), Stack, Config);
 unescape(<<$t, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\t), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\t), Stack, Config);
 unescape(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\"), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\"), Stack, Config);
 unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $\\), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $\\), Stack, Config);
 unescape(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
-    string(Rest, Handler, acc_seq(Acc, $/), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, $/), Stack, Config);
 unescape(
     <<$u, $d, A, B, C, ?rsolidus, $u, $d, X, Y, Z, Rest/binary>>, Handler, 
Acc, Stack, Config
 ) when
@@ -282,7 +318,7 @@ unescape(
     High = erlang:list_to_integer([$d, A, B, C], 16),
     Low = erlang:list_to_integer([$d, X, Y, Z], 16),
     Codepoint = (High - 16#d800) * 16#400 + (Low - 16#dc00) + 16#10000,
-    string(Rest, Handler, acc_seq(Acc, Codepoint), Stack, Config);
+    string(Rest, Handler, acc_str(Acc, Codepoint), Stack, Config);
 unescape(
     <<$u, $d, A, B, C, ?rsolidus, $u, W, X, Y, Z, Rest/binary>>, Handler, Acc, 
Stack, Config
 ) when
@@ -294,15 +330,15 @@ unescape(
     ?is_hex(Y),
     ?is_hex(Z)
 ->
-    string(Rest, Handler, acc_seq(Acc, [16#fffd, 16#fffd]), Stack, Config);
+    string(Rest, Handler, acc_str(acc_str(Acc, 16#fffd), 16#fffd), Stack, 
Config);
 unescape(<<$u, A, B, C, D, Rest/binary>>, Handler, Acc, Stack, Config) when
     ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D)
 ->
     case erlang:list_to_integer([A, B, C, D], 16) of
         Codepoint when Codepoint < 16#d800; Codepoint > 16#dfff ->
-            string(Rest, Handler, acc_seq(Acc, Codepoint), Stack, Config);
+            string(Rest, Handler, acc_str(Acc, Codepoint), Stack, Config);
         _ ->
-            string(Rest, Handler, acc_seq(Acc, 16#fffd), Stack, Config)
+            string(Rest, Handler, acc_str(Acc, 16#fffd), Stack, Config)
     end;
 unescape(_Bin, _Handler, _Acc, _Stack, _Config) ->
     erlang:error(badarg).
diff --git a/lib/erl/src/thrift_json_protocol.erl 
b/lib/erl/src/thrift_json_protocol.erl
index c5331db55..b18e08681 100644
--- a/lib/erl/src/thrift_json_protocol.erl
+++ b/lib/erl/src/thrift_json_protocol.erl
@@ -57,6 +57,8 @@
 
 -define(VERSION_1, 1).
 -define(JSON_DOUBLE_PRECISION, 16).
+%% What has been read of a message is joined into a binary every this many 
bytes.
+-define(JSON_READ_CHUNK_SIZE, 65536).
 
 typeid_to_json(?tType_BOOL) -> "tf";
 typeid_to_json(?tType_DOUBLE) -> "dbl";
@@ -379,7 +381,7 @@ escape_byte(B) -> <<B>>.
 %% A message longer than max_message_size is refused before more than that
 %% many bytes of it are read.
 read_all(#json_protocol{transport = Transport0} = State) ->
-    case read_message(Transport0, max_message_size(State), [], 0, 0, false, 
false) of
+    case read_message(Transport0, max_message_size(State), {0, [], [], 0}, 0, 
false, false) of
         {Transport1, {ok, Bin}} ->
             P = thrift_json_parser:parser(),
             [First | Rest] = P(Bin),
@@ -388,7 +390,7 @@ read_all(#json_protocol{transport = Transport0} = State) ->
             {State#json_protocol{transport = Transport1}, Error}
     end.
 
-read_message(Transport0, Max, Parts, Size, Depth, InString, Escaped) ->
+read_message(Transport0, Max, Read = {Size, _, _, _}, Depth, InString, 
Escaped) ->
     Least = least_left(Depth, InString, Escaped),
     case Size + Least > Max of
         true ->
@@ -399,25 +401,34 @@ read_message(Transport0, Max, Parts, Size, Depth, 
InString, Escaped) ->
                 {ok, <<>>} ->
                     {Transport1, {error, eof}};
                 {ok, Data} ->
+                    Read1 = add_read(Data, Read),
                     case scan(Data, Depth, InString, Escaped) of
                         done ->
-                            {Transport1, {ok, 
iolist_to_binary(lists:reverse([Data | Parts]))}};
+                            {Transport1, {ok, message_read(Read1)}};
                         {Depth1, InString1, Escaped1} ->
-                            read_message(
-                                Transport1,
-                                Max,
-                                [Data | Parts],
-                                Size + byte_size(Data),
-                                Depth1,
-                                InString1,
-                                Escaped1
-                            )
+                            read_message(Transport1, Max, Read1, Depth1, 
InString1, Escaped1)
                     end;
                 {error, _} = Error ->
                     {Transport1, Error}
             end
     end.
 
+%% What has been read of a message: {Size, Chunks, Parts, PartsSize}. The
+%% parts read most recently are joined into a chunk once they add up to
+%% ?JSON_READ_CHUNK_SIZE bytes. Both lists are newest first.
+add_read(Data, {Size, Chunks, Parts, PartsSize}) ->
+    DataSize = byte_size(Data),
+    case PartsSize + DataSize >= ?JSON_READ_CHUNK_SIZE of
+        true ->
+            Chunk = iolist_to_binary(lists:reverse([Data | Parts])),
+            {Size + DataSize, [Chunk | Chunks], [], 0};
+        false ->
+            {Size + DataSize, Chunks, [Data | Parts], PartsSize + DataSize}
+    end.
+
+message_read({_Size, Chunks, Parts, _PartsSize}) ->
+    iolist_to_binary([lists:reverse(Chunks), lists:reverse(Parts)]).
+
 %% The fewest bytes that can still complete the message: a closing bracket
 %% for each open one, and the end of an open string, with the character an
 %% escape in it still needs.
@@ -693,15 +704,17 @@ read_message_begin(This0) ->
         )
     of
         {This1, {ok, [_, Version, Name, Type, SeqId]}} ->
-            case Version =:= ?VERSION_1 of
+            if
+                Version =/= ?VERSION_1 ->
+                    {This1, {error, no_json_protocol_version}};
+                byte_size(Name) > ?MAX_MESSAGE_NAME_SIZE ->
+                    {This1, {error, {message_name_exceeds_maximum, 
?MAX_MESSAGE_NAME_SIZE}}};
                 true ->
                     {This1, #protocol_message_begin{
                         name = binary_to_list(Name),
                         type = Type,
                         seqid = SeqId
-                    }};
-                false ->
-                    {This1, {error, no_json_protocol_version}}
+                    }}
             end;
         Other ->
             Other
diff --git a/lib/erl/test/test_thrift_json_protocol.erl 
b/lib/erl/test/test_thrift_json_protocol.erl
index eb060f83e..f3f281975 100644
--- a/lib/erl/test/test_thrift_json_protocol.erl
+++ b/lib/erl/test/test_thrift_json_protocol.erl
@@ -288,6 +288,61 @@ long_message_test() ->
     {_, Read2} = read_message(Protocol1, Type),
     ?assertEqual({"second", {<<"]}\"[{">>, 2}}, Read2).
 
+%%%% Memory %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% The largest heap, in words, a reader process gets for a message with a
+%% string of about two million characters: less than one list cell each.
+-define(READ_HEAP_WORDS, 4000000).
+
+%% A message with a long string, of every kind of character the reader
+%% handles, is read within ?READ_HEAP_WORDS.
+long_string_heap_test() ->
+    Type = {struct, [{1, string}]},
+    Text = binary:copy(list_to_binary(?AWKWARD), 80000),
+    Bytes = message("m", Type, {args, Text}),
+    Parent = self(),
+    {Pid, Ref} = spawn_opt(
+        fun() ->
+            {_, {"m", {Read}}} = read_message(new_protocol(Bytes), Type),
+            Parent ! {read, self(), Read =:= Text}
+        end,
+        [monitor, {max_heap_size, #{size => ?READ_HEAP_WORDS, kill => true, 
error_logger => false}}]
+    ),
+    receive
+        {read, Pid, Same} ->
+            erlang:demonitor(Ref, [flush]),
+            ?assert(Same);
+        {'DOWN', Ref, process, Pid, Reason} ->
+            ?assertEqual(read, {not_read, Reason})
+    after 60000 ->
+        exit(Pid, kill),
+        ?assert(false)
+    end.
+
+%% A number of more than 1024 characters is refused; one of 1024 is read.
+long_number_test() ->
+    Type = {struct, [{1, i64}]},
+    Message = fun(Digits) ->
+        iolist_to_binary([<<"[1,\"m\",1,42,{\"1\":{\"i64\":">>, Digits, 
<<"}}]">>])
+    end,
+    Longest = binary:copy(<<"1">>, 1024),
+    {_, {"m", {Value}}} = read_message(new_protocol(Message(Longest)), Type),
+    ?assertEqual(binary_to_integer(Longest), Value),
+    ?assertError(badarg, read_message(new_protocol(Message(<<Longest/binary, 
"1">>)), Type)).
+
+%% A message name up to ?MAX_MESSAGE_NAME_SIZE bytes is read; a longer one is
+%% refused by message_begin.
+message_name_test() ->
+    Type = {struct, [{1, string}]},
+    AtMax = lists:duplicate(?MAX_MESSAGE_NAME_SIZE, $n),
+    {_, {Name, _}} = read_message(new_protocol(message(AtMax, Type, {args, 
"x"})), Type),
+    ?assertEqual(AtMax, Name),
+    Over = message([$n | AtMax], Type, {args, "x"}),
+    ?assertMatch(
+        {_, {error, {message_name_exceeds_maximum, ?MAX_MESSAGE_NAME_SIZE}}},
+        thrift_protocol:read(new_protocol(Over), message_begin)
+    ).
+
 %%%% Over a socket %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
 handle_function(_Function, Args) -> {reply, element(1, Args)}.

Reply via email to