diff --git a/Sming/Core/Network/Http/HttpHeaderFields.cpp b/Sming/Core/Network/Http/HttpHeaderFields.cpp index 66d5c2f39f..5096b576ed 100644 --- a/Sming/Core/Network/Http/HttpHeaderFields.cpp +++ b/Sming/Core/Network/Http/HttpHeaderFields.cpp @@ -14,14 +14,28 @@ #include // Define field name strings and a lookup table -#define XX(_tag, _str, _comment) DEFINE_FSTR_LOCAL(hhfnStr_##_tag, _str); +#define XX(tag, str, flags, comment) DEFINE_FSTR_LOCAL(hhfnStr_##tag, str); HTTP_HEADER_FIELDNAME_MAP(XX) #undef XX -#define XX(_tag, _str, _comment) &hhfnStr_##_tag, +#define XX(tag, str, flags, comment) &hhfnStr_##tag, DEFINE_FSTR_VECTOR_LOCAL(fieldNameStrings, FlashString, HTTP_HEADER_FIELDNAME_MAP(XX)); #undef XX +HttpHeaderFields::Flags HttpHeaderFields::getFlags(HttpHeaderFieldName name) const +{ + switch(name) { +#define XX(tag, str, flags, comment) \ + case HttpHeaderFieldName::tag: \ + return flags; + HTTP_HEADER_FIELDNAME_MAP(XX) +#undef XX + default: + // Custom fields + return 0; + } +} + String HttpHeaderFields::toString(HttpHeaderFieldName name) const { if(name == HTTP_HEADER_UNKNOWN) { @@ -35,6 +49,26 @@ String HttpHeaderFields::toString(HttpHeaderFieldName name) const return customFieldNames[unsigned(name) - unsigned(HTTP_HEADER_CUSTOM)]; } +String HttpHeaderFields::toString(HttpHeaderFieldName name, const String& value) const +{ + String tag = toString(name); + if(getFlags(name)[Flag::Multi]) { + tag += ": "; + CStringArray values(value); + String s; + s.reserve((tag.length() + 2) * values.count() + value.length()); + for(auto p : values) { + s += tag; + s += p; + s += "\r\n"; + } + + return s; + } + + return toString(tag, value); +} + String HttpHeaderFields::toString(const String& name, const String& value) { String s; diff --git a/Sming/Core/Network/Http/HttpHeaderFields.h b/Sming/Core/Network/Http/HttpHeaderFields.h index de05acd30b..2ec1c14431 100644 --- a/Sming/Core/Network/Http/HttpHeaderFields.h +++ b/Sming/Core/Network/Http/HttpHeaderFields.h @@ -14,6 +14,7 @@ #include "Data/CStringArray.h" #include "WString.h" +#include /* * Common HTTP header field names. Enumerating these simplifies matching @@ -25,68 +26,91 @@ * A brief description of each header field is given for information purposes. * For details see https://www.iana.org/assignments/message-headers/message-headers.xhtml * + * Entries are formatted thus: XX(tag, str, flags, comment) + * tag: Identifier used in code (without HTTP_HEADER_ prefix) + * str: String used in HTTP protocol + * flags: Additional flags (see HttpHeaderFields::Flag) + * comment: Further details */ #define HTTP_HEADER_FIELDNAME_MAP(XX) \ - XX(ACCEPT, "Accept", "Limit acceptable response types") \ - XX(ACCESS_CONTROL_ALLOW_ORIGIN, "Access-Control-Allow-Origin", "") \ - XX(AUTHORIZATION, "Authorization", "Basic user agent authentication") \ - XX(CC, "Cc", "email field") \ - XX(CONNECTION, "Connection", "Indicates sender's desired control options") \ - XX(CONTENT_DISPOSITION, "Content-Disposition", "Additional information about how to process response payload") \ - XX(CONTENT_ENCODING, "Content-Encoding", "Applied encodings in addition to content type") \ - XX(CONTENT_LENGTH, "Content-Length", "Anticipated size for payload when not using transfer encoding") \ - XX(CONTENT_TYPE, "Content-Type", \ + XX(ACCEPT, "Accept", 0, "Limit acceptable response types") \ + XX(ACCESS_CONTROL_ALLOW_ORIGIN, "Access-Control-Allow-Origin", 0, "") \ + XX(AUTHORIZATION, "Authorization", 0, "Basic user agent authentication") \ + XX(CC, "Cc", 0, "email field") \ + XX(CONNECTION, "Connection", 0, "Indicates sender's desired control options") \ + XX(CONTENT_DISPOSITION, "Content-Disposition", 0, "Additional information about how to process response payload") \ + XX(CONTENT_ENCODING, "Content-Encoding", 0, "Applied encodings in addition to content type") \ + XX(CONTENT_LENGTH, "Content-Length", 0, "Anticipated size for payload when not using transfer encoding") \ + XX(CONTENT_TYPE, "Content-Type", 0, \ "Payload media type indicating both data format and intended manner of processing by recipient") \ - XX(CONTENT_TRANSFER_ENCODING, "Content-Transfer-Encoding", "Coding method used in a MIME message body part") \ - XX(CACHE_CONTROL, "Cache-Control", "Directives for caches along the request/response chain") \ - XX(DATE, "Date", "Message originating date/time") \ - XX(EXPECT, "Expect", "Behaviours to be supported by the server in order to properly handle this request.") \ - XX(ETAG, "ETag", \ + XX(CONTENT_TRANSFER_ENCODING, "Content-Transfer-Encoding", 0, "Coding method used in a MIME message body part") \ + XX(CACHE_CONTROL, "Cache-Control", 0, "Directives for caches along the request/response chain") \ + XX(DATE, "Date", 0, "Message originating date/time") \ + XX(EXPECT, "Expect", 0, "Behaviours to be supported by the server in order to properly handle this request.") \ + XX(ETAG, "ETag", 0, \ "Validates resource, such as a file, so recipient can confirm whether it has changed - generally more " \ "reliable than Date") \ - XX(FROM, "From", "email address of human user who controls the requesting user agent") \ - XX(HOST, "Host", \ + XX(FROM, "From", 0, "email address of human user who controls the requesting user agent") \ + XX(HOST, "Host", 0, \ "Request host and port information for target URI; allows server to service requests for multiple hosts on a " \ "single IP address") \ - XX(IF_MATCH, "If-Match", \ + XX(IF_MATCH, "If-Match", 0, \ "Precondition check using ETag to avoid accidental overwrites when servicing multiple user requests. Ensures " \ "resource entity tag matches before proceeding.") \ - XX(IF_MODIFIED_SINCE, "If-Modified-Since", "Precondition check using Date") \ - XX(LAST_MODIFIED, "Last-Modified", "Server timestamp indicating date and time resource was last modified") \ - XX(LOCATION, "Location", "Used in redirect responses, amongst other places") \ - XX(SEC_WEBSOCKET_ACCEPT, "Sec-WebSocket-Accept", "Server response to opening Websocket handshake") \ - XX(SEC_WEBSOCKET_VERSION, "Sec-WebSocket-Version", \ + XX(IF_MODIFIED_SINCE, "If-Modified-Since", 0, "Precondition check using Date") \ + XX(LAST_MODIFIED, "Last-Modified", 0, "Server timestamp indicating date and time resource was last modified") \ + XX(LOCATION, "Location", 0, "Used in redirect responses, amongst other places") \ + XX(SEC_WEBSOCKET_ACCEPT, "Sec-WebSocket-Accept", 0, "Server response to opening Websocket handshake") \ + XX(SEC_WEBSOCKET_VERSION, "Sec-WebSocket-Version", 0, \ "Websocket opening request indicates acceptable protocol version. Can appear more than once.") \ - XX(SEC_WEBSOCKET_KEY, "Sec-WebSocket-Key", "Websocket opening request validation key") \ - XX(SEC_WEBSOCKET_PROTOCOL, "Sec-WebSocket-Protocol", \ + XX(SEC_WEBSOCKET_KEY, "Sec-WebSocket-Key", 0, "Websocket opening request validation key") \ + XX(SEC_WEBSOCKET_PROTOCOL, "Sec-WebSocket-Protocol", 0, \ "Websocket opening request indicates supported protocol(s), response contains negotiated protocol(s)") \ - XX(SERVER, "Server", "Identifies software handling requests") \ - XX(SET_COOKIE, "Set-Cookie", "Server may pass name/value pairs and associated metadata to user agent (client)") \ - XX(SUBJECT, "Subject", "email subject line") \ - XX(TO, "To", "email intended recipient address") \ - XX(TRANSFER_ENCODING, "Transfer-Encoding", "e.g. Chunked, compress, deflate, gzip") \ - XX(UPGRADE, "Upgrade", \ + XX(SERVER, "Server", 0, "Identifies software handling requests") \ + XX(SET_COOKIE, "Set-Cookie", Flag::Multi, \ + "Server may pass name/value pairs and associated metadata to user agent (client)") \ + XX(SUBJECT, "Subject", 0, "email subject line") \ + XX(TO, "To", 0, "email intended recipient address") \ + XX(TRANSFER_ENCODING, "Transfer-Encoding", 0, "e.g. Chunked, compress, deflate, gzip") \ + XX(UPGRADE, "Upgrade", 0, \ "Used to transition from HTTP to some other protocol on the same connection. e.g. Websocket") \ - XX(USER_AGENT, "User-Agent", "Information about the user agent originating the request") \ - XX(WWW_AUTHENTICATE, "WWW-Authenticate", "Indicates HTTP authentication scheme(s) and applicable parameters") + XX(USER_AGENT, "User-Agent", 0, "Information about the user agent originating the request") \ + XX(WWW_AUTHENTICATE, "WWW-Authenticate", Flag::Multi, \ + "Indicates HTTP authentication scheme(s) and applicable parameters") \ + XX(PROXY_AUTHENTICATE, "Proxy-Authenticate", Flag::Multi, \ + "Indicates proxy authentication scheme(s) and applicable parameters") enum class HttpHeaderFieldName { UNKNOWN = 0, -#define XX(tag, str, comment) tag, +#define XX(tag, str, flags, comment) tag, HTTP_HEADER_FIELDNAME_MAP(XX) #undef XX CUSTOM // First custom header tag value }; -#define XX(tag, str, comment) constexpr HttpHeaderFieldName HTTP_HEADER_##tag = HttpHeaderFieldName::tag; -XX(UNKNOWN, "", "") +#define XX(tag, str, flags, comment) constexpr HttpHeaderFieldName HTTP_HEADER_##tag = HttpHeaderFieldName::tag; +XX(UNKNOWN, "", 0, "") HTTP_HEADER_FIELDNAME_MAP(XX) -XX(CUSTOM, "", "") +XX(CUSTOM, "", 0, "") #undef XX class HttpHeaderFields { public: + /** + * @brief Flag values providing additional information about header fields + */ + enum class Flag { + Multi, ///< Field may have multiple values + }; + using Flags = BitSet; + + /** + * @brief Get flags (if any) for given header field + * @retval Flags + */ + Flags getFlags(HttpHeaderFieldName name) const; + String toString(HttpHeaderFieldName name) const; /** @brief Produce a string for output in the HTTP header, with line ending @@ -96,10 +120,7 @@ class HttpHeaderFields */ static String toString(const String& name, const String& value); - String toString(HttpHeaderFieldName name, const String& value) const - { - return toString(toString(name), value); - } + String toString(HttpHeaderFieldName name, const String& value) const; /** @brief Find the enumerated value for the given field name string * @param name diff --git a/Sming/Core/Network/Http/HttpHeaders.cpp b/Sming/Core/Network/Http/HttpHeaders.cpp new file mode 100644 index 0000000000..9f98985693 --- /dev/null +++ b/Sming/Core/Network/Http/HttpHeaders.cpp @@ -0,0 +1,48 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpHeaders.cpp + * + ****/ + +#include "HttpHeaders.h" +#include + +const String& HttpHeaders::operator[](const String& name) const +{ + auto field = fromString(name); + if(field == HTTP_HEADER_UNKNOWN) { + return nil; + } + return operator[](field); +} + +bool HttpHeaders::append(const HttpHeaderFieldName& name, const String& value) +{ + int i = indexOf(name); + if(i < 0) { + operator[](name) = value; + return true; + } + + if(!getFlags(name)[Flag::Multi]) { + debug_w("[HTTP] Append not supported for header field '%s'", toString(name).c_str()); + return false; + } + + valueAt(i) += '\0' + value; + + return true; +} + +void HttpHeaders::setMultiple(const HttpHeaders& headers) +{ + for(unsigned i = 0; i < headers.count(); i++) { + HttpHeaderFieldName fieldName = headers.keyAt(i); + auto fieldNameString = headers.toString(fieldName); + operator[](fieldNameString) = headers.valueAt(i); + } +} diff --git a/Sming/Core/Network/Http/HttpHeaders.h b/Sming/Core/Network/Http/HttpHeaders.h index 9c51d85857..0c3fd579e4 100644 --- a/Sming/Core/Network/Http/HttpHeaders.h +++ b/Sming/Core/Network/Http/HttpHeaders.h @@ -48,14 +48,7 @@ class HttpHeaders : public HttpHeaderFields, private HashMap