mirror of
https://github.com/ep00ch/lwip-contrib-mac.git
synced 2024-10-06 09:58:44 +00:00
489 lines
14 KiB
C
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);
|
|
}
|
|
|