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