Arduino DLNA Server
Loading...
Searching...
No Matches
HttpHeader.h
Go to the documentation of this file.
1#pragma once
2
3// #include "Platform/AltClient.h"
4#include "HttpLineReader.h"
5#include "basic/Url.h"
6#include "basic/List.h"
7#include "basic/Logger.h"
8#include "basic/Str.h"
9
10namespace tiny_dlna {
11
12// Class Configuration
13const int MaxHeaderLineLength = 200;
14
15// Define relevant header content
16const char* CONTENT_TYPE = "Content-Type";
17const char* CONTENT_LENGTH = "Content-Length";
18const char* CONNECTION = "Connection";
19const char* CON_CLOSE = "close";
20const char* CON_KEEP_ALIVE = "keep-alive";
21const char* TRANSFER_ENCODING = "Transfer-Encoding";
22const char* CHUNKED = "chunked";
23const char* ACCEPT = "Accept";
24const char* ACCEPT_ALL = "*/*";
25const char* SUCCESS = "Success";
26const char* USER_AGENT = "User-Agent";
27const char* DEFAULT_AGENT =
28 "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)";
29const char* HOST_C = "Host";
30const char* ACCEPT_ENCODING = "Accept-Encoding";
31const char* IDENTITY = "identity";
32const char* LOCATION = "Location";
33
34// Http methods
49};
50const char* methods[] = {"?", "GET", "HEAD", "POST",
51 "PUT", "DELETE", "TRACE", "OPTIONS",
52 "CONNECT", "PATCH", "UNSUBSCRIBE", "SUBSCRIBE", "NOTIFY", nullptr};
53
61 bool active;
62};
63
72 public:
74 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader");
75 // set default values
76 protocol_str = "HTTP/1.1";
77 url_path = "/";
78 status_msg = "";
79 }
81 DlnaLogger.log(DlnaLogLevel::Debug, "~HttpHeader");
82 clear(false);
83 }
84
86 HttpHeader& clear(bool activeFlag = true) {
87 is_written = false;
88 is_chunked = false;
89 url_path = "/";
90 for (auto it = lines.begin(); it != lines.end(); ++it) {
91 if (it != nullptr) {
92 if (activeFlag) {
93 (*it)->active = false;
94 } else {
95 delete *it;
96 }
97 }
98 }
99 if (!activeFlag) {
100 lines.clear();
101 }
102 return *this;
103 }
104
105 HttpHeader& put(const char* key, const char* value) {
106 if (value != nullptr) {
107 HttpHeaderLine* hl = headerLine(key);
108 if (hl == nullptr) {
109 DlnaLogger.log(DlnaLogLevel::Error,
110 "HttpHeader::put - did not add HttpHeaderLine for %s",
111 key);
112 return *this;
113 }
114
115 // log entry
116 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put '%s' : %s", key, value);
117 hl->value = value;
118 hl->active = true;
119
120 if (StrView(key) == TRANSFER_ENCODING && StrView(value) == CHUNKED) {
121 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put -> is_chunked!!!");
122 this->is_chunked = true;
123 }
124 } else {
125 DlnaLogger.log(
126 DlnaLogLevel::Debug, "HttpHeader::put - value ignored because it is null for %s",
127 key);
128 }
129 return *this;
130 }
131
133 HttpHeader& put(const char* key, int value) {
134 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put %s %d", key, value);
135 HttpHeaderLine* hl = headerLine(key);
136
137 // add value
138 hl->value = value;
139 hl->active = true;
140 DlnaLogger.log(DlnaLogLevel::Debug, "%s: %s", key, hl->value.c_str());
141 return *this;
142 }
143
145 HttpHeader& put(const char* line) {
146 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put -> %s", (const char*)line);
147 StrView keyStr(line);
148 int pos = keyStr.indexOf(":");
149 char* key = (char*)line;
150 key[pos] = 0;
151
152 // usually there is a leading space - but unfurtunately not always
153 const char* value = line + pos + 1;
154 if (value[0] == ' ') {
155 value = line + pos + 2;
156 }
157 return put((const char*)key, value);
158 }
159
160 // determines a header value with the key
161 const char* get(const char* key) {
162 for (auto it = lines.begin(); it != lines.end(); ++it) {
163 HttpHeaderLine* line = *it;
164 line->key.trim();
165 if (line->key.equalsIgnoreCase(key)) {
166 const char* result = line->value.c_str();
167 return line->active ? result : nullptr;
168 }
169 }
170 return nullptr;
171 }
172
173 // reads a single header line
174 void readLine(Client& in, char* str, int len) {
175 reader.readlnInternal(in, (uint8_t*)str, len, false);
176 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::readLine -> %s", str);
177 }
178
179 // writes a lingle header line
180 void writeHeaderLine(Client& out, HttpHeaderLine* header) {
181 if (header == nullptr) {
182 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::writeHeaderLine",
183 "the value must not be null");
184 return;
185 }
186 if (!header->active) {
187 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::writeHeaderLine %s - not active",
188 header->key.c_str());
189 return;
190 }
191 if (header->value.c_str() == nullptr) {
192 DlnaLogger.log(
194 "HttpHeader::writeHeaderLine - ignored because value is null");
195 return;
196 }
197
198 char msg[400];
199 StrView msg_str(msg, 400);
200 msg_str = header->key.c_str();
201 msg_str += ": ";
202 msg_str += header->value.c_str();
203 msg_str += CRLF;
204 out.print(msg);
205
206 // remove crlf from log
207 int len = strnlen(msg, 200);
208 msg[len - 2] = 0;
209 DlnaLogger.log(DlnaLogLevel::Debug, "writeHeaderLine -> %s", msg);
210
211 // marke as processed
212 header->active = false;
213 }
214
215 const char* urlPath() { return url_path.c_str(); }
216
217 const char* protocol() { return protocol_str.c_str(); }
218
220
221 const char* accept() { return get(ACCEPT); }
222
223 int statusCode() { return status_code; }
224
225 const char* statusMessage() { return status_msg.c_str(); }
226
227 bool isChunked() {
228 // the value is automatically set from the reply
229 return is_chunked;
230 }
231
232 // reads the full header from the request (stream)
233 void read(Client& in) {
234 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::read");
235 // remove all existing value
236 clear();
237
238 char line[MaxHeaderLineLength];
239 if (in.connected()) {
240 delay(200);
241 if (in.available() == 0) {
242 DlnaLogger.log(DlnaLogLevel::Warning, "Waiting for data...");
243 uint64_t end = millis() + in.getTimeout();
244 while (in.available() == 0) {
245 delay(100);
246 if (millis()>end) break;
247 }
248 }
249 readLine(in, line, MaxHeaderLineLength);
250 parse1stLine(line);
251 while (in.available()) {
252 readLine(in, line, MaxHeaderLineLength);
253 StrView lineStr(line);
254 if (lineStr.isEmpty() || lineStr.isNewLine()) {
255 break;
256 }
257 // Always add header lines for requests and replies. Previously this
258 // only added lines for valid/redirect reply statuses which caused
259 // request headers (e.g. CALLBACK for SUBSCRIBE) to be ignored.
260 lineStr.ltrim();
261 put(line);
262 }
263 }
264 }
265
266 // writes the full header to the indicated stream
267 void write(Client& out) {
268 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::write");
269 write1stLine(out);
270 for (auto it = lines.begin(); it != lines.end(); ++it) {
271 writeHeaderLine(out, *it);
272 }
273 // print empty line
274 crlf(out);
275 out.flush();
276 is_written = true;
277 }
278
279 // automatically create new lines
280 void setAutoCreateLines(bool is_auto_line) {
281 create_new_lines = is_auto_line;
282 }
284 bool isValidStatus() { return status_code >= 200 && status_code < 300; }
285
286 bool isRedirectStatus() { return status_code >= 300 && status_code < 400; }
287
288 protected:
290 bool is_written = false;
291 bool is_chunked = false;
292 bool create_new_lines = true;
299 const char* CRLF = "\r\n";
300
301 // the headers need to delimited with CR LF
302 void crlf(Client& out) {
303 out.println();
304 DlnaLogger.log(DlnaLogLevel::Debug, " -> %s", "<CR LF>");
305 }
306
307 // gets or creates a header line by key
308 HttpHeaderLine* headerLine(const char* key) {
309 if (key != nullptr) {
310 for (auto it = lines.begin(); it != lines.end(); ++it) {
311 HttpHeaderLine* pt = (*it);
312 if (pt != nullptr && pt->key.c_str() != nullptr) {
313 if (pt->key.equalsIgnoreCase(key)) {
314 pt->active = true;
315 return pt;
316 }
317 }
318 }
319 if (create_new_lines) {
320 HttpHeaderLine* newLine = new HttpHeaderLine();
321 DlnaLogger.log(DlnaLogLevel::Debug,
322 "HttpHeader::headerLine - new line created for %s", key);
323 newLine->active = true;
324 newLine->key = key;
325 lines.push_back(newLine);
326 return newLine;
327 }
328 } else {
329 DlnaLogger.log(DlnaLogLevel::Error, "HttpHeader::headerLine",
330 "The key must not be null");
331 }
332 return nullptr;
333 }
334
335 TinyMethodID getMethod(const char* line) {
336 // set action
337 for (int j = 0; methods[j] != nullptr; j++) {
338 const char* action = methods[j];
339 int len = strlen(action);
340 if (strncmp(action, line, len) == 0) {
341 return (TinyMethodID)j;
342 }
343 }
344 return (TinyMethodID)0;
345 }
346
347 virtual void write1stLine(Client& out) = 0;
348 virtual void parse1stLine(const char* line) = 0;
349};
350
356 public:
357 // Defines the action id, url path and http version for an request
359 const char* protocol = nullptr) {
360 this->method_id = id;
361 this->url_path = urlPath;
362
363 DlnaLogger.log(DlnaLogLevel::Debug, "HttpRequestHeader::setValues - path: %s",
364 this->url_path.c_str());
365 if (protocol != nullptr) {
366 this->protocol_str = protocol;
367 }
368 return *this;
369 }
370
371 // action path protocol
372 void write1stLine(Client& out) {
373 char msg[201] = {0};
374
375 const char* method_str = methods[this->method_id];
376 strncat(msg, method_str, 200);
377 strncat(msg, " ", 200);
378 strncat(msg, this->url_path.c_str(), 200);
379 strncat(msg, " ", 200);
380 strncat(msg, this->protocol_str.c_str(), 200);
381 strncat(msg, CRLF, 200);
382 out.print(msg);
383 DlnaLogger.log(DlnaLogLevel::Debug, "HttpRequestHeader::write1stLine: %s", msg);
384 }
385
386 // parses the requestline
387 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
388 void parse1stLine(const char* line) {
389 DlnaLogger.log(DlnaLogLevel::Debug, "HttpRequestHeader::parse1stLine %s", line);
390 StrView line_str(line);
391 int space1 = line_str.indexOf(" ");
392 int space2 = line_str.indexOf(" ", space1 + 1);
393
394 this->method_id = getMethod(line);
395 this->protocol_str.substrView(line_str, space2 + 1, line_str.length());
396 this->url_path.substrView(line_str, space1 + 1, space2);
397 this->url_path.trim();
398
399 DlnaLogger.log(DlnaLogLevel::Debug, "->method: %s", methods[this->method_id]);
400 DlnaLogger.log(DlnaLogLevel::Debug, "->protocol: %s", protocol_str.c_str());
401 DlnaLogger.log(DlnaLogLevel::Debug, "->url_path: %s", url_path.c_str());
402 }
403};
404
410 public:
411 // defines the values for the rely
412 void setValues(int statusCode, const char* msg = "",
413 const char* protocol = nullptr) {
414 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::setValues %d", statusCode);
415 status_msg = msg;
417 if (protocol != nullptr) {
418 this->protocol_str = protocol;
419 }
420 }
421
422 // reads the final chunked reply headers
423 void readExt(Client& in) {
424 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::readExt");
425 char line[MaxHeaderLineLength];
426 readLine(in, line, MaxHeaderLineLength);
427 while (strlen(line) != 0) {
428 put(line);
429 readLine(in, line, MaxHeaderLineLength);
430 }
431 }
432
433 // HTTP-Version SP Status-Code SP Reason-Phrase CRLF
434 void write1stLine(Client& out) {
435 char msg[200];
436 StrView msg_str(msg, 200);
437 msg_str = this->protocol_str.c_str();
438 msg_str += " ";
439 msg_str += this->status_code;
440 msg_str += " ";
441 msg_str += this->status_msg.c_str();
442 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::write1stLine: %s", msg);
443 out.print(msg);
444 crlf(out);
445 }
446
447 // HTTP-Version SP Status-Code SP Reason-Phrase CRLF
448 // we just update the pointers to point to the correct position in the
449 // http_status_line
450 void parse1stLine(const char* line) {
451 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::parse1stLine %s", line);
452 StrView line_str(line);
453 int space1 = line_str.indexOf(' ', 0);
454 int space2 = line_str.indexOf(' ', space1 + 1);
455
456 // save http version
457 protocol_str.substrView(line_str, 0, space1);
458
459 // find response status code after the first space
460 char status_c[6];
461 StrView status(status_c, 6);
462 status.substrView(line_str, space1 + 1, space2);
463 status_code = atoi(status_c);
464
465 // get reason-phrase after last SP
466 status_msg.substrView(line_str, space2 + 1, line_str.length());
467 }
468};
469
470} // namespace tiny_dlna
In a http request and reply we need to process header Debugrmation. With this API we can define and q...
Definition: HttpHeader.h:71
List< HttpHeaderLine * > lines
Definition: HttpHeader.h:297
virtual void parse1stLine(const char *line)=0
const char * statusMessage()
Definition: HttpHeader.h:225
void writeHeaderLine(Client &out, HttpHeaderLine *header)
Definition: HttpHeader.h:180
const char * accept()
Definition: HttpHeader.h:221
bool isRedirectStatus()
Definition: HttpHeader.h:286
void read(Client &in)
Definition: HttpHeader.h:233
HttpHeader & clear(bool activeFlag=true)
clears the data - usually we do not delete but we just set the active flag
Definition: HttpHeader.h:86
HttpHeaderLine * headerLine(const char *key)
Definition: HttpHeader.h:308
int statusCode()
Definition: HttpHeader.h:223
HttpHeader & put(const char *key, int value)
adds a new line to the header - e.g. for content size
Definition: HttpHeader.h:133
const char * CRLF
Definition: HttpHeader.h:299
TinyMethodID method()
Definition: HttpHeader.h:219
TinyMethodID method_id
Definition: HttpHeader.h:293
HttpHeader & put(const char *line)
adds a received new line to the header
Definition: HttpHeader.h:145
TinyMethodID getMethod(const char *line)
Definition: HttpHeader.h:335
const char * urlPath()
Definition: HttpHeader.h:215
void crlf(Client &out)
Definition: HttpHeader.h:302
HttpHeader()
Definition: HttpHeader.h:73
virtual void write1stLine(Client &out)=0
Str protocol_str
Definition: HttpHeader.h:294
~HttpHeader()
Definition: HttpHeader.h:80
const char * protocol()
Definition: HttpHeader.h:217
Str url_path
Definition: HttpHeader.h:295
bool is_written
Definition: HttpHeader.h:290
bool isChunked()
Definition: HttpHeader.h:227
void readLine(Client &in, char *str, int len)
Definition: HttpHeader.h:174
HttpLineReader reader
Definition: HttpHeader.h:298
int status_code
Definition: HttpHeader.h:289
bool isValidStatus()
returns true if status code >=200 and < 300
Definition: HttpHeader.h:284
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:105
Str status_msg
Definition: HttpHeader.h:296
void setAutoCreateLines(bool is_auto_line)
Definition: HttpHeader.h:280
bool create_new_lines
Definition: HttpHeader.h:292
bool is_chunked
Definition: HttpHeader.h:291
void write(Client &out)
Definition: HttpHeader.h:267
const char * get(const char *key)
Definition: HttpHeader.h:161
We read a single line. A terminating 0 is added to the string to make it compliant for c string funct...
Definition: HttpLineReader.h:13
virtual int readlnInternal(Stream &client, uint8_t *str, int len, bool incl_nl=true)
Definition: HttpLineReader.h:18
Reading and Writing of Http Replys.
Definition: HttpHeader.h:409
void parse1stLine(const char *line)
Definition: HttpHeader.h:450
void write1stLine(Client &out)
Definition: HttpHeader.h:434
void setValues(int statusCode, const char *msg="", const char *protocol=nullptr)
Definition: HttpHeader.h:412
void readExt(Client &in)
Definition: HttpHeader.h:423
Reading and writing of Http Requests.
Definition: HttpHeader.h:355
void write1stLine(Client &out)
Definition: HttpHeader.h:372
void parse1stLine(const char *line)
Definition: HttpHeader.h:388
HttpHeader & setValues(TinyMethodID id, const char *urlPath, const char *protocol=nullptr)
Definition: HttpHeader.h:358
Double linked list.
Definition: List.h:19
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:18
virtual void substrView(StrView &from, int start, int end)
copies a substring into the current string
Definition: StrView.h:505
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:383
virtual int indexOf(const char c, int start=0)
Definition: StrView.h:274
virtual int length()
Definition: StrView.h:380
virtual void ltrim()
remove leading spaces
Definition: StrView.h:548
virtual bool isNewLine()
Definition: StrView.h:385
Heap-backed string utility used throughout tiny_dlna.
Definition: Str.h:27
bool equalsIgnoreCase(const char *other) const
Case-insensitive equality with C-string.
Definition: Str.h:183
void trim()
Trim spaces on both ends.
Definition: Str.h:235
void substrView(StrView &from, int start, int end)
Assign substring view from StrView [start,end)
Definition: Str.h:69
const char * c_str() const
C-string pointer to internal buffer.
Definition: Str.h:88
Definition: Allocator.h:13
const char * CONTENT_TYPE
Definition: HttpHeader.h:16
const char * IDENTITY
Definition: HttpHeader.h:31
const char * DEFAULT_AGENT
Definition: HttpHeader.h:27
const char * CON_KEEP_ALIVE
Definition: HttpHeader.h:20
const int MaxHeaderLineLength
Definition: HttpHeader.h:13
const char * methods[]
Definition: HttpHeader.h:50
const char * CONTENT_LENGTH
Definition: HttpHeader.h:17
const char * CON_CLOSE
Definition: HttpHeader.h:19
const char * HOST_C
Definition: HttpHeader.h:29
TinyMethodID
Definition: HttpHeader.h:35
@ T_TRACE
Definition: HttpHeader.h:42
@ T_DELETE
Definition: HttpHeader.h:41
@ T_CONNECT
Definition: HttpHeader.h:44
@ T_UNSUBSCRIBE
Definition: HttpHeader.h:46
@ T_UNDEFINED
Definition: HttpHeader.h:36
@ T_OPTIONS
Definition: HttpHeader.h:43
@ T_SUBSCRIBE
Definition: HttpHeader.h:47
@ T_HEAD
Definition: HttpHeader.h:38
@ T_GET
Definition: HttpHeader.h:37
@ T_PUT
Definition: HttpHeader.h:40
@ T_POST
Definition: HttpHeader.h:39
@ T_PATCH
Definition: HttpHeader.h:45
@ T_NOTIFY
Definition: HttpHeader.h:48
const char * TRANSFER_ENCODING
Definition: HttpHeader.h:21
const char * SUCCESS
Definition: HttpHeader.h:25
const char * ACCEPT
Definition: HttpHeader.h:23
const char * ACCEPT_ENCODING
Definition: HttpHeader.h:30
const char * CHUNKED
Definition: HttpHeader.h:22
const char * ACCEPT_ALL
Definition: HttpHeader.h:24
const char * USER_AGENT
Definition: HttpHeader.h:26
const char * CONNECTION
Definition: HttpHeader.h:18
const char * LOCATION
Definition: HttpHeader.h:32
A individual key - value header line.
Definition: HttpHeader.h:58
bool active
Definition: HttpHeader.h:61
Str key
Definition: HttpHeader.h:59
Str value
Definition: HttpHeader.h:60