5050
5151#include " web/http/http_utils.h"
5252
53+ #include < charconv>
54+ #include < limits>
55+ #include < system_error>
5356#include < tuple>
5457#include < utility>
5558
5659#endif /* DOXYGEN_SHOULD_SKIP_THIS */
5760
5861namespace web ::http {
5962
63+ static bool parseContentLengthStrict (const std::string& s, std::size_t & out) {
64+ bool success = false ;
65+
66+ unsigned long long value = 0 ;
67+
68+ if (!s.empty ()) {
69+ const char * first = s.data ();
70+ const char * last = s.data () + s.size ();
71+
72+ const auto [ptr, ec] = std::from_chars (first, last, value, 10 );
73+
74+ if (ec == std::errc{} && ptr == last && value <= static_cast <unsigned long long >(std::numeric_limits<std::size_t >::max ())) {
75+ out = static_cast <std::size_t >(value);
76+ success = true ;
77+ }
78+ }
79+
80+ return success;
81+ }
82+
83+ static bool transferEncodingHasChunked (CiStringMap<std::string>& headers) {
84+ bool hasChunked = false ;
85+
86+ if (headers.contains (" Transfer-Encoding" )) {
87+ const std::string& encoding = headers[" Transfer-Encoding" ];
88+ hasChunked = web::http::ciContains (encoding, " chunked" );
89+ }
90+
91+ return hasChunked;
92+ }
93+
6094 // HTTP/1.0 and HTTP/1.1
6195 const std::regex Parser::httpVersionRegex (" ^HTTP/([1])[.]([0-1])$" );
6296
@@ -81,7 +115,7 @@ namespace web::http {
81115 contentLength = 0 ;
82116 contentLengthRead = 0 ;
83117
84- for (ContentDecoder* contentDecoder : decoderQueue) {
118+ for (const ContentDecoder* contentDecoder : decoderQueue) {
85119 delete contentDecoder;
86120 }
87121 decoderQueue.clear ();
@@ -158,58 +192,75 @@ namespace web::http {
158192 }
159193
160194 void Parser::analyzeHeader () {
161- if (headers.contains (" Content-Length" )) {
162- contentLength = std::stoul (headers[" Content-Length" ]);
163- decoderQueue.emplace_back (new web::http::decoder::Identity (socketContext, contentLength));
164- }
165- if (headers.contains (" Transfer-Encoding" )) {
166- const std::string& encoding = headers[" Transfer-Encoding" ];
195+ bool success = true ;
167196
168- if ( web::http::ciContains (encoding, " chunked " )) {
169- transferEncoding = TransferEncoding::Chunked;
170- decoderQueue. emplace_back ( new web::http::decoder::Chunked (socketContext) );
197+ // Determine message framing.
198+ // RFC 9112 §6.3: Transfer-Encoding (chunked) overrides Content-Length.
199+ const bool hasChunked = transferEncodingHasChunked (headers );
171200
172- if (headers.contains (" Trailer" )) {
173- std::string trailers = headers[" Trailer" ];
201+ if (hasChunked) {
202+ transferEncoding = TransferEncoding::Chunked;
203+ decoderQueue.emplace_back (new web::http::decoder::Chunked (socketContext));
174204
175- while (!trailers.empty ()) {
176- std::string trailerField;
177- std::tie (trailerField, trailers) = httputils::str_split (trailers, ' ,' );
178- httputils::str_trimm (trailerField);
179- trailerFieldsExpected.insert (trailerField);
180- trailerField.clear ();
181- }
182- trailerDecoder.setFieldsExpected (trailerFieldsExpected);
205+ if (headers.contains (" Trailer" )) {
206+ std::string trailers = headers[" Trailer" ];
207+
208+ while (!trailers.empty ()) {
209+ std::string trailerField;
210+ std::tie (trailerField, trailers) = httputils::str_split (trailers, ' ,' );
211+ httputils::str_trimm (trailerField);
212+ trailerFieldsExpected.insert (trailerField);
213+ trailerField.clear ();
183214 }
215+ trailerDecoder.setFieldsExpected (trailerFieldsExpected);
184216 }
185- if (web::http::ciContains (encoding, " compressed" )) {
186- // decoderQueue.emplace_back(new web::http::decoder::Compress(socketContext));
187- }
188- if (web::http::ciContains (encoding, " deflate" )) {
189- // decoderQueue.emplace_back(new web::http::decoder::Deflate(socketContext));
190- }
191- if (web::http::ciContains (encoding, " gzip" )) {
192- // decoderQueue.emplace_back(new web::http::decoder::GZip(socketContext));
217+ } else if (headers.contains (" Content-Length" )) {
218+ std::size_t length = 0 ;
219+
220+ if (!parseContentLengthStrict (headers[" Content-Length" ], length)) {
221+ parseError (400 , " Invalid Content-Length" );
222+ success = false ;
223+ } else {
224+ contentLength = length;
225+ decoderQueue.emplace_back (new web::http::decoder::Identity (socketContext, contentLength));
193226 }
194227 }
195- if (decoderQueue.empty ()) {
196- decoderQueue.emplace_back (new web::http::decoder::HTTP10Response (socketContext));
197- }
198-
199- if (headers.contains (" Content-Encoding" )) {
200- const std::string& encoding = headers[" Content-Encoding" ];
201228
202- if (web::http::ciContains (encoding, " compressed" )) {
203- // decoderQueue.emplace_back(new web::http::decoder::Compress(socketContext));
204- }
205- if (web::http::ciContains (encoding, " deflate" )) {
206- // decoderQueue.emplace_back(new web::http::decoder::Deflate(socketContext));
229+ if (success) {
230+ // Transfer-Encoding (other than chunked) is currently not implemented, but we keep the
231+ // existing behavior of not altering the decoder queue here.
232+ if (headers.contains (" Transfer-Encoding" )) {
233+ const std::string& encoding = headers[" Transfer-Encoding" ];
234+ if (web::http::ciContains (encoding, " compressed" )) {
235+ // decoderQueue.emplace_back(new web::http::decoder::Compress(socketContext));
236+ }
237+ if (web::http::ciContains (encoding, " deflate" )) {
238+ // decoderQueue.emplace_back(new web::http::decoder::Deflate(socketContext));
239+ }
240+ if (web::http::ciContains (encoding, " gzip" )) {
241+ // decoderQueue.emplace_back(new web::http::decoder::GZip(socketContext));
242+ }
207243 }
208- if (web::http::ciContains (encoding, " gzip" )) {
209- // decoderQueue.emplace_back(new web::http::decoder::GZip(socketContext));
244+
245+ if (decoderQueue.empty ()) {
246+ decoderQueue.emplace_back (new web::http::decoder::HTTP10Response (socketContext));
210247 }
211- if (web::http::ciContains (encoding, " br" )) {
212- // decoderQueue.emplace_back(new web::http::decoder::Br(socketContext));
248+
249+ if (headers.contains (" Content-Encoding" )) {
250+ const std::string& encoding = headers[" Content-Encoding" ];
251+
252+ if (web::http::ciContains (encoding, " compressed" )) {
253+ // decoderQueue.emplace_back(new web::http::decoder::Compress(socketContext));
254+ }
255+ if (web::http::ciContains (encoding, " deflate" )) {
256+ // decoderQueue.emplace_back(new web::http::decoder::Deflate(socketContext));
257+ }
258+ if (web::http::ciContains (encoding, " gzip" )) {
259+ // decoderQueue.emplace_back(new web::http::decoder::GZip(socketContext));
260+ }
261+ if (web::http::ciContains (encoding, " br" )) {
262+ // decoderQueue.emplace_back(new web::http::decoder::Br(socketContext));
263+ }
213264 }
214265 }
215266 }
0 commit comments