Arduino DLNA Server
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 
10 namespace tiny_dlna {
11 
12 // Class Configuration
13 const int MaxHeaderLineLength = 200;
14 
15 // Define relevant header content
16 const char* CONTENT_TYPE = "Content-Type";
17 const char* CONTENT_LENGTH = "Content-Length";
18 const char* CONNECTION = "Connection";
19 const char* CON_CLOSE = "close";
20 const char* CON_KEEP_ALIVE = "keep-alive";
21 const char* TRANSFER_ENCODING = "Transfer-Encoding";
22 const char* CHUNKED = "chunked";
23 const char* ACCEPT = "Accept";
24 const char* ACCEPT_ALL = "*/*";
25 const char* SUCCESS = "Success";
26 const char* USER_AGENT = "User-Agent";
27 const char* DEFAULT_AGENT =
28  "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)";
29 const char* HOST_C = "Host";
30 const char* ACCEPT_ENCODING = "Accept-Encoding";
31 const char* IDENTITY = "identity";
32 const char* LOCATION = "Location";
33 
34 // Http methods
47 };
48 const char* methods[] = {"?", "GET", "HEAD", "POST",
49  "PUT", "DELETE", "TRACE", "OPTIONS",
50  "CONNECT", "PATCH", "SUBSCRIBE", nullptr};
51 
59  bool active;
60 };
61 
69 class HttpHeader {
70  public:
72  DlnaLogger.log(DlnaDebug, "HttpHeader");
73  // set default values
74  protocol_str = "HTTP/1.1";
75  url_path = "/";
76  status_msg = "";
77  }
79  DlnaLogger.log(DlnaDebug, "~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(DlnaDebug, "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(DlnaInfo, "HttpHeader::put -> is_chunked!!!");
120  this->is_chunked = true;
121  }
122  } else {
123  DlnaLogger.log(
124  DlnaInfo, "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(DlnaDebug, "HttpHeader::put %s %d", key, value);
133  HttpHeaderLine* hl = headerLine(key);
134 
135  if (value > 1024) {
136  DlnaLogger.log(DlnaWarning, "value %d is > 1024 for %s", value, key);
137  }
138 
139  // add value
140  hl->value = value;
141  hl->active = true;
142  DlnaLogger.log(DlnaInfo, "%s: %s", key, hl->value.c_str());
143  return *this;
144  }
145 
147  HttpHeader& put(const char* line) {
148  DlnaLogger.log(DlnaDebug, "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(DlnaInfo, "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(DlnaInfo, "HttpHeader::writeHeaderLine",
185  "the value must not be null");
186  return;
187  }
188  if (!header->active) {
189  DlnaLogger.log(DlnaInfo, "HttpHeader::writeHeaderLine %s - not active",
190  header->key.c_str());
191  return;
192  }
193  if (header->value.c_str() == nullptr) {
194  DlnaLogger.log(
195  DlnaInfo,
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(DlnaInfo, "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(DlnaInfo, "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(DlnaWarning, "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 HttpStreamedMultiOutput stream
268  void write(Client& out) {
269  DlnaLogger.log(DlnaInfo, "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
299  Str url_path = Str(70);
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(DlnaInfo, " -> %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(DlnaError, "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(DlnaInfo, "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(DlnaInfo, "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(DlnaInfo, "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(DlnaInfo, "->method: %s", methods[this->method_id]);
404  DlnaLogger.log(DlnaInfo, "->protocol: %s", protocol_str.c_str());
405  DlnaLogger.log(DlnaInfo, "->url_path: %s", url_path.c_str());
406  }
407 };
408 
413 class HttpReplyHeader : public HttpHeader {
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(DlnaInfo, "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(DlnaInfo, "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(DlnaInfo, "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(DlnaInfo, "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 information. With this API we can define and qu...
Definition: HttpHeader.h:69
List< HttpHeaderLine * > lines
Definition: HttpHeader.h:301
virtual void parse1stLine(const char *line)=0
void writeHeaderLine(Client &out, HttpHeaderLine *header)
Definition: HttpHeader.h:182
bool isRedirectStatus()
Definition: HttpHeader.h:287
void read(Client &in)
Definition: HttpHeader.h:235
const char * accept()
Definition: HttpHeader.h:223
int statusCode()
Definition: HttpHeader.h:225
HttpHeaderLine * headerLine(const char *key)
Definition: HttpHeader.h:312
const char * CRLF
Definition: HttpHeader.h:303
TinyMethodID method()
Definition: HttpHeader.h:221
TinyMethodID method_id
Definition: HttpHeader.h:294
TinyMethodID getMethod(const char *line)
Definition: HttpHeader.h:339
HttpHeader & put(const char *key, int value)
adds a new line to the header - e.g. for content size
Definition: HttpHeader.h:131
HttpHeader & put(const char *line)
adds a received new line to the header
Definition: HttpHeader.h:147
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
Str url_path
Definition: HttpHeader.h:299
bool is_written
Definition: HttpHeader.h:291
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:103
bool isChunked()
Definition: HttpHeader.h:229
void readLine(Client &in, char *str, int len)
Definition: HttpHeader.h:176
HttpHeader & clear(bool activeFlag=true)
clears the data - usually we do not delete but we just set the active flag
Definition: HttpHeader.h:84
HttpLineReader reader
Definition: HttpHeader.h:302
const char * get(const char *key)
Definition: HttpHeader.h:163
int status_code
Definition: HttpHeader.h:290
const char * urlPath()
Definition: HttpHeader.h:217
bool isValidStatus()
returns true if status code >=200 and < 300
Definition: HttpHeader.h:285
const char * protocol()
Definition: HttpHeader.h:219
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
const char * statusMessage()
Definition: HttpHeader.h:227
bool is_chunked
Definition: HttpHeader.h:292
void write(Client &out)
Definition: HttpHeader.h:268
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:486
virtual bool equalsIgnoreCase(const char *alt)
Compares the string ignoring the case.
Definition: StrView.h:578
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:376
virtual int indexOf(const char c, int start=0)
Definition: StrView.h:269
virtual int length()
Definition: StrView.h:373
virtual void trim()
remove leading and traling spaces
Definition: StrView.h:513
virtual void ltrim()
remove leading spaces
Definition: StrView.h:529
virtual bool isNewLine()
Definition: StrView.h:378
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:188
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
@ DlnaDebug
Definition: Logger.h:16
@ DlnaInfo
Definition: Logger.h:16
@ DlnaWarning
Definition: Logger.h:16
@ DlnaError
Definition: Logger.h:16
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