Skip to content

Commit f4de3cb

Browse files
Rewrite HTTPClient chunked transfer-encoding (#3034)
* Rewrite HTTPClient chunked transfer-encoding Fixes #3029 The chunked decoder seems to have had some race conditions resulting in lost data. The HTTPClient::getStreamPtr returned the raw WiFiClient which included all of the chunked block size markers, requiring the user to manually parse the chunked encoding. Remove the existing chunked handling, add a HTTPStream as a WiFiClient subclass. The HTTPClient will return this HTTPStream which will always properly cut out chunk markers when present (and pass things raw for non-encoded streams). Use this new HTTPStream class to replace the existing handling in HTTPClient Update the fingerprint in the StreamHTTPSClient example because their cert was updated. * Astyle * Add block write passthru * Add other WiFiClient passthru calls
1 parent 7ea9115 commit f4de3cb

File tree

3 files changed

+238
-53
lines changed

3 files changed

+238
-53
lines changed

libraries/HTTPClient/examples/StreamHttpsClient/StreamHttpsClient.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void loop() {
4545
Serial.print("[HTTPS] begin...\n");
4646

4747
// configure server and url
48-
const char *fp = "41:FA:FD:B6:96:5F:33:09:F4:ED:09:28:BF:66:4D:5B:A2:88:03:65";
48+
const char *fp = "e5:e8:05:7d:85:70:b0:db:d9:5a:b6:a6:bb:2b:29:ae:d9:b1:b7:f9";
4949

5050
HTTPClient https;
5151
https.setFingerprint(fp);

libraries/HTTPClient/src/HTTPClient.cpp

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol
323323
return false;
324324
}
325325
DEBUG_HTTPCLIENT("[HTTP-Client][begin] host: %s port: %d url: %s\n", _host.c_str(), _port, _uri.c_str());
326+
326327
return true;
327328
}
328329

@@ -675,6 +676,7 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
675676
}
676677
} while (redirect);
677678

679+
678680
// handle Server Response (Header)
679681
return returnError(code);
680682
}
@@ -744,7 +746,7 @@ const String& HTTPClient::getLocation(void) {
744746
*/
745747
WiFiClient& HTTPClient::getStream(void) {
746748
if (connected()) {
747-
return *_client();
749+
return _stream;
748750
}
749751

750752
DEBUG_HTTPCLIENT("[HTTP-Client] getStream: not connected\n");
@@ -758,7 +760,7 @@ WiFiClient& HTTPClient::getStream(void) {
758760
*/
759761
WiFiClient* HTTPClient::getStreamPtr(void) {
760762
if (connected()) {
761-
return _client();
763+
return &_stream;
762764
}
763765

764766
DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n");
@@ -808,57 +810,10 @@ int HTTPClient::writeToPrint(Print * print) {
808810
// return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
809811
// }
810812
} else if (_transferEncoding == HTTPC_TE_CHUNKED) {
811-
int size = 0;
812-
while (1) {
813-
if (!connected()) {
814-
return returnError(HTTPC_ERROR_CONNECTION_LOST);
815-
}
816-
String chunkHeader = _client()->readStringUntil('\n');
817-
818-
if (chunkHeader.length() <= 0) {
819-
return returnError(HTTPC_ERROR_READ_TIMEOUT);
820-
}
821-
822-
chunkHeader.trim(); // remove \r
823-
DEBUG_HTTPCLIENT("[HTTP-Client] chunk header: '%s'\n", chunkHeader.c_str());
824-
825-
// read size of chunk
826-
len = (uint32_t) strtol((const char *) chunkHeader.c_str(), nullptr, 16);
827-
size += len;
828-
DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len);
829-
830-
// data left?
831-
if (len > 0) {
832-
// read len bytes with timeout
833-
int r = StreamSendSize(_client(), print, len);
834-
if (r != len) {
835-
return HTTPC_ERROR_NO_STREAM;
836-
}
837-
838-
// if (_client->getLastSendReport() != Stream::Report::Success)
839-
// // not all data transferred
840-
// return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
841-
ret += r;
842-
} else {
813+
ret = StreamSendSize(&_stream, print, -1);
843814

844-
// if no length Header use global chunk size
845-
if (_size <= 0) {
846-
_size = size;
847-
}
848-
849-
// check if we have write all data out
850-
if (ret != _size) {
851-
return returnError(HTTPC_ERROR_STREAM_WRITE);
852-
}
853-
break;
854-
}
855-
856-
// read trailing \r\n at the end of the chunk
857-
char buf[2];
858-
auto trailing_seq_len = _client()->readBytes((uint8_t*)buf, 2);
859-
if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') {
860-
return returnError(HTTPC_ERROR_READ_TIMEOUT);
861-
}
815+
if (ret == 0) {
816+
return HTTPC_ERROR_NO_STREAM;
862817
}
863818
} else {
864819
return returnError(HTTPC_ERROR_ENCODING);
@@ -1112,6 +1067,9 @@ int HTTPClient::handleHeaderResponse() {
11121067

11131068
_canReuse = _reuse;
11141069

1070+
// Hook up the HTTPStream to this WiFiClient already connected and headers decoded
1071+
_stream.reset(_client(), false);
1072+
11151073
String transferEncoding;
11161074

11171075
_transferEncoding = HTTPC_TE_IDENTITY;
@@ -1204,6 +1162,9 @@ int HTTPClient::handleHeaderResponse() {
12041162
_transferEncoding = HTTPC_TE_IDENTITY;
12051163
}
12061164

1165+
// Hook up the HTTPStream to this WiFiClient already connected and headers decoded
1166+
_stream.reset(_client(), _transferEncoding == HTTPC_TE_CHUNKED);
1167+
12071168
if (_returnCode <= 0) {
12081169
DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!");
12091170
_returnCode = HTTPC_ERROR_NO_HTTP_SERVER;

libraries/HTTPClient/src/HTTPClient.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,228 @@ typedef struct {
176176
} Cookie;
177177
typedef std::vector<Cookie> CookieJar;
178178

179+
class HTTPStream : public WiFiClient {
180+
public:
181+
HTTPStream() {
182+
_conn = nullptr;
183+
_chunked = false;
184+
_chunkLen = 0;
185+
_state = READ_HEX;
186+
_partial = 0;
187+
_eof = false;
188+
}
189+
190+
HTTPStream(const HTTPStream&);
191+
HTTPStream& operator=(const HTTPStream&);
192+
193+
void reset(WiFiClient *connection, bool chunked) {
194+
DEBUG_HTTPCLIENT("[HTTPStream] reset(%p, %d)\n", connection, chunked);
195+
_conn = connection;
196+
_partial = 0;
197+
_state = READ_HEX;
198+
_chunked = chunked;
199+
_eof = false;
200+
}
201+
202+
virtual size_t write(uint8_t c) override {
203+
return _conn->write(c);
204+
}
205+
206+
virtual size_t write(const uint8_t *buf, size_t size) override {
207+
return _conn->write(buf, size);
208+
}
209+
210+
virtual uint8_t connected() override {
211+
return _conn->connected();
212+
}
213+
214+
virtual int available() override {
215+
if (!_chunked) {
216+
return _conn->available();
217+
}
218+
// We're chunked at this point
219+
if (_eof) {
220+
return 0;
221+
}
222+
if (!_chunkLen) {
223+
tryReadChunkLen(1);
224+
}
225+
return std::min(_chunkLen, _conn->available());
226+
}
227+
228+
virtual int availableForWrite() override {
229+
return _conn->availableForWrite();
230+
}
231+
232+
virtual void flush() override {
233+
_conn->flush();
234+
}
235+
236+
virtual void stop() override {
237+
_conn->stop();
238+
}
239+
240+
virtual int read() override {
241+
if (!_chunked) {
242+
return _conn->read();
243+
}
244+
// We're chunked at this point
245+
if (_eof) {
246+
DEBUG_HTTPCLIENT("[HTTPStream] ::read() after EOF\n");
247+
return -1;
248+
}
249+
if (!_chunkLen) {
250+
tryReadChunkLen(_timeout);
251+
}
252+
if (!_chunkLen) {
253+
DEBUG_HTTPCLIENT("[HTTPStream] ::read no chunkLen\n");
254+
return -1;
255+
}
256+
uint32_t start = millis();
257+
while (((millis() - start) < _timeout) && !_conn->available() && _conn->connected()) {
258+
delay(1);
259+
}
260+
if (_conn->available()) {
261+
_chunkLen--;
262+
return _conn->read();
263+
} else {
264+
DEBUG_HTTPCLIENT("[HTTPStream] ::read wrapped stream failure. avail = %d, conn = %d\n", _conn->available(), _conn->connected());
265+
return -1;
266+
}
267+
}
268+
269+
virtual int read(uint8_t *buf, size_t size) override {
270+
if (!_chunked) {
271+
return _conn->read(buf, size);
272+
}
273+
for (int i = 0; i < (int)size; i++) {
274+
int c = read();
275+
if (c < 0) {
276+
return i;
277+
}
278+
*(buf++) = (uint8_t)c;
279+
}
280+
return size;
281+
}
282+
283+
virtual int peek() override {
284+
if (!_chunked) {
285+
return _conn->peek();
286+
}
287+
// We're chunked at this point
288+
if (_eof) {
289+
DEBUG_HTTPCLIENT("[HTTPStream] ::peek after EOF\n");
290+
return -1;
291+
}
292+
if (!_chunkLen) {
293+
tryReadChunkLen(_timeout);
294+
}
295+
if (!_chunkLen) {
296+
DEBUG_HTTPCLIENT("[HTTPStream] ::peek no chunkLen\n");
297+
return -1;
298+
}
299+
uint32_t start = millis();
300+
while (((millis() - start) < _timeout) && !_conn->available() && _conn->connected()) {
301+
delay(1);
302+
}
303+
if (_conn->available()) {
304+
return _conn->peek();
305+
} else {
306+
DEBUG_HTTPCLIENT("[HTTPStream] ::peek wrapped stream failure. avail = %d, conn = %d\n", _conn->available(), _conn->connected());
307+
return -1;
308+
}
309+
}
310+
311+
void setTimeout(unsigned long timeout) {
312+
_timeout = timeout;
313+
_conn->setTimeout(timeout);
314+
}
315+
316+
private:
317+
void tryReadChunkLen(uint32_t to) {
318+
if (_state == ERROR) {
319+
return;
320+
}
321+
uint32_t start = millis();
322+
while ((millis() - start) <= to) {
323+
if (_conn->available()) {
324+
int recv = _conn->read();
325+
if (recv < 0) {
326+
DEBUG_HTTPCLIENT("[HTTPStream] Read of available data failed\n");
327+
_state = ERROR;
328+
return;
329+
}
330+
switch (_state) {
331+
case READ_HEX:
332+
if (recv == '\r') {
333+
DEBUG_HTTPCLIENT("[HTTPStream] Saw \\r of chunk len\r\n");
334+
_state = READ_LF;
335+
break;
336+
}
337+
if (recv >= '0' && recv <= '9') {
338+
DEBUG_HTTPCLIENT("[HTTPStream] Read %c of chunk size\n", recv);
339+
_partial <<= 4;
340+
_partial |= recv - '0';
341+
} else if (tolower(recv) >= 'a' && tolower(recv) <= 'f') {
342+
DEBUG_HTTPCLIENT("[HTTPStream] Read %c of chunk size\n", recv);
343+
_partial <<= 4;
344+
_partial |= tolower(recv) - 'a' + 10;
345+
} else {
346+
DEBUG_HTTPCLIENT("[HTTPStream] READ_HEX error '%c'\n", recv);
347+
_state = ERROR;
348+
return;
349+
}
350+
break;
351+
case READ_LF:
352+
if (recv != '\n') {
353+
_state = ERROR;
354+
DEBUG_HTTPCLIENT("[HTTPStream] READ_LF error '%02x'\n", recv);
355+
return;
356+
}
357+
DEBUG_HTTPCLIENT("[HTTPStream] Chunk len = %d\n", _partial);
358+
_chunkLen = _partial;
359+
_partial = 0;
360+
_state = TAIL_CR;
361+
if (_chunkLen == 0) {
362+
// 0-sized chunk is EOF special case
363+
_eof = true;
364+
}
365+
return;
366+
case TAIL_CR:
367+
if (recv == '\r') {
368+
DEBUG_HTTPCLIENT("[HTTPStream] Saw \\r of chunk end\n");
369+
_state = TAIL_LF;
370+
break;
371+
}
372+
DEBUG_HTTPCLIENT("[HTTPStream] TAIL_CR error '%c'\n", recv);
373+
_state = ERROR;
374+
return;
375+
case TAIL_LF:
376+
if (recv == '\n') {
377+
DEBUG_HTTPCLIENT("[HTTPStream] Saw \\n of chunk end\n");
378+
_state = READ_HEX;
379+
break;
380+
}
381+
DEBUG_HTTPCLIENT("[HTTPStream] TAIL_LF error '%c'\n", recv);
382+
_state = ERROR;
383+
return;
384+
case ERROR:
385+
return;
386+
}
387+
}
388+
}
389+
DEBUG_HTTPCLIENT("[HTTPStream] Timeout waiting for chunk\n");
390+
}
391+
392+
WiFiClient *_conn;
393+
bool _chunked;
394+
int _chunkLen;
395+
enum { READ_HEX, READ_LF, TAIL_CR, TAIL_LF, ERROR } _state;
396+
int _partial;
397+
bool _eof;
398+
};
399+
400+
179401
class HTTPClient {
180402
public:
181403
HTTPClient() = default;
@@ -407,4 +629,6 @@ class HTTPClient {
407629
std::unique_ptr<StreamString> _payload;
408630
// Cookie jar support
409631
CookieJar *_cookieJar = nullptr;
632+
633+
HTTPStream _stream;
410634
};

0 commit comments

Comments
 (0)