Skip to content

Commit bb6309a

Browse files
Merge pull request #215 from ESP32Async/fix/AsyncWebHeader
Fixed Header parsing
2 parents 029b057 + c5fa501 commit bb6309a

File tree

5 files changed

+99
-34
lines changed

5 files changed

+99
-34
lines changed

examples/HeaderManipulation/HeaderManipulation.ino

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,29 @@ void setup() {
7979
)
8080
.addMiddleware(&headerFree);
8181

82+
// curl -v http://192.168.4.1/
83+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
84+
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello, world!");
85+
response->addHeader(AsyncWebHeader::parse("X-Test-1: value1"));
86+
response->addHeader(AsyncWebHeader::parse("X-Test-2:value2"));
87+
response->addHeader(AsyncWebHeader::parse("X-Test-3:"));
88+
response->addHeader(AsyncWebHeader::parse("X-Test-4: "));
89+
response->addHeader(AsyncWebHeader::parse(""));
90+
response->addHeader(AsyncWebHeader::parse(":"));
91+
request->send(response);
92+
/**
93+
< HTTP/1.1 200 OK
94+
< connection: close
95+
< X-Test-1: value1
96+
< X-Test-2: value2
97+
< X-Test-3:
98+
< X-Test-4:
99+
< accept-ranges: none
100+
< content-length: 13
101+
< content-type: text/plain
102+
*/
103+
});
104+
82105
server.begin();
83106
}
84107

src/AsyncWebHeader.cpp

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,32 @@
33

44
#include <ESPAsyncWebServer.h>
55

6-
AsyncWebHeader::AsyncWebHeader(const String &data) {
6+
const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
7+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
8+
// In HTTP/1.X, a header is a case-insensitive name followed by a colon, then optional whitespace which will be ignored, and finally by its value
79
if (!data) {
8-
return;
10+
return AsyncWebHeader(); // nullptr
911
}
10-
int index = data.indexOf(':');
11-
if (index < 0) {
12-
return;
12+
if (data[0] == '\0') {
13+
return AsyncWebHeader(); // empty string
1314
}
14-
if (data.indexOf('\r') >= 0 || data.indexOf('\n') >= 0) {
15-
// Note: do not log as info, warn or error because this could flood the logs without being able to filter this out
16-
#ifdef ESP32
17-
log_v("Invalid character in HTTP header");
18-
#endif
19-
return; // Invalid header format
15+
if (strchr(data, '\n') || strchr(data, '\r')) {
16+
return AsyncWebHeader(); // Invalid header format
2017
}
21-
_name = data.substring(0, index);
22-
_value = data.substring(index + 2);
23-
}
24-
25-
String AsyncWebHeader::toString() const {
26-
String str;
27-
if (str.reserve(_name.length() + _value.length() + 2)) {
28-
str.concat(_name);
29-
str.concat((char)0x3a);
30-
str.concat((char)0x20);
31-
str.concat(_value);
32-
str.concat(asyncsrv::T_rn);
33-
} else {
34-
#ifdef ESP32
35-
log_e("Failed to allocate");
36-
#endif
18+
char *colon = strchr(data, ':');
19+
if (!colon) {
20+
return AsyncWebHeader(); // separator not found
21+
}
22+
if (colon == data) {
23+
return AsyncWebHeader(); // Header name cannot be empty
24+
}
25+
char *startOfValue = colon + 1; // Skip the colon
26+
// skip one optional whitespace after the colon
27+
if (*startOfValue == ' ') {
28+
startOfValue++;
3729
}
38-
return str;
30+
String name;
31+
name.reserve(colon - data);
32+
name.concat(data, colon - data);
33+
return AsyncWebHeader(name, String(startOfValue));
3934
}

src/ESPAsyncWebServer.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,39 @@ class AsyncWebHeader {
135135
String _value;
136136

137137
public:
138+
AsyncWebHeader() {}
138139
AsyncWebHeader(const AsyncWebHeader &) = default;
140+
AsyncWebHeader(AsyncWebHeader &&) = default;
139141
AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
140142
AsyncWebHeader(const String &name, const String &value) : _name(name), _value(value) {}
141-
AsyncWebHeader(const String &data);
143+
144+
#ifndef ESP8266
145+
[[deprecated("Use AsyncWebHeader::parse(data) instead")]]
146+
#endif
147+
AsyncWebHeader(const String &data)
148+
: AsyncWebHeader(parse(data)){};
142149

143150
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
151+
AsyncWebHeader &operator=(AsyncWebHeader &&other) = default;
144152

145153
const String &name() const {
146154
return _name;
147155
}
148156
const String &value() const {
149157
return _value;
150158
}
159+
151160
String toString() const;
161+
162+
// returns true if the header is valid
163+
operator bool() const {
164+
return _name.length();
165+
}
166+
167+
static const AsyncWebHeader parse(const String &data) {
168+
return parse(data.c_str());
169+
}
170+
static const AsyncWebHeader parse(const char *data);
152171
};
153172

154173
/*
@@ -1038,6 +1057,10 @@ class AsyncWebServerResponse {
10381057
setContentType(type.c_str());
10391058
}
10401059
void setContentType(const char *type);
1060+
bool addHeader(AsyncWebHeader &&header, bool replaceExisting = true);
1061+
bool addHeader(const AsyncWebHeader &header, bool replaceExisting = true) {
1062+
return header && addHeader(header.name(), header.value(), replaceExisting);
1063+
}
10411064
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
10421065
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
10431066
return addHeader(name.c_str(), value.c_str(), replaceExisting);

src/WebRequest.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,10 @@ bool AsyncWebServerRequest::_parseReqHead() {
341341
}
342342

343343
bool AsyncWebServerRequest::_parseReqHeader() {
344-
int index = _temp.indexOf(':');
345-
if (index) {
346-
String name(_temp.substring(0, index));
347-
String value(_temp.substring(index + 2));
344+
AsyncWebHeader header = AsyncWebHeader::parse(_temp);
345+
if (header) {
346+
const String &name = header.name();
347+
const String &value = header.value();
348348
if (name.equalsIgnoreCase(T_Host)) {
349349
_host = value;
350350
} else if (name.equalsIgnoreCase(T_Content_Type)) {
@@ -392,7 +392,7 @@ bool AsyncWebServerRequest::_parseReqHeader() {
392392
_reqconntype = RCT_EVENT;
393393
}
394394
}
395-
_headers.emplace_back(name, value);
395+
_headers.emplace_back(std::move(header));
396396
}
397397
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
398398
// Ancient PRI core does not have String::clear() method 8-()

src/WebResponses.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,30 @@ bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
134134
return false;
135135
}
136136

137+
bool AsyncWebServerResponse::addHeader(AsyncWebHeader &&header, bool replaceExisting) {
138+
if (!header) {
139+
return false; // invalid header
140+
}
141+
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
142+
if (i->name().equalsIgnoreCase(header.name())) {
143+
// header already set
144+
if (replaceExisting) {
145+
// remove, break and add the new one
146+
_headers.erase(i);
147+
break;
148+
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
149+
// do not update
150+
return false;
151+
} else {
152+
break; // accept multiple headers with the same name
153+
}
154+
}
155+
}
156+
// header was not found found, or existing one was removed
157+
_headers.emplace_back(std::move(header));
158+
return true;
159+
}
160+
137161
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
138162
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
139163
if (i->name().equalsIgnoreCase(name)) {

0 commit comments

Comments
 (0)