Fork of Smoothie to port to mbed non-LPC targets.
Fork of Smoothie by
Diff: libs/Network/uip/webserver/httpd.c
- Revision:
- 2:1df0b61d3b5a
diff -r ab59fc9af055 -r 1df0b61d3b5a libs/Network/uip/webserver/httpd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libs/Network/uip/webserver/httpd.c Fri Feb 28 18:52:52 2014 -0800 @@ -0,0 +1,680 @@ +#pragma GCC diagnostic ignored "-Wredundant-decls" +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#pragma GCC diagnostic ignored "-Wcast-align" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + +/** + * \addtogroup apps + * @{ + */ + +/** + * \defgroup httpd Web server + * @{ + * The uIP web server is a very simplistic implementation of an HTTP + * server. It can serve web pages and files from a read-only ROM + * filesystem, and provides a very small scripting language. + + */ + +/** + * \file + * Web server + * \author + * Adam Dunkels <adam@sics.se> + */ + + +/* + * Copyright (c) 2004, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * Author: Adam Dunkels <adam@sics.se> + * + * $Id: httpd.c,v 1.2 2006/06/11 21:46:38 adam Exp $ + */ + +#include <stdio.h> + +#include "uip.h" +#include "httpd.h" +#include "httpd-fs.h" +#include "http-strings.h" + +#include <string.h> +#include "stdio.h" +#include "stdlib.h" + +#include "CommandQueue.h" +#include "CallbackStream.h" + +#include "c-fifo.h" + +#define STATE_WAITING 0 +#define STATE_HEADERS 1 +#define STATE_BODY 2 +#define STATE_OUTPUT 3 +#define STATE_UPLOAD 4 + +#define GET 1 +#define POST 2 + +#define ISO_nl 0x0a +#define ISO_space 0x20 +#define ISO_bang 0x21 +#define ISO_percent 0x25 +#define ISO_period 0x2e +#define ISO_slash 0x2f +#define ISO_colon 0x3a + +#define DEBUG_PRINTF printf +//#define DEBUG_PRINTF(...) + + +// this callback gets the results of a command, line by line. need to check if +// we need to stall the upstream sender return 0 if stalled 1 if ok to keep +// providing more -1 if the connection has closed or is not in output state. +// need to see which connection to send to based on state and add result to +// that fifo for each connection. NOTE this will not get called if the +// connection has been closed and the stream will get deleted when the last +// command has been executed +static int command_result(const char *str, void *state) +{ + struct httpd_state *s = (struct httpd_state *)state; + if (s == NULL) { + // connection was closed so discard, this should never happen + DEBUG_PRINTF("ERROR: command result for closed state %d\n", (int)state); + return -1; + } + + if (str == NULL) { + DEBUG_PRINTF("End of command (%p)\n", state); + fifo_push(s->fifo, NULL); + + } else { + if (fifo_size(s->fifo) < 10) { + DEBUG_PRINTF("Got command result (%p): %s", state, str); + fifo_push(s->fifo, strdup(str)); + return 1; + } else { + DEBUG_PRINTF("command result fifo is full (%p)\n", state); + return 0; + } + } + return 1; +} + +static void create_callback_stream(struct httpd_state *s) +{ + // need to create a callback stream here, but do one per connection pass + // the state to the callback, also create the fifo for the command results + s->fifo = new_fifo(); + s->pstream = new_callback_stream(command_result, s); +} + +// Used to save files to SDCARD during upload +static FILE *fd; +static char *output_filename = NULL; +static int file_cnt = 0; +static int open_file(const char *fn) +{ + if (output_filename != NULL) free(output_filename); + output_filename = malloc(strlen(fn) + 5); + strcpy(output_filename, "/sd/"); + strcat(output_filename, fn); + fd = fopen(output_filename, "w"); + if (fd == NULL) { + free(output_filename); + output_filename = NULL; + return 0; + } + return 1; +} + +static int close_file() +{ + free(output_filename); + output_filename = NULL; + fclose(fd); + return 1; +} + +static int save_file(uint8_t *buf, unsigned int len) +{ + if (fwrite(buf, 1, len, fd) == len) { + file_cnt += len; + // HACK alert work around bug causing file corruption when writing large amounts of data + if (file_cnt >= 400) { + file_cnt = 0; + fclose(fd); + fd = fopen(output_filename, "a"); + } + return 1; + + } else { + close_file(); + return 0; + } +} + +static int fs_open(struct httpd_state *s) +{ + if (strncmp(s->filename, "/sd/", 4) == 0) { + DEBUG_PRINTF("Opening file %s\n", s->filename); + s->fd = fopen(s->filename, "r"); + if (s->fd == NULL) { + DEBUG_PRINTF("Failed to open: %s\n", s->filename); + return 0; + } + return 1; + + } else { + s->fd = NULL; + return httpd_fs_open(s->filename, &s->file); + } +} + +/*---------------------------------------------------------------------------*/ +static PT_THREAD(send_command_response(struct httpd_state *s)) +{ + PSOCK_BEGIN(&s->sout); + + do { + PSOCK_WAIT_UNTIL( &s->sout, fifo_size(s->fifo) > 0 ); + s->strbuf = fifo_pop(s->fifo); + if (s->strbuf != NULL) { + // send it + DEBUG_PRINTF("Sending response: %s", s->strbuf); + // TODO send as much as we can in one packet + PSOCK_SEND_STR(&s->sout, s->strbuf); + // free the strdup + free(s->strbuf); + }else if(--s->command_count <= 0) { + // when all commands have completed exit + break; + } + } while (1); + + PSOCK_END(&s->sout); +} + +/*---------------------------------------------------------------------------*/ +static unsigned short generate_part_of_file(void *state) +{ + struct httpd_state *s = (struct httpd_state *)state; + + if (s->file.len > uip_mss()) { + s->len = uip_mss(); + } else { + s->len = s->file.len; + } + memcpy(uip_appdata, s->file.data, s->len); + + return s->len; +} +/*---------------------------------------------------------------------------*/ +static unsigned short generate_part_of_sd_file(void *state) +{ + struct httpd_state *s = (struct httpd_state *)state; + + int len = fread(uip_appdata, 1, uip_mss(), s->fd); + if (len <= 0) { + // we need to send something + strcpy(uip_appdata, "\r\n"); + len = 2; + s->len = 0; + } else { + s->len = len; + } + return len; +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(send_file(struct httpd_state *s)) +{ + PSOCK_BEGIN(&s->sout); + + do { + PSOCK_GENERATOR_SEND(&s->sout, generate_part_of_file, s); + s->file.len -= s->len; + s->file.data += s->len; + } while (s->file.len > 0); + + PSOCK_END(&s->sout); +} + +/*---------------------------------------------------------------------------*/ +static PT_THREAD(send_sd_file(struct httpd_state *s)) +{ + PSOCK_BEGIN(&s->sout); + + do { + PSOCK_GENERATOR_SEND(&s->sout, generate_part_of_sd_file, s); + } while (s->len > 0); + + fclose(s->fd); + s->fd = NULL; + + PSOCK_END(&s->sout); +} + +/*---------------------------------------------------------------------------*/ +static PT_THREAD(send_headers_3(struct httpd_state *s, const char *statushdr, char send_content_type)) +{ + char *ptr; + + PSOCK_BEGIN(&s->sout); + + PSOCK_SEND_STR(&s->sout, statushdr); + + if (send_content_type) { + ptr = strrchr(s->filename, ISO_period); + if (ptr == NULL) { + PSOCK_SEND_STR(&s->sout, http_content_type_plain); // http_content_type_binary); + } else if (strncmp(http_html, ptr, 5) == 0 || strncmp(http_shtml, ptr, 6) == 0) { + PSOCK_SEND_STR(&s->sout, http_content_type_html); + } else if (strncmp(http_css, ptr, 4) == 0) { + PSOCK_SEND_STR(&s->sout, http_content_type_css); + } else if (strncmp(http_png, ptr, 4) == 0) { + PSOCK_SEND_STR(&s->sout, http_content_type_png); + } else if (strncmp(http_gif, ptr, 4) == 0) { + PSOCK_SEND_STR(&s->sout, http_content_type_gif); + } else if (strncmp(http_jpg, ptr, 4) == 0) { + PSOCK_SEND_STR(&s->sout, http_content_type_jpg); + } else { + PSOCK_SEND_STR(&s->sout, http_content_type_plain); + } + } + PSOCK_END(&s->sout); +} +static PT_THREAD(send_headers(struct httpd_state *s, const char *statushdr)) +{ + return send_headers_3(s, statushdr, 1); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(handle_output(struct httpd_state *s)) +{ + PT_BEGIN(&s->outputpt); + + if (s->method == POST) { + if (strcmp(s->filename, "/command") == 0) { + DEBUG_PRINTF("Executed command post\n"); + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200)); + // send response as we get it + PT_WAIT_THREAD(&s->outputpt, send_command_response(s)); + + } else if (strcmp(s->filename, "/command_silent") == 0) { + DEBUG_PRINTF("Executed silent command post\n"); + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200)); + + } else if (strcmp(s->filename, "/upload") == 0) { + DEBUG_PRINTF("upload output: %d\n", s->uploadok); + if (s->uploadok == 0) { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_503)); + PSOCK_SEND_STR(&s->sout, "FAILED\r\n"); + } else { + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200)); + PSOCK_SEND_STR(&s->sout, "OK\r\n"); + } + + } else { + DEBUG_PRINTF("Unknown POST: %s\n", s->filename); + httpd_fs_open(http_404_html, &s->file); + strcpy(s->filename, http_404_html); + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404)); + PT_WAIT_THREAD(&s->outputpt, send_file(s)); + } + + } else { + // Presume method GET + if (!fs_open(s)) { // Note this has the side effect of opening the file + DEBUG_PRINTF("404 file not found\n"); + httpd_fs_open(http_404_html, &s->file); + strcpy(s->filename, http_404_html); + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404)); + PT_WAIT_THREAD(&s->outputpt, send_file(s)); + + } else if (s->cache_page) { + if (s->fd != NULL) { + // if it was an sd file then we need to close it + fclose(s->fd); + s->fd = NULL; + } + // tell it it has not changed + DEBUG_PRINTF("304 Not Modified\n"); + PT_WAIT_THREAD(&s->outputpt, send_headers_3(s, http_header_304, 0)); + + } else { + DEBUG_PRINTF("sending file %s\n", s->filename); + PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200)); + if (s->fd != NULL) { + // send from sd card + PT_WAIT_THREAD(&s->outputpt, send_sd_file(s)); + + } else { + // send from FLASH + PT_WAIT_THREAD(&s->outputpt, send_file(s)); + } + } + } + + PSOCK_CLOSE(&s->sout); + PT_END(&s->outputpt); +} + +/*---------------------------------------------------------------------------*/ +// this forces us to yield every other call as we read all data everytime +static char has_newdata(struct httpd_state *s) +{ + if (s->upload_state == 1) { + /* All data in uip_appdata buffer already consumed. */ + s->upload_state = 0; + return 0; + } else if (uip_newdata()) { + /* There is new data that has not been consumed. */ + return 1; + } else { + /* There is no new data. */ + return 0; + } +} + +/* + * handle trhe uploaded data, as there may be part of that buffer still in the last packet buffer + * write that first from the buf/len parameters + */ +static PT_THREAD(handle_uploaded_data(struct httpd_state *s, uint8_t *buf, int len)) +{ + PT_BEGIN(&s->inputpt); + + DEBUG_PRINTF("Uploading file: %s, %d\n", s->upload_name, s->content_length); + + // The body is the raw data to be stored to the file + if (!open_file(s->upload_name)) { + DEBUG_PRINTF("failed to open file\n"); + s->uploadok = 0; + PT_EXIT(&s->inputpt); + } + + DEBUG_PRINTF("opened file: %s\n", s->upload_name); + + if (len > 0) { + // write the first part of the buffer + if (!save_file(buf, len)) { + DEBUG_PRINTF("initial write failed\n"); + s->uploadok = 0; + PT_EXIT(&s->inputpt); + } + s->content_length -= len; + } + + s->upload_state = 1; // first time through we need to yield to get new data + + // save the entire input buffer + while (s->content_length > 0) { + PT_WAIT_UNTIL(&s->inputpt, has_newdata(s)); + s->upload_state = 1; + + u8_t *readptr = (u8_t *)uip_appdata; + int readlen = uip_datalen(); + //DEBUG_PRINTF("read %d bytes of data\n", readlen); + + if (readlen > 0) { + if (!save_file(readptr, readlen)) { + DEBUG_PRINTF("write failed\n"); + s->uploadok = 0; + PT_EXIT(&s->inputpt); + } + s->content_length -= readlen; + } + } + + close_file(); + s->uploadok = 1; + DEBUG_PRINTF("finished upload\n"); + + PT_END(&s->inputpt); +} +/*---------------------------------------------------------------------------*/ +static +PT_THREAD(handle_input(struct httpd_state *s)) +{ + PSOCK_BEGIN(&s->sin); + + PSOCK_READTO(&s->sin, ISO_space); + + if (strncmp(s->inputbuf, http_get, 4) == 0) { + s->method = GET; + } else if (strncmp(s->inputbuf, http_post, 4) == 0) { + s->method = POST; + } else { + DEBUG_PRINTF("Unexpected method: %s\n", s->inputbuf); + PSOCK_CLOSE_EXIT(&s->sin); + } + + DEBUG_PRINTF("Method: %s\n", s->method == POST ? "POST" : "GET"); + + PSOCK_READTO(&s->sin, ISO_space); + + if (s->inputbuf[0] != ISO_slash) { + PSOCK_CLOSE_EXIT(&s->sin); + } + + if (s->inputbuf[1] == ISO_space) { + strncpy(s->filename, http_index_html, sizeof(s->filename)); + } else { + s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; + strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename)); + } + + DEBUG_PRINTF("filename: %s\n", s->filename); + + /* httpd_log_file(uip_conn->ripaddr, s->filename);*/ + + s->state = STATE_HEADERS; + s->content_length = 0; + s->cache_page = 0; + while (1) { + if (s->state == STATE_HEADERS) { + // read the headers of the request + PSOCK_READTO(&s->sin, ISO_nl); + s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; + if (s->inputbuf[0] == '\r') { + DEBUG_PRINTF("end of headers\n"); + if (s->method == GET) { + s->state = STATE_OUTPUT; + break; + } else if (s->method == POST) { + if (strcmp(s->filename, "/upload") == 0) { + s->state = STATE_UPLOAD; + } else { + s->state = STATE_BODY; + } + } + } else { + DEBUG_PRINTF("reading header: %s\n", s->inputbuf); + // handle headers here + if (strncmp(s->inputbuf, http_content_length, sizeof(http_content_length) - 1) == 0) { + s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0; + s->content_length = atoi(&s->inputbuf[sizeof(http_content_length) - 1]); + DEBUG_PRINTF("Content length= %s, %d\n", &s->inputbuf[sizeof(http_content_length) - 1], s->content_length); + + } else if (strncmp(s->inputbuf, "X-Filename: ", 11) == 0) { + s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0; + strncpy(s->upload_name, &s->inputbuf[12], sizeof(s->upload_name) - 1); + DEBUG_PRINTF("Upload name= %s\n", s->upload_name); + + } else if (strncmp(s->inputbuf, http_cache_control, sizeof(http_cache_control) - 1) == 0) { + s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0; + s->cache_page = strncmp(http_no_cache, &s->inputbuf[sizeof(http_cache_control) - 1], sizeof(http_no_cache) - 1) != 0; + DEBUG_PRINTF("cache page= %d\n", s->cache_page); + } + } + + } else if (s->state == STATE_BODY) { + if (s->method == POST && strcmp(s->filename, "/command") == 0) { + // create a callback stream and fifo for the results as it is a command + create_callback_stream(s); + + } else if (s->method == POST && strcmp(s->filename, "/command_silent") == 0) { + // stick the command on the command queue specifying null output stream + s->pstream = NULL; + + } else { // unknown POST + DEBUG_PRINTF("Unknown Post URL: %s\n", s->filename); + s->state = STATE_OUTPUT; + break; + } + s->command_count= 0; + // read the Body of the request, each line is a command + if (s->content_length > 0) { + DEBUG_PRINTF("start reading body %d...\n", s->content_length); + while (s->content_length > 2) { + PSOCK_READTO(&s->sin, ISO_nl); + s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; + s->content_length -= PSOCK_DATALEN(&s->sin); + // stick the command on the command queue, with this connections stream output + DEBUG_PRINTF("Adding command: %s, left: %d\n", s->inputbuf, s->content_length); + network_add_command(s->inputbuf, s->pstream); + s->command_count++; // count number of command lines we submit + } + DEBUG_PRINTF("Read body done\n"); + s->state = STATE_OUTPUT; + + } else { + s->state = STATE_OUTPUT; + } + break; + + } else if (s->state == STATE_UPLOAD) { + PSOCK_WAIT_THREAD(&s->sin, handle_uploaded_data(s, PSOCK_GET_START_OF_REST_OF_BUFFER(&s->sin), PSOCK_GET_LENGTH_OF_REST_OF_BUFFER(&s->sin))); + PSOCK_MARK_BUFFER_READ(&s->sin); + s->state = STATE_OUTPUT; + break; + + } else { + DEBUG_PRINTF("WTF State: %d", s->state); + break; + } + } + + PSOCK_END(&s->sin); +} +/*---------------------------------------------------------------------------*/ +static void +handle_connection(struct httpd_state *s) +{ + if (s->state != STATE_OUTPUT) { + handle_input(s); + } + if (s->state == STATE_OUTPUT) { + handle_output(s); + } +} +/*---------------------------------------------------------------------------*/ +void +httpd_appcall(void) +{ + struct httpd_state *s = (struct httpd_state *)(uip_conn->appstate); + + if (uip_connected()) { + s = malloc(sizeof(struct httpd_state)); + if (s == NULL) { + DEBUG_PRINTF("Connection: Out of memory\n"); + uip_abort(); + return; + } + uip_conn->appstate = s; + DEBUG_PRINTF("Connection: %d.%d.%d.%d:%d\n", + uip_ipaddr1(uip_conn->ripaddr), uip_ipaddr2(uip_conn->ripaddr), + uip_ipaddr3(uip_conn->ripaddr), uip_ipaddr4(uip_conn->ripaddr), + HTONS(uip_conn->rport)); + + PSOCK_INIT(&s->sin, s->inputbuf, sizeof(s->inputbuf) - 1); + PSOCK_INIT(&s->sout, s->inputbuf, sizeof(s->inputbuf) - 1); + PT_INIT(&s->outputpt); + PT_INIT(&s->inputpt); + s->state = STATE_WAITING; + /* timer_set(&s->timer, CLOCK_SECOND * 100);*/ + s->timer = 0; + s->fd = NULL; + s->strbuf = NULL; + s->fifo = NULL; + s->pstream = NULL; + } + + if (s == NULL) { + DEBUG_PRINTF("ERROR no state context: %d\n", uip_flags); + uip_abort(); + return; + } + + // check for timeout on connection here so we can cleanup if we abort + if (uip_poll()) { + ++s->timer; + if (s->timer >= 20 * 2) { // we have a 0.5 second poll and we want 20 second timeout + DEBUG_PRINTF("Timer expired, aborting\n"); + uip_abort(); + } + } else { + s->timer = 0; + } + + if (uip_closed() || uip_aborted() || uip_timedout()) { + DEBUG_PRINTF("Closing connection: %d\n", HTONS(uip_conn->rport)); + if (s->fd != NULL) fclose(fd); // clean up + if (s->strbuf != NULL) free(s->strbuf); + if (s->pstream != NULL) { + // free these if they were allocated + delete_fifo(s->fifo); + delete_callback_stream(s->pstream); // this will mark it as closed and will get deleted when no longer needed + } + free(s) ; + uip_conn->appstate = NULL; + + } else { + handle_connection(s); + } +} + +/*---------------------------------------------------------------------------*/ +/** + * \brief Initialize the web server + * + * This function initializes the web server and should be + * called at system boot-up. + */ +void httpd_init(void) +{ + uip_listen(HTONS(80)); +} +/*---------------------------------------------------------------------------*/ +/** @} */