lwip-contrib-mac/apps/httpserver_raw/httpd.c
2010-01-25 08:20:46 +00:00

489 lines
14 KiB
C

/*
* 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 <adam@sics.se>
* Simon Goldschmidt
*
*/
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "httpd.h"
#include "lwip/tcp.h"
#include "fs.h"
#include <string.h>
#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 (struct http_state *)memp_malloc(MEMP_HTTPD_STATE);
#else /* HTTPD_USE_MEM_POOL */
return (struct http_state *)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=%p hs=%p left=%d\n", (void*)pcb,
(void*)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=%p\n", lwip_strerr(err), (void*)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=%p hs=%p left=%d\n", (void*)pcb,
(void*)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 = (u16_t)LWIP_MIN(snd_buf, hs->left);
if (hs->left <= snd_buf) {
LWIP_ASSERT("hs->left did not fit into u16_t!", len == hs->left);
}
#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 = (char*)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 length must be positive!", file.len >= 0);
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 = (struct http_state *)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 = (struct http_state *)arg;
LWIP_DEBUGF(HTTPD_DEBUG, ("http_sent: pcb=%p hs=%p len=%"U16_F"\n",
(void*)pcb, (void*)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 = (struct http_state *)arg;
LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: pcb=%p hs=%p pcb_state=%s\n",
(void*)pcb, (void*)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 = (struct http_state *)arg;
LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void*)pcb,
(void*)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 %"S32_F"\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", 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);
}