diff --git a/examples/HeaderManipulation/HeaderManipulation.ino b/examples/HeaderManipulation/HeaderManipulation.ino index cc1c1c1f9..5b4c9f7b3 100644 --- a/examples/HeaderManipulation/HeaderManipulation.ino +++ b/examples/HeaderManipulation/HeaderManipulation.ino @@ -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(); } diff --git a/src/AsyncWebHeader.cpp b/src/AsyncWebHeader.cpp index 6fbc82641..6e659e96b 100644 --- a/src/AsyncWebHeader.cpp +++ b/src/AsyncWebHeader.cpp @@ -3,37 +3,32 @@ #include -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)); } diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index ae45d131f..48569a5c0 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -135,12 +135,20 @@ 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; @@ -148,7 +156,18 @@ class AsyncWebHeader { 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); }; /* @@ -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); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 1227f325c..3e651d636 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -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)) { @@ -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-() diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 3de8f329e..862ba19d1 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -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)) {