Make server reply content type message configurable. Add related tests.
[bertos.git] / bertos / net / http.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2011 Develer S.r.l. (http://www.develer.com/)
30  *
31  * -->
32  *
33  * \author Daniele Basile <asterix@develer.com>
34  *
35  * \brief Simple Http server.
36  *
37  * This simple web server read the site's pages from SD card, and manage
38  * the cases where SD is not present or page not found, using embedded pages.
39  * Quering from browser the /status page, the server return a json dictionary where are store
40  * some board status info, like board temperature, up-time, etc.
41  *
42  * notest: avr
43  */
44
45 #include "http.h"
46
47 #include "hw/hw_sd.h"
48 #include "hw/hw_http.h"
49
50 #include "cfg/cfg_http.h"
51
52 // Define logging setting (for cfg/log.h module).
53 #define LOG_LEVEL         HTTP_LOG_LEVEL
54 #define LOG_VERBOSITY     HTTP_LOG_FORMAT
55 #include <cfg/log.h>
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60
61 static struct { const char *key; const char *content; } http_content_type[] =
62 {
63         {"",    "Content-type: application/json\r\n\r\n"},
64         {"htm", "Content-type: text/html\r\n\r\n"},
65         {"css", "Content-type: text/css\r\n\r\n"},
66         {"js",  "Content-type: text/javascript\r\n\r\n"},
67         {"png", "Content-type: image/png\r\n\r\n"},
68         {"jpg", "Content-type: image/jpeg\r\n\r\n"},
69         {"gif", "Content-type: image/gif\r\n\r\n"},
70         {"txt", "Content-type: text/plain\r\n\r\n"},
71 };
72
73 static const char http_html_hdr_200[] = "HTTP/1.0 200 OK\r\n";
74 static const char http_html_hdr_404[] = "HTTP/1.0 404 Not Found\r\n";
75 static const char http_html_hdr_500[] = "HTTP/1.0 500 Internal Server Error\r\n";
76
77 static HttpCGI *cgi_table;
78 static http_handler_t http_callback;
79 static char decoded_str[80];
80
81 /**
82  * Get key value from tokenized buffer
83  */
84 int http_getValue(char *tolenized_buf, size_t tolenized_buf_len, const char *key, char *value, size_t len)
85 {
86         if (!tolenized_buf || !key || !value)
87                 return -1;
88
89         char *p = tolenized_buf;
90         size_t value_len = 0;
91
92         memset(value, 0, len);
93
94         for (size_t i = 0; i < tolenized_buf_len; i++)
95         {
96                 size_t token_len = strlen(p);
97                 http_decodeUrl(p, token_len, decoded_str, sizeof(decoded_str));
98
99                 if (!strcmp(key, decoded_str))
100                 {
101                         /* skip key */
102                         p += token_len + 1;
103
104                         http_decodeUrl(p, strlen(p), decoded_str, sizeof(decoded_str));
105                         value_len = strlen(decoded_str);
106
107                         if (value_len >= len)
108                                 return -1;
109
110                         strcpy(value, decoded_str);
111                         return value_len;
112                 }
113                 /* jump to next pair */
114                 p += token_len + 1;
115         }
116
117         return -1;
118 }
119
120 /**
121  * tokenize a buffer
122  */
123 int http_tokenizeGetRequest(char *raw_buf, size_t raw_len)
124 {
125         size_t token = 0;
126
127     for(size_t i = 0; (i < raw_len) && raw_buf; i++)
128         {
129                 if (raw_buf[i] == '&')
130                 {
131                         token++;
132                         raw_buf[i] = '\0';
133                 }
134
135                 if (raw_buf[i] == '=')
136                         raw_buf[i] = '\0';
137     }
138
139     return token + 1;
140 }
141
142 static char http_hexToAscii(char first, char second)
143 {
144         char hex[5], *stop;
145         hex[0] = '0';
146         hex[1] = 'x';
147         hex[2] = first;
148         hex[3] = second;
149         hex[4] = 0;
150         return strtol(hex, &stop, 16);
151 }
152
153 void http_decodeUrl(const char *raw_buf, size_t raw_len, char *decodec_buf, size_t len)
154 {
155         ASSERT(decodec_buf);
156
157         char value;
158         memset(decodec_buf, 0, len);
159
160         for (size_t i = 0; i < raw_len; i++)
161         {
162                 if (!len)
163                         return;
164
165                 if (raw_buf[i] == '%')
166                 {
167                         if (i + 2 < raw_len)
168                         {
169                                 /* convert hex value after % */
170                                 value = http_hexToAscii(raw_buf[i + 1], raw_buf[i + 2]);
171                                 if (value)
172                                 {
173                                         *decodec_buf++ = value;
174                                         len--;
175                                         /* decoded two digit of hex value, go to next value*/
176                                         i += 2;
177                                         continue;
178                                 }
179                         }
180                 }
181
182                 /* Manage special case of '+', that it should be convert in space */
183                 *decodec_buf++ = (raw_buf[i] == '+' ? ' ' : raw_buf[i]);
184                 len--;
185         }
186 }
187
188 void http_getPageName(const char *recv_buf, size_t recv_len, char *page_name, size_t len)
189 {
190         int i = 0;
191         bool str_ok = false;
192         const char *p = recv_buf;
193         if (p && (recv_len > sizeof("GET /")))
194         {
195                 if (*p++ == 'G' && *p++ == 'E' && *p++ == 'T')
196                 {
197                         str_ok = true;
198                         /* skip the space and "/" */
199                         p += 2;
200                 }
201         }
202
203         if (str_ok)
204         {
205                 while ((size_t)i < recv_len)
206                 {
207                         char ch = *(p++);
208                         if (ch == ' ' || ch == '\t' || ch == '\n')
209                                 break;
210                         if((size_t)i == len - 1)
211                                 break;
212                         page_name[i++] = ch;
213                 }
214         }
215
216         page_name[i] = '\0';
217 }
218
219 INLINE const char *get_ext(const char *name)
220 {
221         const char *ext = strstr(name, ".");
222         if(ext && (ext + 1))
223                 return (ext + 1);
224
225         return NULL;
226 }
227
228 int http_searchContentType(const char *name)
229 {
230         if (!name)
231                 return 0;
232
233         const char *ext = get_ext(name);
234         LOG_INFO("Ext: %s\n", !ext ? "none" : ext);
235
236         if (!ext)
237                 return 0;
238
239         if (!strcmp(ext, "ico"))
240                 return HTTP_CONTENT_JPEG;
241
242         for (int i = 0; i < HTTP_CONTENT_CNT; i++)
243         {
244                 if (!strcmp(ext, http_content_type[i].key))
245                         return i;
246         }
247
248         return 0;
249 }
250
251
252 /**
253  * Send on \param client socket
254  * the 200 Ok http header
255  */
256 void http_sendOk(struct netconn *client, int content_type)
257 {
258         ASSERT(content_type < HTTP_CONTENT_CNT);
259
260         netconn_write(client, http_html_hdr_200, sizeof(http_html_hdr_200) - 1, NETCONN_COPY);
261         netconn_write(client, http_content_type[content_type].content,
262                         strlen(http_content_type[content_type].content), NETCONN_COPY);
263 }
264
265
266 /**
267  * Send on \param client socket
268  * the 404 File not found http header
269  */
270 void http_sendFileNotFound(struct netconn *client, int content_type)
271 {
272         ASSERT(content_type < HTTP_CONTENT_CNT);
273
274         netconn_write(client, http_html_hdr_404, sizeof(http_html_hdr_404) - 1, NETCONN_COPY);
275         netconn_write(client, http_content_type[content_type].content,
276                         strlen(http_content_type[content_type].content), NETCONN_COPY);
277 }
278
279 /**
280  * Send on \param client socket
281  * the 500 internal server error http header
282  */
283 void http_sendInternalErr(struct netconn *client, int content_type)
284 {
285         ASSERT(content_type < HTTP_CONTENT_CNT);
286
287         netconn_write(client, http_html_hdr_500, sizeof(http_html_hdr_500) - 1, NETCONN_COPY);
288         netconn_write(client, http_content_type[content_type].content,
289                         strlen(http_content_type[content_type].content), NETCONN_COPY);
290 }
291
292 static http_handler_t cgi_search(const char *name,  HttpCGI *table)
293 {
294         if (!table)
295                 return NULL;
296
297         int i = 0;
298         const char *ext = get_ext(name);
299
300         while(table[i].name)
301         {
302                 if (ext && table[i].type == CGI_MATCH_EXT)
303                 {
304
305                         if (!strcmp(table[i].name, ext))
306                         {
307                                 LOG_INFO("Match all ext %s\n", ext);
308                                 break;
309                         }
310                 }
311                 else if (table[i].type == CGI_MATCH_NAME)
312                 {
313
314                         if (strstr(name, table[i].name) != NULL)
315                         {
316                                 LOG_INFO("Match all name %s\n", name);
317                                 break;
318                         }
319                 }
320                 else /* (table[i].type == CGI_MATCH_WORD) */
321                 {
322
323                         if (!strcmp(table[i].name, name))
324                         {
325                                 LOG_INFO("Match all word %s\n", name);
326                                 break;
327                         }
328                 }
329
330                 i++;
331         }
332
333         return table[i].handler;
334 }
335
336 static char req_string[80];
337
338 /**
339  * Http polling function.
340  *
341  * Call this functions to process each client connections.
342  *
343  */
344 void http_poll(struct netconn *server)
345 {
346         struct netconn *client;
347         struct netbuf *rx_buf_conn;
348         char *rx_buf;
349         uint16_t len;
350
351         client = netconn_accept(server);
352         if (!client)
353                 return;
354
355         rx_buf_conn = netconn_recv(client);
356         if (rx_buf_conn)
357         {
358                 netbuf_data(rx_buf_conn, (void **)&rx_buf, &len);
359                 if (rx_buf)
360                 {
361                         memset(req_string, 0, sizeof(req_string));
362                         http_getPageName(rx_buf, len, req_string, sizeof(req_string));
363
364                         LOG_INFO("Search %s\n", req_string);
365                         if (req_string[0] == '\0')
366                                 strcpy(req_string, HTTP_DEFAULT_PAGE);
367
368                         http_handler_t cgi = cgi_search(req_string, cgi_table);
369                         if (cgi)
370                         {
371                                 if (cgi(client, req_string, rx_buf, len) < 0)
372                                 {
373                                         LOG_ERR("Internal server error\n");
374                                         http_sendInternalErr(client, HTTP_CONTENT_HTML);
375                                         netconn_write(client, http_server_error, http_server_error_len - 1, NETCONN_NOCOPY);
376                                 }
377                         }
378                         else
379                         {
380                                 http_callback(client, req_string, rx_buf, len);
381                         }
382                 }
383                 netconn_close(client);
384                 netbuf_delete(rx_buf_conn);
385         }
386         netconn_delete(client);
387 }
388
389 /**
390  * Init the http server.
391  *
392  * The simply http server call for each client request the default_callback function. The
393  * user should define this callback to manage the client request, i.e. reading site's page
394  * from SD card. The user can define the cgi_table, where associate one callback to the user string.
395  * In this way the user could filter some client request and redirect they to custom callback, i.e.
396  * the client could request status of the device only loading the particular page name.
397  *
398  * \param default_callback fuction that server call for all request, that does'nt match cgi table.
399  * \param table of callcack to call when client request a particular page.
400  */
401 void http_init(http_handler_t default_callback, struct HttpCGI *table)
402 {
403         ASSERT(default_callback);
404
405         cgi_table = table;
406         http_callback = default_callback;
407 }
408