Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions examples/HeaderManipulation/HeaderManipulation.ino
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@ void setup() {
)
.addMiddleware(&headerFree);

// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello, world!");
response->addHeader(AsyncWebHeader::parse("X-Test-1: value1"));
response->addHeader(AsyncWebHeader::parse("X-Test-2:value2"));
response->addHeader(AsyncWebHeader::parse("X-Test-3:"));
response->addHeader(AsyncWebHeader::parse("X-Test-4: "));
response->addHeader(AsyncWebHeader::parse(""));
response->addHeader(AsyncWebHeader::parse(":"));
request->send(response);
/**
< HTTP/1.1 200 OK
< connection: close
< X-Test-1: value1
< X-Test-2: value2
< X-Test-3:
< X-Test-4:
< accept-ranges: none
< content-length: 13
< content-type: text/plain
*/
});

server.begin();
}

Expand Down
51 changes: 23 additions & 28 deletions src/AsyncWebHeader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,32 @@

#include <ESPAsyncWebServer.h>

AsyncWebHeader::AsyncWebHeader(const String &data) {
const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
// 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
if (!data) {
return;
return AsyncWebHeader(); // nullptr
}
int index = data.indexOf(':');
if (index < 0) {
return;
if (data[0] == '\0') {
return AsyncWebHeader(); // empty string
}
if (data.indexOf('\r') >= 0 || data.indexOf('\n') >= 0) {
// Note: do not log as info, warn or error because this could flood the logs without being able to filter this out
#ifdef ESP32
log_v("Invalid character in HTTP header");
#endif
return; // Invalid header format
if (strchr(data, '\n') || strchr(data, '\r')) {
return AsyncWebHeader(); // Invalid header format
}
_name = data.substring(0, index);
_value = data.substring(index + 2);
}

String AsyncWebHeader::toString() const {
String str;
if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
char *colon = strchr(data, ':');
if (!colon) {
return AsyncWebHeader(); // separator not found
}
if (colon == data) {
return AsyncWebHeader(); // Header name cannot be empty
}
char *startOfValue = colon + 1; // Skip the colon
// skip one optional whitespace after the colon
if (*startOfValue == ' ') {
startOfValue++;
}
return str;
String name;
name.reserve(colon - data);
name.concat(data, colon - data);
return AsyncWebHeader(name, String(startOfValue));
}
25 changes: 24 additions & 1 deletion src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,39 @@ class AsyncWebHeader {
String _value;

public:
AsyncWebHeader() {}
AsyncWebHeader(const AsyncWebHeader &) = default;
AsyncWebHeader(AsyncWebHeader &&) = default;
AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
AsyncWebHeader(const String &name, const String &value) : _name(name), _value(value) {}
AsyncWebHeader(const String &data);

#ifndef ESP8266
[[deprecated("Use AsyncWebHeader::parse(data) instead")]]
#endif
AsyncWebHeader(const String &data)
: AsyncWebHeader(parse(data)){};

AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
AsyncWebHeader &operator=(AsyncWebHeader &&other) = default;

const String &name() const {
return _name;
}
const String &value() const {
return _value;
}

String toString() const;

// returns true if the header is valid
operator bool() const {
return _name.length();
}

static const AsyncWebHeader parse(const String &data) {
return parse(data.c_str());
}
static const AsyncWebHeader parse(const char *data);
};

/*
Expand Down Expand Up @@ -1038,6 +1057,10 @@ class AsyncWebServerResponse {
setContentType(type.c_str());
}
void setContentType(const char *type);
bool addHeader(AsyncWebHeader &&header, bool replaceExisting = true);
bool addHeader(const AsyncWebHeader &header, bool replaceExisting = true) {
return header && addHeader(header.name(), header.value(), replaceExisting);
}
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
return addHeader(name.c_str(), value.c_str(), replaceExisting);
Expand Down
10 changes: 5 additions & 5 deletions src/WebRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,10 @@ bool AsyncWebServerRequest::_parseReqHead() {
}

bool AsyncWebServerRequest::_parseReqHeader() {
int index = _temp.indexOf(':');
if (index) {
String name(_temp.substring(0, index));
String value(_temp.substring(index + 2));
AsyncWebHeader header = AsyncWebHeader::parse(_temp);
if (header) {
const String &name = header.name();
const String &value = header.value();
if (name.equalsIgnoreCase(T_Host)) {
_host = value;
} else if (name.equalsIgnoreCase(T_Content_Type)) {
Expand Down Expand Up @@ -392,7 +392,7 @@ bool AsyncWebServerRequest::_parseReqHeader() {
_reqconntype = RCT_EVENT;
}
}
_headers.emplace_back(name, value);
_headers.emplace_back(std::move(header));
}
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
// Ancient PRI core does not have String::clear() method 8-()
Expand Down
24 changes: 24 additions & 0 deletions src/WebResponses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,30 @@ bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
return false;
}

bool AsyncWebServerResponse::addHeader(AsyncWebHeader &&header, bool replaceExisting) {
if (!header) {
return false; // invalid header
}
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(header.name())) {
// header already set
if (replaceExisting) {
// remove, break and add the new one
_headers.erase(i);
break;
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update
return false;
} else {
break; // accept multiple headers with the same name
}
}
}
// header was not found found, or existing one was removed
_headers.emplace_back(std::move(header));
return true;
}

bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
Expand Down