@@ -25,11 +25,19 @@ def close
2525 @request . body &.close
2626 end
2727 end
28+
29+ unless method_defined? ( :body_stream )
30+ def body_stream
31+ @request . body
32+ end
33+ end
2834 end
2935 end
3036end
3137
3238class Fluent ::Plugin ::Opentelemetry ::HttpInputHandler
39+ class SizeLimitError < StandardError ; end
40+
3341 using Fluent ::PluginHelper ::HttpServer ::Extension
3442
3543 def initialize ( http_config , logger )
@@ -54,13 +62,29 @@ def traces(req, &block)
5462 def common ( req , request_class , response_class )
5563 content_type = req . headers [ "content-type" ]
5664 content_encoding = req . headers [ "content-encoding" ] &.first
57- body = req . body
65+
66+ content_length = req . headers [ "content-length" ] &.first &.to_i
67+ if content_length && content_length >= @http_config . body_size_limit
68+ @logger . warn { "Received too big content length: #{ content_length } " }
69+ return response_payload_too_large
70+ end
71+
72+ begin
73+ body = read_body ( req , limit : @http_config . body_size_limit )
74+ rescue SizeLimitError
75+ @logger . warn { "Received payload exceeding body_size_limit" }
76+ return response_payload_too_large
77+ end
78+
5879 return response_unsupported_media_type unless valid_content_type? ( content_type )
5980 return response_bad_request ( content_type ) unless valid_content_encoding? ( content_encoding )
6081
6182 if content_encoding == Fluent ::Plugin ::Opentelemetry ::CONTENT_ENCODING_GZIP
6283 begin
63- body = Zlib ::GzipReader . new ( StringIO . new ( body ) ) . read
84+ body = decompress ( body , limit : @http_config . decompression_size_limit )
85+ rescue SizeLimitError
86+ @logger . warn { "Decompressed payload exceeding decompression_size_limit" }
87+ return response_payload_too_large
6488 rescue Zlib ::Error => e
6589 @logger . warn { "Failed to decompress gzip payload: #{ e . message } " }
6690 return response_bad_request ( content_type )
@@ -83,6 +107,42 @@ def common(req, request_class, response_class)
83107 req . close
84108 end
85109
110+ def read_body ( request , limit :)
111+ body = +""
112+ while ( chunk = request . body_stream &.read )
113+ body << chunk
114+ if body . bytesize > limit
115+ raise SizeLimitError , "Too large payload"
116+ end
117+ end
118+ body
119+ end
120+
121+ BYTES_TO_READ = 64 * 1024
122+
123+ def decompress ( compressed_data , limit :)
124+ io = StringIO . new ( compressed_data )
125+ out = +""
126+ loop do
127+ reader = Zlib ::GzipReader . new ( io )
128+ while ( chunk = reader . read ( BYTES_TO_READ ) )
129+ out << chunk
130+ if out . bytesize > limit
131+ raise SizeLimitError , "Decompressed data exceeds limit of #{ limit } bytes"
132+ end
133+ end
134+
135+ unused = reader . unused
136+ reader . finish
137+ unless unused . nil?
138+ adjust = unused . length
139+ io . pos -= adjust
140+ end
141+ break if io . eof?
142+ end
143+ out
144+ end
145+
86146 def valid_content_type? ( content_type )
87147 case content_type
88148 when Fluent ::Plugin ::Opentelemetry ::CONTENT_TYPE_PROTOBUF , Fluent ::Plugin ::Opentelemetry ::CONTENT_TYPE_JSON
@@ -106,6 +166,10 @@ def response_unsupported_media_type
106166 response ( 415 , Fluent ::Plugin ::Opentelemetry ::CONTENT_TYPE_PLAIN , "415 unsupported media type, supported: [application/json, application/x-protobuf]" )
107167 end
108168
169+ def response_payload_too_large
170+ response ( 413 , Fluent ::Plugin ::Opentelemetry ::CONTENT_TYPE_PLAIN , "413 Payload Too Large" )
171+ end
172+
109173 def response_bad_request ( content_type )
110174 response ( 400 , content_type , "" ) # TODO: fix body message
111175 end
0 commit comments