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
47};
48const char* methods[] = {"?", "GET", "HEAD", "POST",
49 "PUT", "DELETE", "TRACE", "OPTIONS",
50 "CONNECT", "PATCH", "SUBSCRIBE", nullptr};
51
59 bool active;
60};
61
70 public:
72 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader");
73 // set default values
74 protocol_str = "HTTP/1.1";
75 url_path = "/";
76 status_msg = "";
77 }
79 DlnaLogger.log(DlnaLogLevel::Debug, "~HttpHeader");
80 clear(false);
81 }
82
84 HttpHeader& clear(bool activeFlag = true) {
85 is_written = false;
86 is_chunked = false;
87 url_path = "/";
88 for (auto it = lines.begin(); it != lines.end(); ++it) {
89 if (it != nullptr) {
90 if (activeFlag) {
91 (*it)->active = false;
92 } else {
93 delete *it;
94 }
95 }
96 }
97 if (!activeFlag) {
98 lines.clear();
99 }
100 return *this;
101 }
102
103 HttpHeader& put(const char* key, const char* value) {
104 if (value != nullptr) {
105 HttpHeaderLine* hl = headerLine(key);
106 if (hl == nullptr) {
108 "HttpHeader::put - did not add HttpHeaderLine for %s",
109 key);
110 return *this;
111 }
112
113 // log entry
114 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put '%s' : %s", key, value);
115 hl->value = value;
116 hl->active = true;
117
118 if (StrView(key) == TRANSFER_ENCODING && StrView(value) == CHUNKED) {
119 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put -> is_chunked!!!");
120 this->is_chunked = true;
121 }
122 } else {
124 DlnaLogLevel::Debug, "HttpHeader::put - value ignored because it is null for %s",
125 key);
126 }
127 return *this;
128 }
129
131 HttpHeader& put(const char* key, int value) {
132 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put %s %d", key, value);
133 HttpHeaderLine* hl = headerLine(key);
134
135 if (value > 1024) {
136 DlnaLogger.log(DlnaLogLevel::Warning, "value %d is > 1024 for %s", value, key);
137 }
138
139 // add value
140 hl->value = value;
141 hl->active = true;
142 DlnaLogger.log(DlnaLogLevel::Debug, "%s: %s", key, hl->value.c_str());
143 return *this;
144 }
145
147 HttpHeader& put(const char* line) {
148 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::put -> %s", (const char*)line);
149 StrView keyStr(line);
150 int pos = keyStr.indexOf(":");
151 char* key = (char*)line;
152 key[pos] = 0;
153
154 // usually there is a leading space - but unfurtunately not always
155 const char* value = line + pos + 1;
156 if (value[0] == ' ') {
157 value = line + pos + 2;
158 }
159 return put((const char*)key, value);
160 }
161
162 // determines a header value with the key
163 const char* get(const char* key) {
164 for (auto it = lines.begin(); it != lines.end(); ++it) {
165 HttpHeaderLine* line = *it;
166 line->key.trim();
167 if (line->key.equalsIgnoreCase(key)) {
168 const char* result = line->value.c_str();
169 return line->active ? result : nullptr;
170 }
171 }
172 return nullptr;
173 }
174
175 // reads a single header line
176 void readLine(Client& in, char* str, int len) {
177 reader.readlnInternal(in, (uint8_t*)str, len, false);
178 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::readLine -> %s", str);
179 }
180
181 // writes a lingle header line
182 void writeHeaderLine(Client& out, HttpHeaderLine* header) {
183 if (header == nullptr) {
184 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::writeHeaderLine",
185 "the value must not be null");
186 return;
187 }
188 if (!header->active) {
189 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::writeHeaderLine %s - not active",
190 header->key.c_str());
191 return;
192 }
193 if (header->value.c_str() == nullptr) {
196 "HttpHeader::writeHeaderLine - ignored because value is null");
197 return;
198 }
199
200 char msg[400];
201 StrView msg_str(msg, 400);
202 msg_str = header->key.c_str();
203 msg_str += ": ";
204 msg_str += header->value.c_str();
205 msg_str += CRLF;
206 out.print(msg);
207
208 // remove crlf from log
209 int len = strnlen(msg, 200);
210 msg[len - 2] = 0;
211 DlnaLogger.log(DlnaLogLevel::Debug, "writeHeaderLine -> %s", msg);
212
213 // marke as processed
214 header->active = false;
215 }
216
217 const char* urlPath() { return url_path.c_str(); }
218
219 const char* protocol() { return protocol_str.c_str(); }
220
222
223 const char* accept() { return get(ACCEPT); }
224
225 int statusCode() { return status_code; }
226
227 const char* statusMessage() { return status_msg.c_str(); }
228
229 bool isChunked() {
230 // the value is automatically set from the reply
231 return is_chunked;
232 }
233
234 // reads the full header from the request (stream)
235 void read(Client& in) {
236 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::read");
237 // remove all existing value
238 clear();
239
240 char line[MaxHeaderLineLength];
241 if (in.connected()) {
242 delay(200);
243 if (in.available() == 0) {
244 DlnaLogger.log(DlnaLogLevel::Warning, "Waiting for data...");
245 uint64_t end = millis() + in.getTimeout();
246 while (in.available() == 0) {
247 delay(100);
248 if (millis()>end) break;
249 }
250 }
251 readLine(in, line, MaxHeaderLineLength);
252 parse1stLine(line);
253 while (in.available()) {
254 readLine(in, line, MaxHeaderLineLength);
255 StrView lineStr(line);
256 if (lineStr.isEmpty() || lineStr.isNewLine()) {
257 break;
258 }
259 if (isValidStatus() || isRedirectStatus()) {
260 lineStr.ltrim();
261 put(line);
262 }
263 }
264 }
265 }
266
267 // writes the full header to the indicated stream
268 void write(Client& out) {
269 DlnaLogger.log(DlnaLogLevel::Debug, "HttpHeader::write");
270 write1stLine(out);
271 for (auto it = lines.begin(); it != lines.end(); ++it) {
272 writeHeaderLine(out, *it);
273 }
274 // print empty line
275 crlf(out);
276 out.flush();
277 is_written = true;
278 }
279
280 // automatically create new lines
281 void setAutoCreateLines(bool is_auto_line) {
282 create_new_lines = is_auto_line;
283 }
285 bool isValidStatus() { return status_code >= 200 && status_code < 300; }
286
287 bool isRedirectStatus() { return status_code >= 300 && status_code < 400; }
288
289 protected:
291 bool is_written = false;
292 bool is_chunked = false;
293 bool create_new_lines = true;
295 // we store the values on the heap. this is acceptable because we just have
296 // one instance for the requests and one for the replys: which needs about
297 // 2*100 bytes
303 const char* CRLF = "\r\n";
304
305 // the headers need to delimited with CR LF
306 void crlf(Client& out) {
307 out.print(CRLF);
308 DlnaLogger.log(DlnaLogLevel::Debug, " -> %s", "<CR LF>");
309 }
310
311 // gets or creates a header line by key
312 HttpHeaderLine* headerLine(const char* key) {
313 if (key != nullptr) {
314 for (auto it = lines.begin(); it != lines.end(); ++it) {
315 HttpHeaderLine* pt = (*it);
316 if (pt != nullptr && pt->key.c_str() != nullptr) {
317 if (pt->key.equalsIgnoreCase(key)) {
318 pt->active = true;
319 return pt;
320 }
321 }
322 }
323 if (create_new_lines) {
324 HttpHeaderLine* newLine = new HttpHeaderLine();
326 "HttpHeader::headerLine - new line created for %s", key);
327 newLine->active = true;
328 newLine->key = key;
329 lines.push_back(newLine);
330 return newLine;
331 }
332 } else {
333 DlnaLogger.log(DlnaLogLevel::Error, "HttpHeader::headerLine",
334 "The key must not be null");
335 }
336 return nullptr;
337 }
338
339 TinyMethodID getMethod(const char* line) {
340 // set action
341 for (int j = 0; methods[j] != nullptr; j++) {
342 const char* action = methods[j];
343 int len = strlen(action);
344 if (strncmp(action, line, len) == 0) {
345 return (TinyMethodID)j;
346 }
347 }
348 return (TinyMethodID)0;
349 }
350
351 virtual void write1stLine(Client& out) = 0;
352 virtual void parse1stLine(const char* line) = 0;
353};
354
360 public:
361 // Defines the action id, url path and http version for an request
363 const char* protocol = nullptr) {
364 this->method_id = id;
365 this->url_path = urlPath;
366
367 DlnaLogger.log(DlnaLogLevel::Debug, "HttpRequestHeader::setValues - path: %s",
368 this->url_path.c_str());
369 if (protocol != nullptr) {
370 this->protocol_str = protocol;
371 }
372 return *this;
373 }
374
375 // action path protocol
376 void write1stLine(Client& out) {
377 char msg[201] = {0};
378
379 const char* method_str = methods[this->method_id];
380 strncat(msg, method_str, 200);
381 strncat(msg, " ", 200);
382 strncat(msg, this->url_path.c_str(), 200);
383 strncat(msg, " ", 200);
384 strncat(msg, this->protocol_str.c_str(), 200);
385 strncat(msg, CRLF, 200);
386 out.print(msg);
387 DlnaLogger.log(DlnaLogLevel::Debug, "HttpRequestHeader::write1stLine: %s", msg);
388 }
389
390 // parses the requestline
391 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
392 void parse1stLine(const char* line) {
393 DlnaLogger.log(DlnaLogLevel::Debug, "HttpRequestHeader::parse1stLine %s", line);
394 StrView line_str(line);
395 int space1 = line_str.indexOf(" ");
396 int space2 = line_str.indexOf(" ", space1 + 1);
397
398 this->method_id = getMethod(line);
399 this->protocol_str.substrView(line_str, space2 + 1, line_str.length());
400 this->url_path.substrView(line_str, space1 + 1, space2);
401 this->url_path.trim();
402
403 DlnaLogger.log(DlnaLogLevel::Debug, "->method: %s", methods[this->method_id]);
405 DlnaLogger.log(DlnaLogLevel::Debug, "->url_path: %s", url_path.c_str());
406 }
407};
408
414 public:
415 // defines the values for the rely
416 void setValues(int statusCode, const char* msg = "",
417 const char* protocol = nullptr) {
418 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::setValues %d", statusCode);
419 status_msg = msg;
421 if (protocol != nullptr) {
422 this->protocol_str = protocol;
423 }
424 }
425
426 // reads the final chunked reply headers
427 void readExt(Client& in) {
428 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::readExt");
429 char line[MaxHeaderLineLength];
430 readLine(in, line, MaxHeaderLineLength);
431 while (strlen(line) != 0) {
432 put(line);
433 readLine(in, line, MaxHeaderLineLength);
434 }
435 }
436
437 // HTTP-Version SP Status-Code SP Reason-Phrase CRLF
438 void write1stLine(Client& out) {
439 char msg[200];
440 StrView msg_str(msg, 200);
441 msg_str = this->protocol_str.c_str();
442 msg_str += " ";
443 msg_str += this->status_code;
444 msg_str += " ";
445 msg_str += this->status_msg.c_str();
446 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::write1stLine: %s", msg);
447 out.print(msg);
448 crlf(out);
449 }
450
451 // HTTP-Version SP Status-Code SP Reason-Phrase CRLF
452 // we just update the pointers to point to the correct position in the
453 // http_status_line
454 void parse1stLine(const char* line) {
455 DlnaLogger.log(DlnaLogLevel::Debug, "HttpReplyHeader::parse1stLine %s", line);
456 StrView line_str(line);
457 int space1 = line_str.indexOf(' ', 0);
458 int space2 = line_str.indexOf(' ', space1 + 1);
459
460 // save http version
461 protocol_str.substrView(line_str, 0, space1);
462
463 // find response status code after the first space
464 char status_c[6];
465 StrView status(status_c, 6);
466 status.substrView(line_str, space1 + 1, space2);
467 status_code = atoi(status_c);
468
469 // get reason-phrase after last SP
470 status_msg.substrView(line_str, space2 + 1, line_str.length());
471 }
472};
473
474} // 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:69
List< HttpHeaderLine * > lines
Definition: HttpHeader.h:301
virtual void parse1stLine(const char *line)=0
const char * statusMessage()
Definition: HttpHeader.h:227
void writeHeaderLine(Client &out, HttpHeaderLine *header)
Definition: HttpHeader.h:182
const char * accept()
Definition: HttpHeader.h:223
bool isRedirectStatus()
Definition: HttpHeader.h:287
void read(Client &in)
Definition: HttpHeader.h:235
HttpHeader & clear(bool activeFlag=true)
clears the data - usually we do not delete but we just set the active flag
Definition: HttpHeader.h:84
HttpHeaderLine * headerLine(const char *key)
Definition: HttpHeader.h:312
int statusCode()
Definition: HttpHeader.h:225
HttpHeader & put(const char *key, int value)
adds a new line to the header - e.g. for content size
Definition: HttpHeader.h:131
const char * CRLF
Definition: HttpHeader.h:303
TinyMethodID method()
Definition: HttpHeader.h:221
TinyMethodID method_id
Definition: HttpHeader.h:294
HttpHeader & put(const char *line)
adds a received new line to the header
Definition: HttpHeader.h:147
TinyMethodID getMethod(const char *line)
Definition: HttpHeader.h:339
const char * urlPath()
Definition: HttpHeader.h:217
void crlf(Client &out)
Definition: HttpHeader.h:306
HttpHeader()
Definition: HttpHeader.h:71
virtual void write1stLine(Client &out)=0
Str protocol_str
Definition: HttpHeader.h:298
~HttpHeader()
Definition: HttpHeader.h:78
const char * protocol()
Definition: HttpHeader.h:219
Str url_path
Definition: HttpHeader.h:299
bool is_written
Definition: HttpHeader.h:291
bool isChunked()
Definition: HttpHeader.h:229
void readLine(Client &in, char *str, int len)
Definition: HttpHeader.h:176
HttpLineReader reader
Definition: HttpHeader.h:302
int status_code
Definition: HttpHeader.h:290
bool isValidStatus()
returns true if status code >=200 and < 300
Definition: HttpHeader.h:285
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:103
Str status_msg
Definition: HttpHeader.h:300
void setAutoCreateLines(bool is_auto_line)
Definition: HttpHeader.h:281
bool create_new_lines
Definition: HttpHeader.h:293
bool is_chunked
Definition: HttpHeader.h:292
void write(Client &out)
Definition: HttpHeader.h:268
const char * get(const char *key)
Definition: HttpHeader.h:163
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:413
void parse1stLine(const char *line)
Definition: HttpHeader.h:454
void write1stLine(Client &out)
Definition: HttpHeader.h:438
void setValues(int statusCode, const char *msg="", const char *protocol=nullptr)
Definition: HttpHeader.h:416
void readExt(Client &in)
Definition: HttpHeader.h:427
Reading and writing of Http Requests.
Definition: HttpHeader.h:359
void write1stLine(Client &out)
Definition: HttpHeader.h:376
void parse1stLine(const char *line)
Definition: HttpHeader.h:392
HttpHeader & setValues(TinyMethodID id, const char *urlPath, const char *protocol=nullptr)
Definition: HttpHeader.h:362
Double linked list.
Definition: List.h:19
void log(DlnaLogLevel current_level, const char *fmt...)
Print log message.
Definition: Logger.h:40
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:19
virtual void substrView(StrView &from, int start, int end)
copies a substring into the current string
Definition: StrView.h:493
virtual bool equalsIgnoreCase(const char *alt)
Compares the string ignoring the case.
Definition: StrView.h:585
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:275
virtual int length()
Definition: StrView.h:380
virtual void trim()
remove leading and traling spaces
Definition: StrView.h:520
virtual void ltrim()
remove leading spaces
Definition: StrView.h:536
virtual bool isNewLine()
Definition: StrView.h:385
String implementation which keeps the data on the heap. We grow the allocated memory only if the copy...
Definition: Str.h:22
const char * c_str()
provides the string value as const char*
Definition: Str.h:187
Definition: Allocator.h:6
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:48
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_UNDEFINED
Definition: HttpHeader.h:36
@ T_OPTIONS
Definition: HttpHeader.h:43
@ T_SUBSCRIBE
Definition: HttpHeader.h:46
@ 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
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
LoggerClass DlnaLogger
Definition: Logger.cpp:5
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:56
bool active
Definition: HttpHeader.h:59
Str key
Definition: HttpHeader.h:57
Str value
Definition: HttpHeader.h:58