* the cases where SD is not present or page not found, using embedded pages.
* Quering from browser the /status page, the server return a json dictionary where are store
* some board status info, like board temperature, up-time, etc.
+ *
+ * notest: avr
*/
#include "http.h"
#define LOG_VERBOSITY HTTP_LOG_FORMAT
#include <cfg/log.h>
-#include <drv/sd.h>
-
-#include <fs/fat.h>
-
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+static struct { const char *key; const char *content; } http_content_type[] =
+{
+ {"", "Content-type: application/json\r\n\r\n"},
+ {"htm", "Content-type: text/html\r\n\r\n"},
+ {"css", "Content-type: text/css\r\n\r\n"},
+ {"js", "Content-type: text/javascript\r\n\r\n"},
+ {"png", "Content-type: image/png\r\n\r\n"},
+ {"jpg", "Content-type: image/jpeg\r\n\r\n"},
+ {"gif", "Content-type: image/gif\r\n\r\n"},
+ {"txt", "Content-type: text/plain\r\n\r\n"},
+};
+
+static const char http_html_hdr_200[] = "HTTP/1.0 200 OK\r\n";
+static const char http_html_hdr_404[] = "HTTP/1.0 404 Not Found\r\n";
+static const char http_html_hdr_500[] = "HTTP/1.0 500 Internal Server Error\r\n";
-static const char http_html_hdr_200[] = "HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n";
-static const char http_html_hdr_404[] = "HTTP/1.1 404 Not Found\r\nContent-type: text/html\r\n\r\n";
+static HttpCGI *cgi_table;
+static http_handler_t http_callback;
+static char decoded_str[80];
-void http_sendOk(struct netconn *client)
+/**
+ * Get key value from tokenized buffer
+ */
+int http_getValue(char *tolenized_buf, size_t tolenized_buf_len, const char *key, char *value, size_t len)
{
- netconn_write(client, http_html_hdr_200, sizeof(http_html_hdr_200) - 1, NETCONN_NOCOPY);
+ if (!tolenized_buf || !key || !value)
+ return -1;
+
+ char *p = tolenized_buf;
+ size_t value_len = 0;
+
+ memset(value, 0, len);
+
+ for (size_t i = 0; i < tolenized_buf_len; i++)
+ {
+ size_t token_len = strlen(p);
+ http_decodeUrl(p, token_len, decoded_str, sizeof(decoded_str));
+
+ if (!strcmp(key, decoded_str))
+ {
+ /* skip key */
+ p += token_len + 1;
+
+ http_decodeUrl(p, strlen(p), decoded_str, sizeof(decoded_str));
+ value_len = strlen(decoded_str);
+
+ if (value_len >= len)
+ return -1;
+
+ strcpy(value, decoded_str);
+ return value_len;
+ }
+ /* jump to next pair */
+ p += token_len + 1;
+ }
+
+ return -1;
}
-void http_sendFileNotFound(struct netconn *client)
+/**
+ * tokenize a buffer
+ */
+int http_tokenizeGetRequest(char *raw_buf, size_t raw_len)
{
- netconn_write(client, http_html_hdr_404, sizeof(http_html_hdr_404) - 1, NETCONN_NOCOPY);
+ size_t token = 0;
+
+ for(size_t i = 0; (i < raw_len) && raw_buf; i++)
+ {
+ if (raw_buf[i] == '&')
+ {
+ token++;
+ raw_buf[i] = '\0';
+ }
+
+ if (raw_buf[i] == '=')
+ raw_buf[i] = '\0';
+ }
+
+ return token + 1;
}
+static char http_hexToAscii(char first, char second)
+{
+ char hex[5], *stop;
+ hex[0] = '0';
+ hex[1] = 'x';
+ hex[2] = first;
+ hex[3] = second;
+ hex[4] = 0;
+ return strtol(hex, &stop, 16);
+}
+
+void http_decodeUrl(const char *raw_buf, size_t raw_len, char *decodec_buf, size_t len)
+{
+ ASSERT(decodec_buf);
+
+ char value;
+ memset(decodec_buf, 0, len);
+
+ for (size_t i = 0; i < raw_len; i++)
+ {
+ if (!len)
+ return;
+
+ if (raw_buf[i] == '%')
+ {
+ if (i + 2 < raw_len)
+ {
+ /* convert hex value after % */
+ value = http_hexToAscii(raw_buf[i + 1], raw_buf[i + 2]);
+ if (value)
+ {
+ *decodec_buf++ = value;
+ len--;
+ /* decoded two digit of hex value, go to next value*/
+ i += 2;
+ continue;
+ }
+ }
+ }
+
+ /* Manage special case of '+', that it should be convert in space */
+ *decodec_buf++ = (raw_buf[i] == '+' ? ' ' : raw_buf[i]);
+ len--;
+ }
+}
-static void get_fileName(char *revc_buf, char *name, size_t len)
+void http_getPageName(const char *recv_buf, size_t recv_len, char *page_name, size_t len)
{
- char *p = strstr(revc_buf, "GET");
- if (p)
+ int i = 0;
+ bool str_ok = false;
+ const char *p = recv_buf;
+ if (p && (recv_len > sizeof("GET /")))
{
- //skip the "/" in get string request
- p += sizeof("GET") + 1;
- for (size_t i = 0; *p != ' '; i++,p++)
+ if (*p++ == 'G' && *p++ == 'E' && *p++ == 'T')
{
- if (i > len)
+ str_ok = true;
+ /* skip the space and "/" */
+ p += 2;
+ }
+ }
+
+ if (str_ok)
+ {
+ while ((size_t)i < recv_len)
+ {
+ char ch = *(p++);
+ if (ch == ' ' || ch == '\t' || ch == '\n')
break;
- name[i] = *p;
+ if((size_t)i == len - 1)
+ break;
+ page_name[i++] = ch;
}
}
+
+ page_name[i] = '\0';
}
-static http_gci_handler_t cgi_search(const char *name, HttpCGI *table)
+INLINE const char *get_ext(const char *name)
{
- for (int i = 0; table[i].name; i++)
+ const char *ext = strstr(name, ".");
+ if(ext && (ext + 1))
+ return (ext + 1);
+
+ return NULL;
+}
+
+int http_searchContentType(const char *name)
+{
+ if (!name)
+ return 0;
+
+ const char *ext = get_ext(name);
+ LOG_INFO("Ext: %s\n", !ext ? "none" : ext);
+
+ if (!ext)
+ return 0;
+
+ if (!strcmp(ext, "ico"))
+ return HTTP_CONTENT_JPEG;
+
+ for (int i = 0; i < HTTP_CONTENT_CNT; i++)
{
- if (!strcmp(table[i].name, name))
- return table[i].handler;
+ if (!strcmp(ext, http_content_type[i].key))
+ return i;
}
- return NULL;
+
+ return 0;
}
-static uint8_t tx_buf[2048];
-static char file_name[80];
-void http_server(struct netconn *server, struct HttpCGI *table)
+/**
+ * Send on \param client socket the 200 Ok http header with
+ * select \param content_type
+ */
+void http_sendOk(struct netconn *client, int content_type)
+{
+ ASSERT(content_type < HTTP_CONTENT_CNT);
+
+ netconn_write(client, http_html_hdr_200, sizeof(http_html_hdr_200) - 1, NETCONN_COPY);
+ netconn_write(client, http_content_type[content_type].content,
+ strlen(http_content_type[content_type].content), NETCONN_COPY);
+}
+
+
+/**
+ * Send on \param client socket the 404 File not found http header with
+ * select \param content_type
+ */
+void http_sendFileNotFound(struct netconn *client, int content_type)
+{
+ ASSERT(content_type < HTTP_CONTENT_CNT);
+
+ netconn_write(client, http_html_hdr_404, sizeof(http_html_hdr_404) - 1, NETCONN_COPY);
+ netconn_write(client, http_content_type[content_type].content,
+ strlen(http_content_type[content_type].content), NETCONN_COPY);
+}
+
+/**
+ * Send on \param client socket the 500 internal server error http header with
+ * select \param content_type
+ */
+void http_sendInternalErr(struct netconn *client, int content_type)
+{
+ ASSERT(content_type < HTTP_CONTENT_CNT);
+
+ netconn_write(client, http_html_hdr_500, sizeof(http_html_hdr_500) - 1, NETCONN_COPY);
+ netconn_write(client, http_content_type[content_type].content,
+ strlen(http_content_type[content_type].content), NETCONN_COPY);
+}
+
+static http_handler_t cgi_search(const char *name, HttpCGI *table)
+{
+ if (!table)
+ return NULL;
+
+ int i = 0;
+ const char *ext = get_ext(name);
+
+ while(table[i].name)
+ {
+ if (ext && table[i].type == CGI_MATCH_EXT)
+ {
+
+ if (!strcmp(table[i].name, ext))
+ {
+ LOG_INFO("Match ext: %s\n", ext);
+ break;
+ }
+ }
+ else if (table[i].type == CGI_MATCH_NAME)
+ {
+
+ if (strstr(name, table[i].name) != NULL)
+ {
+ LOG_INFO("Match string: %s\n", name);
+ break;
+ }
+ }
+ else /* (table[i].type == CGI_MATCH_WORD) */
+ {
+
+ if (!strcmp(table[i].name, name))
+ {
+ LOG_INFO("Word match: %s\n", name);
+ break;
+ }
+ }
+
+ i++;
+ }
+
+ return table[i].handler;
+}
+
+static char req_string[80];
+
+/**
+ * Http polling function.
+ *
+ * Call this functions to process each client connections.
+ *
+ */
+void http_poll(struct netconn *server)
{
- // SD fat filesystem context
- Sd sd;
- FATFS fs;
- FatFile in_file;
struct netconn *client;
struct netbuf *rx_buf_conn;
char *rx_buf;
- u16_t len;
- FRESULT result;
+ uint16_t len;
client = netconn_accept(server);
if (!client)
netbuf_data(rx_buf_conn, (void **)&rx_buf, &len);
if (rx_buf)
{
- memset(file_name, 0, sizeof(file_name));
- get_fileName(rx_buf, file_name, sizeof(file_name));
+ memset(req_string, 0, sizeof(req_string));
+ http_getPageName(rx_buf, len, req_string, sizeof(req_string));
- LOG_INFO("Search %s\n", file_name);
- if (strlen(file_name) == 0)
- strcpy(file_name, HTTP_DEFAULT_PAGE);
+ if (req_string[0] == '\0')
+ strcpy(req_string, HTTP_DEFAULT_PAGE);
- http_gci_handler_t cgi = cgi_search(file_name, table);
+ http_handler_t cgi = cgi_search(req_string, cgi_table);
if (cgi)
{
- cgi(rx_buf, client);
- }
- else if (SD_CARD_PRESENT())
- {
- bool sd_ok = sd_init(&sd, NULL, 0);
- if (sd_ok)
+ if (cgi(client, req_string, rx_buf, len) < 0)
{
- LOG_INFO("Mount FAT filesystem.\n");
- result = f_mount(0, &fs);
- if (result != FR_OK)
- {
- LOG_ERR("Mounting FAT volumes error[%d]\n", result);
- sd_ok = false;
- f_mount(0, NULL);
- }
-
- if (sd_ok)
- {
- result = fatfile_open(&in_file, file_name, FA_OPEN_EXISTING | FA_READ);
-
- size_t count = 0;
- if (result == FR_OK)
- {
- LOG_INFO("Opened file '%s' size %ld\n", file_name, in_file.fat_file.fsize);
-
- http_sendOk(client);
-
- while (count < in_file.fat_file.fsize)
- {
- int len = kfile_read(&in_file.fd, tx_buf, sizeof(tx_buf));
- netconn_write(client, tx_buf, len, NETCONN_COPY);
- count += len;
- }
-
- kfile_flush(&in_file.fd);
- kfile_close(&in_file.fd);
-
- LOG_INFO("Sent: %d\n", count);
- }
- else
- {
- LOG_ERR("Unable to open file: '%s' error[%d]\n", file_name, result);
- http_sendFileNotFound(client);
- netconn_write(client, http_file_not_found, http_file_not_found_len - 1, NETCONN_NOCOPY);
- }
- }
+ LOG_ERR("Internal server error\n");
+ http_sendInternalErr(client, HTTP_CONTENT_HTML);
+ netconn_write(client, http_server_error, http_server_error_len - 1, NETCONN_NOCOPY);
}
- f_mount(0, NULL);
- LOG_INFO("Umount FAT filesystem.\n");
}
else
{
- http_sendFileNotFound(client);
- netconn_write(client, http_sd_not_present, http_sd_not_present_len, NETCONN_NOCOPY);
+ http_callback(client, req_string, rx_buf, len);
}
}
netconn_close(client);
}
netconn_delete(client);
}
+
+/**
+ * Init the http server.
+ *
+ * The simply http server call for each client request the default_callback function. The
+ * user should define this callback to manage the client request, i.e. reading site's page
+ * from SD card. The user can define the cgi_table, where associate one callback to the user string.
+ * In this way the user could filter some client request and redirect they to custom callback, i.e.
+ * the client could request status of the device only loading the particular page name.
+ *
+ * \param default_callback fuction that server call for all request, that does'nt match cgi table.
+ * \param table of callcack to call when client request a particular page.
+ */
+void http_init(http_handler_t default_callback, struct HttpCGI *table)
+{
+ ASSERT(default_callback);
+
+ cgi_table = table;
+ http_callback = default_callback;
+}
+