/* * Copyright (c) 2001-2003 Swedish Institute of Computer Science. * 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. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 lwIP TCP/IP stack. * * Author: Adam Dunkels * Simon Goldschmidt * */ #include "lwip/debug.h" #include "lwip/stats.h" #include "httpd.h" #include "lwip/tcp.h" #include "fs.h" #include #ifndef HTTPD_DEBUG #define HTTPD_DEBUG LWIP_DBG_OFF #endif /** Set this to 1 and add the next line to lwippools.h to use a memp pool * for allocating struct http_state instead of the heap: * * LWIP_MEMPOOL(HTTPD_STATE, 20, 100, "HTTPD_STATE") */ #ifndef HTTPD_USE_MEM_POOL #define HTTPD_USE_MEM_POOL 0 #endif /** The server port for HTTPD to use */ #define HTTPD_SERVER_PORT 80 /** Maximum retries before the connection is aborted/closed. * - number of times pcb->poll is called -> default is 4*500ms = 2s; * - reset when pcb->sent is called */ #define HTTPD_MAX_RETRIES 4 /** The poll delay is X*500ms */ #define HTTPD_POLL_INTERVAL 4 /** An u16_t telling us how much data to pass to tcp_write at maximum * Define HTTPD_USE_MAX_SEND_LIMIT to 1 to use this */ #define HTTPD_MAX_SEND_LIMIT(http_state) 0xffff /** If 1 HTTPD_MAX_SEND_LIMIT() sets an upper limit to the bytes passed to tcp_writes */ #define HTTPD_USE_MAX_SEND_LIMIT 0 /** An u8_t telling us if we have to copy the file when enqueueing (1) or not (0)*/ #if HTTPD_SUPPORT_DYNAMIC_PAGES #define HTTPD_FILE_IS_VOLATILE(http_state) ((http_state)->file_orig != NULL) #else #define HTTPD_FILE_IS_VOLATILE(http_state) 0 #endif /* HTTPD_SUPPORT_DYNAMIC_PAGES */ /** Track sent bytes for debug purposes */ #define HTTPD_TRACK_SENT_BYTES 0 /** Priority for tcp pcbs created by HTTPD */ #define HTTPD_TCP_PRIO TCP_PRIO_MIN struct http_state { s32_t left; #if HTTPD_TRACK_SENT_BYTES u32_t file_size; u32_t sent_total; #endif /* HTTPD_TRACK_SENT_BYTES */ const unsigned char *file; #if HTTPD_SUPPORT_DYNAMIC_PAGES const unsigned char *file_orig; #endif /* HTTPD_SUPPORT_DYNAMIC_PAGES */ u8_t retries; }; static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len); static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err); /** Allocate a struct http_state. */ static struct http_state* http_state_alloc() { #if HTTPD_USE_MEM_POOL return memp_malloc(MEMP_HTTPD_STATE); #else /* HTTPD_USE_MEM_POOL */ return mem_malloc(sizeof(struct http_state)); #endif /* HTTPD_USE_MEM_POOL */ } /** Free a struct http_state. * Also frees the file data if dynamic. */ static void http_state_free(struct http_state *hs) { if (hs != NULL) { #if HTTPD_SUPPORT_DYNAMIC_PAGES if (hs->file_orig != NULL) { mem_free((void*)hs->file_orig); } #endif /* HTTPD_SUPPORT_DYNAMIC_PAGES */ #if HTTPD_USE_MEM_POOL memp_free(MEMP_HTTPD_STATE, hs); #else /* HTTPD_USE_MEM_POOL */ mem_free(hs); #endif /* HTTPD_USE_MEM_POOL */ } } /** * The connection shall be actively closed. * Reset the sent- and recv-callbacks. */ static void http_close_conn(struct tcp_pcb *pcb, struct http_state *hs) { err_t err; LWIP_DEBUGF(HTTPD_DEBUG, ("http_close: pcb=0x%08X hs=0x%08X left=%d\n", pcb, hs, hs != NULL ? hs->left : 0)); tcp_recv(pcb, NULL); err = tcp_close(pcb); if (err != ERR_OK) { /* closing failed, try again later */ LWIP_DEBUGF(HTTPD_DEBUG, ("Error %s closing pcb=0x%08X\n", lwip_strerr(err), pcb)); tcp_recv(pcb, http_recv); } else { /* closing succeeded */ tcp_arg(pcb, NULL); tcp_poll(pcb, NULL, 0); tcp_sent(pcb, NULL); if (hs != NULL) { http_state_free(hs); } } } /** * Try to send more data on this pcb. */ static void http_send_data(struct tcp_pcb *pcb, struct http_state *hs) { err_t err; u16_t len; u16_t snd_buf; LWIP_DEBUGF(HTTPD_DEBUG, ("http_send_data: pcb=0x%08X hs=0x%08X left=%d\n", pcb, hs, hs != NULL ? hs->left : 0)); if ((hs == NULL) || (hs->left == 0)) { /* Already closed or nothing more to send; be robust: close */ http_close_conn(pcb, hs); return; } /* We cannot send more data than space available in the send buffer. */ snd_buf = tcp_sndbuf(pcb); len = LWIP_MIN(snd_buf, hs->left); if (hs->left <= snd_buf) { LWIP_ASSERT((len == hs->left), "hs->left did not fit into u16_t!"); } #if HTTPD_USE_MAX_SEND_LIMIT /* upper send limit */ if (len > HTTPD_MAX_SEND_LIMIT(hs)) { len = HTTPD_MAX_SEND_LIMIT(hs); } #endif /* HTTPD_USE_MAX_SEND_LIMIT */ do { err = tcp_write(pcb, hs->file, len, HTTPD_FILE_IS_VOLATILE(hs)); LWIP_DEBUGF(HTTPD_DEBUG, ("http_send_data: tcp_write(%d) -> %s\n", len, lwip_strerr(err))); if (err == ERR_MEM) { len /= 2; } } while ((err == ERR_MEM) && (len > 1)); if (err == ERR_OK) { hs->file += len; LWIP_ASSERT((hs->left >= len), "hs->left >= len"); hs->left -= len; } if (hs->left <= 0) { LWIP_DEBUGF(HTTPD_DEBUG, ("http_send_data: file finished, closing\n")); http_close_conn(pcb, hs); } } /** * When data has been received in the correct state, try to parse it * as a HTTP request. * * @param p the received pbuf * @param hs the connection state * @return ERR_OK if request was OK and hs has been initialized correctly * another err_t otherwise */ static err_t http_parse_request(struct pbuf *p, struct http_state *hs) { int i; err_t request_supported; char *data; struct fs_file file; const char* filename = NULL; data = p->payload; /* default is request not supported */ request_supported = ERR_ARG; /* @todo: support POST, check p->len */ if (strncmp(data, "GET ", 4) == 0) { request_supported = ERR_OK; for(i = 0; i < 40; i++) { if (((char *)data + 4)[i] == ' ' || ((char *)data + 4)[i] == '\r' || ((char *)data + 4)[i] == '\n') { ((char *)data + 4)[i] = 0; } } } if (request_supported == ERR_OK) { if (*(char *)(data + 4) == '/' && *(char *)(data + 5) == 0) { /* root -> index.html */ /* @todo: trailing / -> /../index.html */ filename = "/index.html"; } else { /* @todo: filter out hostname (valid request!) */ filename = (const char *)data + 4; } } else { /* invalid request/not implemented */ filename = "/501.html"; } LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: GET -> %s\n", filename)); if (!fs_open(filename, &file)) { if(!fs_open("/404.html", &file)) { /* Be robusts, don't assert here although it's a misconfiguration */ LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: GET: /404.html not found!\n")); return ERR_ABRT; } LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: GET -> not found -> /404.html\n")); } hs->file = file.data; #if HTTPD_SUPPORT_DYNAMIC_PAGES /* @todo: if file was created dynamically and must be freed after sending: */ /*hs->file_orig = hs->file;*/ #endif /* HTTPD_SUPPORT_DYNAMIC_PAGES */ LWIP_ASSERT((file.len >= 0), "File length must be positive!"); hs->left = file.len; #if HTTPD_TRACK_SENT_BYTES hs->file_size = file.len; #endif /* HTTPD_TRACK_SENT_BYTES */ return ERR_OK; } /** * The pcb had an error and is already deallocated. * The argument might still be valid (if != NULL). */ static void http_err(void *arg, err_t err) { struct http_state *hs = arg; LWIP_UNUSED_ARG(err); LWIP_DEBUGF(HTTPD_DEBUG, ("http_err: %s", lwip_strerr(err))); if (hs != NULL) { http_state_free(hs); } } /** * Data has been sent and acknowledged by the remote host. * This means that more data can be sent. */ static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len) { struct http_state *hs = arg; LWIP_DEBUGF(HTTPD_DEBUG, ("http_sent: pcb=0x%08X hs=0x%08X len=%d\n", pcb, hs, len)); LWIP_UNUSED_ARG(len); if (hs == NULL) { /* this should not happen, but just to be robust... */ return ERR_OK; } #if HTTPD_TRACK_SENT_BYTES hs->sent_total += len; LWIP_DEBUGF(HTTPD_DEBUG, ("http_sent: sent_total=%d, file size=%d\n", hs->sent_total, hs->file_size)); if(hs->sent_total > hs->file_size) { LWIP_DEBUGF(HTTPD_DEBUG, ("http_sent: sent %d bytes too much!\n", hs->sent_total - hs->file_size)); } #endif /* HTTPD_TRACK_SENT_BYTES */ /* reset retry counter */ hs->retries = 0; if (hs->left > 0) { LWIP_DEBUGF(HTTPD_DEBUG, ("http_sent: %d bytes left, calling http_send_data\n", hs->left)); http_send_data(pcb, hs); } else { /* this should normally not happen, print to be robust */ LWIP_DEBUGF(HTTPD_DEBUG, ("http_sent: no bytes left\n")); http_close_conn(pcb, hs); } return ERR_OK; } /** * The poll function is called every 2nd second. * If there has been no data sent (which resets the retries) in 8 seconds, close. * If the last portion of a file has not been sent in 2 seconds, close. * * This could be increased, but we don't want to waste resources for bad connections. */ static err_t http_poll(void *arg, struct tcp_pcb *pcb) { struct http_state *hs = arg; LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: pcb=0x%08X hs=0x%08X pcb_state=%s\n", pcb, hs, tcp_debug_state_str(pcb->state))); if (hs == NULL) { if (pcb->state == ESTABLISHED) { /* arg is null, close. */ LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: arg is NULL, close\n")); http_close_conn(pcb, hs); return ERR_ABRT; } } else { hs->retries++; if (hs->retries == HTTPD_MAX_RETRIES) { LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n")); http_close_conn(pcb, hs); return ERR_ABRT; } LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: try to send more data\n")); http_send_data(pcb, hs); } return ERR_OK; } /** * Data has been received on this pcb. * For HTTP 1.0, this should normally only happen once (if the request fits in one packet). */ static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { err_t parsed = ERR_ABRT; struct http_state *hs = arg; LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: pcb=0x%08X pbuf=0x%08X err=%s\n", pcb, p, lwip_strerr(err))); if (p != NULL) { /* Inform TCP that we have taken the data. */ tcp_recved(pcb, p->tot_len); } if (hs == NULL) { /* be robust */ LWIP_DEBUGF(HTTPD_DEBUG, ("Error, http_recv: hs is NULL, abort\n")); http_close_conn(pcb, hs); return ERR_OK; } if ((err != ERR_OK) || (p == NULL)) { /* error or closed by other side */ if (p != NULL) { pbuf_free(p); } http_close_conn(pcb, hs); return ERR_OK; } if (hs->file == NULL) { parsed = http_parse_request(p, hs); } else { LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n")); } pbuf_free(p); if (parsed == ERR_OK) { LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: data %p len %ld\n", hs->file, hs->left)); http_send_data(pcb, hs); } else if (parsed == ERR_ABRT) { http_close_conn(pcb, hs); return ERR_OK; } return ERR_OK; } /** * A new incoming connection has been accepted. */ static err_t http_accept(void *arg, struct tcp_pcb *pcb, err_t err) { struct http_state *hs; struct tcp_pcb_listen *lpcb = (struct tcp_pcb_listen*)arg; LWIP_UNUSED_ARG(err); LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept(%p)\n", arg)); /* Decrease the listen backlog counter */ tcp_accepted(lpcb); tcp_setprio(pcb, HTTPD_TCP_PRIO); /* Allocate memory for the structure that holds the state of the connection. */ hs = http_state_alloc(); if (hs == NULL) { LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept: Out of memory, RST\n")); return ERR_MEM; } /* Initialize the structure. */ hs->file = NULL; #if HTTPD_SUPPORT_DYNAMIC_PAGES hs->file_orig = NULL; #endif /* HTTPD_SUPPORT_DYNAMIC_PAGES */ hs->left = 0; hs->retries = 0; #if HTTPD_TRACK_SENT_BYTES hs->sent_total = 0; #endif /* HTTPD_TRACK_SENT_BYTES */ /* Tell TCP that this is the structure we wish to be passed for our callbacks. */ tcp_arg(pcb, hs); /* Set up the various callback functions */ tcp_recv(pcb, http_recv); tcp_err(pcb, http_err); tcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL); tcp_sent(pcb, http_sent); return ERR_OK; } /** * Initialize the httpd: set up a listening PCB and bind it to the defined port */ void httpd_init(void) { struct tcp_pcb *pcb; err_t err; pcb = tcp_new(); LWIP_ASSERT(("httpd_init: tcp_new failed"), pcb != NULL); err = tcp_bind(pcb, IP_ADDR_ANY, HTTPD_SERVER_PORT); LWIP_ASSERT(("httpd_init: tcp_bind failed: %s", lwip_strerr(err)), err == ERR_OK); pcb = tcp_listen(pcb); LWIP_ASSERT(("httpd_init: tcp_listen failed"), pcb != NULL); /* initialize callback arg and accept callback */ tcp_arg(pcb, pcb); tcp_accept(pcb, http_accept); }