summaryrefslogtreecommitdiff
path: root/src/apps/http
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2024-10-16 08:10:14 -0600
committerTom Rini <[email protected]>2024-10-16 08:10:14 -0600
commitf3f86fd1fe0fb288356bff78f8a6fa2edf89e3fc (patch)
treef0a99ea87d92f63895a6d053e3185838ebecf2d0 /src/apps/http
Squashed 'lib/lwip/lwip/' content from commit 0a0452b2c39b
git-subtree-dir: lib/lwip/lwip git-subtree-split: 0a0452b2c39bdd91e252aef045c115f88f6ca773
Diffstat (limited to 'src/apps/http')
-rw-r--r--src/apps/http/altcp_proxyconnect.c584
-rw-r--r--src/apps/http/fs.c161
-rw-r--r--src/apps/http/fs/404.html21
-rw-r--r--src/apps/http/fs/img/sics.gifbin0 -> 724 bytes
-rw-r--r--src/apps/http/fs/index.html47
-rw-r--r--src/apps/http/fsdata.c336
-rw-r--r--src/apps/http/fsdata.h41
-rw-r--r--src/apps/http/http_client.c911
-rw-r--r--src/apps/http/httpd.c2770
-rw-r--r--src/apps/http/httpd_structs.h123
-rw-r--r--src/apps/http/makefsdata/makefsdata97
-rw-r--r--src/apps/http/makefsdata/makefsdata.c1307
-rw-r--r--src/apps/http/makefsdata/readme.txt23
-rw-r--r--src/apps/http/makefsdata/tinydir.h831
14 files changed, 7252 insertions, 0 deletions
diff --git a/src/apps/http/altcp_proxyconnect.c b/src/apps/http/altcp_proxyconnect.c
new file mode 100644
index 00000000000..3d5e7e837a7
--- /dev/null
+++ b/src/apps/http/altcp_proxyconnect.c
@@ -0,0 +1,584 @@
+/**
+ * @file
+ * Application layered TCP connection API that executes a proxy-connect.
+ *
+ * This file provides a starting layer that executes a proxy-connect e.g. to
+ * set up TLS connections through a http proxy.
+ */
+
+/*
+ * Copyright (c) 2018 Simon Goldschmidt
+ * 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: Simon Goldschmidt <[email protected]>
+ *
+ */
+
+#include "lwip/apps/altcp_proxyconnect.h"
+
+#if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/altcp.h"
+#include "lwip/priv/altcp_priv.h"
+
+#include "lwip/altcp_tcp.h"
+#include "lwip/altcp_tls.h"
+
+#include "lwip/mem.h"
+#include "lwip/init.h"
+
+#include <stdio.h>
+
+/** This string is passed in the HTTP header as "User-Agent: " */
+#ifndef ALTCP_PROXYCONNECT_CLIENT_AGENT
+#define ALTCP_PROXYCONNECT_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)"
+#endif
+
+#define ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED 0x01
+#define ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE 0x02
+
+typedef struct altcp_proxyconnect_state_s
+{
+ ip_addr_t outer_addr;
+ u16_t outer_port;
+ struct altcp_proxyconnect_config *conf;
+ u8_t flags;
+} altcp_proxyconnect_state_t;
+
+/* Variable prototype, the actual declaration is at the end of this file
+ since it contains pointers to static functions declared here */
+extern const struct altcp_functions altcp_proxyconnect_functions;
+
+/* memory management functions: */
+
+static altcp_proxyconnect_state_t *
+altcp_proxyconnect_state_alloc(void)
+{
+ altcp_proxyconnect_state_t *ret = (altcp_proxyconnect_state_t *)mem_calloc(1, sizeof(altcp_proxyconnect_state_t));
+ return ret;
+}
+
+static void
+altcp_proxyconnect_state_free(altcp_proxyconnect_state_t *state)
+{
+ LWIP_ASSERT("state != NULL", state != NULL);
+ mem_free(state);
+}
+
+/* helper functions */
+
+#define PROXY_CONNECT "CONNECT %s:%d HTTP/1.1\r\n" /* HOST, PORT */ \
+ "User-Agent: %s\r\n" /* User-Agent */\
+ "Proxy-Connection: keep-alive\r\n" \
+ "Connection: keep-alive\r\n" \
+ "\r\n"
+#define PROXY_CONNECT_FORMAT(host, port) PROXY_CONNECT, host, port, ALTCP_PROXYCONNECT_CLIENT_AGENT
+
+/* Format the http proxy connect request via snprintf */
+static int
+altcp_proxyconnect_format_request(char *buffer, size_t bufsize, const char *host, int port)
+{
+ return snprintf(buffer, bufsize, PROXY_CONNECT_FORMAT(host, port));
+}
+
+/* Create and send the http proxy connect request */
+static err_t
+altcp_proxyconnect_send_request(struct altcp_pcb *conn)
+{
+ int len, len2;
+ mem_size_t alloc_len;
+ char *buffer, *host;
+ altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state;
+
+ if (!state) {
+ return ERR_VAL;
+ }
+ /* Use printf with zero length to get the required allocation size */
+ len = altcp_proxyconnect_format_request(NULL, 0, "", state->outer_port);
+ if (len < 0) {
+ return ERR_VAL;
+ }
+ /* add allocation size for IP address strings */
+#if LWIP_IPV6
+ len += 40; /* worst-case IPv6 address length */
+#else
+ len += 16; /* worst-case IPv4 address length */
+#endif
+ alloc_len = (mem_size_t)len;
+ if ((len < 0) || (int)alloc_len != len) {
+ /* overflow */
+ return ERR_MEM;
+ }
+ /* Allocate a buffer for the request string */
+ buffer = (char *)mem_malloc(alloc_len);
+ if (buffer == NULL) {
+ return ERR_MEM;
+ }
+ host = ipaddr_ntoa(&state->outer_addr);
+ len2 = altcp_proxyconnect_format_request(buffer, alloc_len, host, state->outer_port);
+ if ((len2 > 0) && (len2 <= len) && (len2 <= 0xFFFF)) {
+ err_t err = altcp_write(conn->inner_conn, buffer, (u16_t)len2, TCP_WRITE_FLAG_COPY);
+ if (err != ERR_OK) {
+ /* @todo: abort? */
+ mem_free(buffer);
+ return err;
+ }
+ }
+ mem_free(buffer);
+ return ERR_OK;
+}
+
+/* callback functions from inner/lower connection: */
+
+/** Connected callback from lower connection (i.e. TCP).
+ * Not really implemented/tested yet...
+ */
+static err_t
+altcp_proxyconnect_lower_connected(void *arg, struct altcp_pcb *inner_conn, err_t err)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn && conn->state) {
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ /* upper connected is called when handshake is done */
+ if (err != ERR_OK) {
+ if (conn->connected) {
+ if (conn->connected(conn->arg, conn, err) == ERR_ABRT) {
+ return ERR_ABRT;
+ }
+ return ERR_OK;
+ }
+ }
+ /* send proxy connect request here */
+ return altcp_proxyconnect_send_request(conn);
+ }
+ return ERR_VAL;
+}
+
+/** Recv callback from lower connection (i.e. TCP)
+ * This one mainly differs between connection setup (wait for proxy OK string)
+ * and application phase (data is passed on to the application).
+ */
+static err_t
+altcp_proxyconnect_lower_recv(void *arg, struct altcp_pcb *inner_conn, struct pbuf *p, err_t err)
+{
+ altcp_proxyconnect_state_t *state;
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+
+ LWIP_ASSERT("no err expected", err == ERR_OK);
+ LWIP_UNUSED_ARG(err);
+
+ if (!conn) {
+ /* no connection given as arg? should not happen, but prevent pbuf/conn leaks */
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+ altcp_close(inner_conn);
+ return ERR_CLSD;
+ }
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ if (!state) {
+ /* already closed */
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+ altcp_close(inner_conn);
+ return ERR_CLSD;
+ }
+ if (state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE) {
+ /* application phase, just pass this through */
+ if (conn->recv) {
+ return conn->recv(conn->arg, conn, p, err);
+ }
+ pbuf_free(p);
+ return ERR_OK;
+ } else {
+ /* setup phase */
+ /* handle NULL pbuf (inner connection closed) */
+ if (p == NULL) {
+ if (altcp_close(conn) != ERR_OK) {
+ altcp_abort(conn);
+ return ERR_ABRT;
+ }
+ return ERR_OK;
+ } else {
+ /* @todo: parse setup phase rx data
+ for now, we just wait for the end of the header... */
+ u16_t idx = pbuf_memfind(p, "\r\n\r\n", 4, 0);
+ altcp_recved(inner_conn, p->tot_len);
+ pbuf_free(p);
+ if (idx != 0xFFFF) {
+ state->flags |= ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE;
+ if (conn->connected) {
+ return conn->connected(conn->arg, conn, ERR_OK);
+ }
+ }
+ return ERR_OK;
+ }
+ }
+}
+
+/** Sent callback from lower connection (i.e. TCP)
+ * This only informs the upper layer to try to send more, not about
+ * the number of ACKed bytes.
+ */
+static err_t
+altcp_proxyconnect_lower_sent(void *arg, struct altcp_pcb *inner_conn, u16_t len)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ LWIP_UNUSED_ARG(len);
+ if (conn) {
+ altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state;
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (!state || !(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) {
+ /* @todo: do something here? */
+ return ERR_OK;
+ }
+ /* pass this on to upper sent */
+ if (conn->sent) {
+ return conn->sent(conn->arg, conn, len);
+ }
+ }
+ return ERR_OK;
+}
+
+/** Poll callback from lower connection (i.e. TCP)
+ * Just pass this on to the application.
+ * @todo: retry sending?
+ */
+static err_t
+altcp_proxyconnect_lower_poll(void *arg, struct altcp_pcb *inner_conn)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn) {
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (conn->poll) {
+ return conn->poll(conn->arg, conn);
+ }
+ }
+ return ERR_OK;
+}
+
+static void
+altcp_proxyconnect_lower_err(void *arg, err_t err)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn) {
+ conn->inner_conn = NULL; /* already freed */
+ if (conn->err) {
+ conn->err(conn->arg, err);
+ }
+ altcp_free(conn);
+ }
+}
+
+
+/* setup functions */
+
+static void
+altcp_proxyconnect_setup_callbacks(struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
+{
+ altcp_arg(inner_conn, conn);
+ altcp_recv(inner_conn, altcp_proxyconnect_lower_recv);
+ altcp_sent(inner_conn, altcp_proxyconnect_lower_sent);
+ altcp_err(inner_conn, altcp_proxyconnect_lower_err);
+ /* tcp_poll is set when interval is set by application */
+ /* listen is set totally different :-) */
+}
+
+static err_t
+altcp_proxyconnect_setup(struct altcp_proxyconnect_config *config, struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
+{
+ altcp_proxyconnect_state_t *state;
+ if (!config) {
+ return ERR_ARG;
+ }
+ LWIP_ASSERT("invalid inner_conn", conn != inner_conn);
+
+ /* allocate proxyconnect context */
+ state = altcp_proxyconnect_state_alloc();
+ if (state == NULL) {
+ return ERR_MEM;
+ }
+ state->flags = 0;
+ state->conf = config;
+ altcp_proxyconnect_setup_callbacks(conn, inner_conn);
+ conn->inner_conn = inner_conn;
+ conn->fns = &altcp_proxyconnect_functions;
+ conn->state = state;
+ return ERR_OK;
+}
+
+/** Allocate a new altcp layer connecting through a proxy.
+ * This function gets the inner pcb passed.
+ *
+ * @param config struct altcp_proxyconnect_config that contains the proxy settings
+ * @param inner_pcb pcb that makes the connection to the proxy (i.e. tcp pcb)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_new(struct altcp_proxyconnect_config *config, struct altcp_pcb *inner_pcb)
+{
+ struct altcp_pcb *ret;
+ if (inner_pcb == NULL) {
+ return NULL;
+ }
+ ret = altcp_alloc();
+ if (ret != NULL) {
+ if (altcp_proxyconnect_setup(config, ret, inner_pcb) != ERR_OK) {
+ altcp_free(ret);
+ return NULL;
+ }
+ }
+ return ret;
+}
+
+/** Allocate a new altcp layer connecting through a proxy.
+ * This function allocates the inner pcb as tcp pcb, resulting in a direct tcp
+ * connection to the proxy.
+ *
+ * @param config struct altcp_proxyconnect_config that contains the proxy settings
+ * @param ip_type IP type of the connection (@ref lwip_ip_addr_type)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_new_tcp(struct altcp_proxyconnect_config *config, u8_t ip_type)
+{
+ struct altcp_pcb *inner_pcb, *ret;
+
+ /* inner pcb is tcp */
+ inner_pcb = altcp_tcp_new_ip_type(ip_type);
+ if (inner_pcb == NULL) {
+ return NULL;
+ }
+ ret = altcp_proxyconnect_new(config, inner_pcb);
+ if (ret == NULL) {
+ altcp_close(inner_pcb);
+ }
+ return ret;
+}
+
+/** Allocator function to allocate a proxy connect altcp pcb connecting directly
+ * via tcp to the proxy.
+ *
+ * The returned pcb is a chain: altcp_proxyconnect - altcp_tcp - tcp pcb
+ *
+ * This function is meant for use with @ref altcp_new.
+ *
+ * @param arg struct altcp_proxyconnect_config that contains the proxy settings
+ * @param ip_type IP type of the connection (@ref lwip_ip_addr_type)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_alloc(void *arg, u8_t ip_type)
+{
+ return altcp_proxyconnect_new_tcp((struct altcp_proxyconnect_config *)arg, ip_type);
+}
+
+
+#if LWIP_ALTCP_TLS
+
+/** Allocator function to allocate a TLS connection through a proxy.
+ *
+ * The returned pcb is a chain: altcp_tls - altcp_proxyconnect - altcp_tcp - tcp pcb
+ *
+ * This function is meant for use with @ref altcp_new.
+ *
+ * @param arg struct altcp_proxyconnect_tls_config that contains the proxy settings
+ * and tls settings
+ * @param ip_type IP type of the connection (@ref lwip_ip_addr_type)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_tls_alloc(void *arg, u8_t ip_type)
+{
+ struct altcp_proxyconnect_tls_config *cfg = (struct altcp_proxyconnect_tls_config *)arg;
+ struct altcp_pcb *proxy_pcb;
+ struct altcp_pcb *tls_pcb;
+
+ proxy_pcb = altcp_proxyconnect_new_tcp(&cfg->proxy, ip_type);
+ tls_pcb = altcp_tls_wrap(cfg->tls_config, proxy_pcb);
+
+ if (tls_pcb == NULL) {
+ altcp_close(proxy_pcb);
+ }
+ return tls_pcb;
+}
+#endif /* LWIP_ALTCP_TLS */
+
+/* "virtual" functions */
+static void
+altcp_proxyconnect_set_poll(struct altcp_pcb *conn, u8_t interval)
+{
+ if (conn != NULL) {
+ altcp_poll(conn->inner_conn, altcp_proxyconnect_lower_poll, interval);
+ }
+}
+
+static void
+altcp_proxyconnect_recved(struct altcp_pcb *conn, u16_t len)
+{
+ altcp_proxyconnect_state_t *state;
+ if (conn == NULL) {
+ return;
+ }
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state == NULL) {
+ return;
+ }
+ if (!(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) {
+ return;
+ }
+ altcp_recved(conn->inner_conn, len);
+}
+
+static err_t
+altcp_proxyconnect_connect(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port, altcp_connected_fn connected)
+{
+ altcp_proxyconnect_state_t *state;
+
+ if ((conn == NULL) || (ipaddr == NULL)) {
+ return ERR_VAL;
+ }
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state == NULL) {
+ return ERR_VAL;
+ }
+ if (state->flags & ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED) {
+ return ERR_VAL;
+ }
+ state->flags |= ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED;
+
+ conn->connected = connected;
+ /* connect to our proxy instead, but store the requested address and port */
+ ip_addr_copy(state->outer_addr, *ipaddr);
+ state->outer_port = port;
+
+ return altcp_connect(conn->inner_conn, &state->conf->proxy_addr, state->conf->proxy_port, altcp_proxyconnect_lower_connected);
+}
+
+static struct altcp_pcb *
+altcp_proxyconnect_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err)
+{
+ LWIP_UNUSED_ARG(conn);
+ LWIP_UNUSED_ARG(backlog);
+ LWIP_UNUSED_ARG(err);
+ /* listen not supported! */
+ return NULL;
+}
+
+static void
+altcp_proxyconnect_abort(struct altcp_pcb *conn)
+{
+ if (conn != NULL) {
+ if (conn->inner_conn != NULL) {
+ altcp_abort(conn->inner_conn);
+ }
+ altcp_free(conn);
+ }
+}
+
+static err_t
+altcp_proxyconnect_close(struct altcp_pcb *conn)
+{
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+ if (conn->inner_conn != NULL) {
+ err_t err = altcp_close(conn->inner_conn);
+ if (err != ERR_OK) {
+ /* closing inner conn failed, return the error */
+ return err;
+ }
+ }
+ /* no inner conn or closing it succeeded, deallocate myself */
+ altcp_free(conn);
+ return ERR_OK;
+}
+
+static err_t
+altcp_proxyconnect_write(struct altcp_pcb *conn, const void *dataptr, u16_t len, u8_t apiflags)
+{
+ altcp_proxyconnect_state_t *state;
+
+ LWIP_UNUSED_ARG(apiflags);
+
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state == NULL) {
+ /* @todo: which error? */
+ return ERR_CLSD;
+ }
+ if (!(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) {
+ /* @todo: which error? */
+ return ERR_VAL;
+ }
+ return altcp_write(conn->inner_conn, dataptr, len, apiflags);
+}
+
+static void
+altcp_proxyconnect_dealloc(struct altcp_pcb *conn)
+{
+ /* clean up and free tls state */
+ if (conn) {
+ altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state) {
+ altcp_proxyconnect_state_free(state);
+ conn->state = NULL;
+ }
+ }
+}
+const struct altcp_functions altcp_proxyconnect_functions = {
+ altcp_proxyconnect_set_poll,
+ altcp_proxyconnect_recved,
+ altcp_default_bind,
+ altcp_proxyconnect_connect,
+ altcp_proxyconnect_listen,
+ altcp_proxyconnect_abort,
+ altcp_proxyconnect_close,
+ altcp_default_shutdown,
+ altcp_proxyconnect_write,
+ altcp_default_output,
+ altcp_default_mss,
+ altcp_default_sndbuf,
+ altcp_default_sndqueuelen,
+ altcp_default_nagle_disable,
+ altcp_default_nagle_enable,
+ altcp_default_nagle_disabled,
+ altcp_default_setprio,
+ altcp_proxyconnect_dealloc,
+ altcp_default_get_tcp_addrinfo,
+ altcp_default_get_ip,
+ altcp_default_get_port
+#ifdef LWIP_DEBUG
+ , altcp_default_dbg_get_tcp_state
+#endif
+};
+
+#endif /* LWIP_ALTCP */
diff --git a/src/apps/http/fs.c b/src/apps/http/fs.c
new file mode 100644
index 00000000000..e12e314e200
--- /dev/null
+++ b/src/apps/http/fs.c
@@ -0,0 +1,161 @@
+/*
+ * 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 <[email protected]>
+ *
+ */
+
+#include "lwip/apps/httpd_opts.h"
+#include "lwip/def.h"
+#include "lwip/apps/fs.h"
+#include <string.h>
+
+
+#include HTTPD_FSDATA_FILE
+
+/*-----------------------------------------------------------------------------------*/
+err_t
+fs_open(struct fs_file *file, const char *name)
+{
+ const struct fsdata_file *f;
+
+ if ((file == NULL) || (name == NULL)) {
+ return ERR_ARG;
+ }
+
+#if LWIP_HTTPD_CUSTOM_FILES
+ if (fs_open_custom(file, name)) {
+ file->flags |= FS_FILE_FLAGS_CUSTOM;
+ return ERR_OK;
+ }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+ for (f = FS_ROOT; f != NULL; f = f->next) {
+ if (!strcmp(name, (const char *)f->name)) {
+ file->data = (const char *)f->data;
+ file->len = f->len;
+ file->index = f->len;
+ file->flags = f->flags;
+#if HTTPD_PRECALCULATED_CHECKSUM
+ file->chksum_count = f->chksum_count;
+ file->chksum = f->chksum;
+#endif /* HTTPD_PRECALCULATED_CHECKSUM */
+#if LWIP_HTTPD_FILE_EXTENSION
+ file->pextension = NULL;
+#endif /* LWIP_HTTPD_FILE_EXTENSION */
+#if LWIP_HTTPD_FILE_STATE
+ file->state = fs_state_init(file, name);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+ return ERR_OK;
+ }
+ }
+ /* file not found */
+ return ERR_VAL;
+}
+
+/*-----------------------------------------------------------------------------------*/
+void
+fs_close(struct fs_file *file)
+{
+#if LWIP_HTTPD_CUSTOM_FILES
+ if ((file->flags & FS_FILE_FLAGS_CUSTOM) != 0) {
+ fs_close_custom(file);
+ }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#if LWIP_HTTPD_FILE_STATE
+ fs_state_free(file, file->state);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+ LWIP_UNUSED_ARG(file);
+}
+/*-----------------------------------------------------------------------------------*/
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+#if LWIP_HTTPD_FS_ASYNC_READ
+int
+fs_read_async(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg)
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+int
+fs_read(struct fs_file *file, char *buffer, int count)
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+{
+ int read;
+ if (file->index == file->len) {
+ return FS_READ_EOF;
+ }
+#if LWIP_HTTPD_FS_ASYNC_READ
+ LWIP_UNUSED_ARG(callback_fn);
+ LWIP_UNUSED_ARG(callback_arg);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+#if LWIP_HTTPD_CUSTOM_FILES
+ if ((file->flags & FS_FILE_FLAGS_CUSTOM) != 0) {
+#if LWIP_HTTPD_FS_ASYNC_READ
+ return fs_read_async_custom(file, buffer, count, callback_fn, callback_arg);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+ return fs_read_custom(file, buffer, count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+ }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+ read = file->len - file->index;
+ if (read > count) {
+ read = count;
+ }
+
+ MEMCPY(buffer, (file->data + file->index), read);
+ file->index += read;
+
+ return (read);
+}
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+/*-----------------------------------------------------------------------------------*/
+#if LWIP_HTTPD_FS_ASYNC_READ
+int
+fs_is_file_ready(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg)
+{
+ if (file != NULL) {
+#if LWIP_HTTPD_FS_ASYNC_READ
+#if LWIP_HTTPD_CUSTOM_FILES
+ if (!fs_canread_custom(file)) {
+ if (fs_wait_read_custom(file, callback_fn, callback_arg)) {
+ return 0;
+ }
+ }
+#else /* LWIP_HTTPD_CUSTOM_FILES */
+ LWIP_UNUSED_ARG(callback_fn);
+ LWIP_UNUSED_ARG(callback_arg);
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+ }
+ return 1;
+}
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+/*-----------------------------------------------------------------------------------*/
+int
+fs_bytes_left(struct fs_file *file)
+{
+ return file->len - file->index;
+}
diff --git a/src/apps/http/fs/404.html b/src/apps/http/fs/404.html
new file mode 100644
index 00000000000..40b343a91e2
--- /dev/null
+++ b/src/apps/http/fs/404.html
@@ -0,0 +1,21 @@
+<html>
+<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
+<body bgcolor="white" text="black">
+
+ <table width="100%">
+ <tr valign="top"><td width="80">
+ <a href="http://www.sics.se/"><img src="/img/sics.gif"
+ border="0" alt="SICS logo" title="SICS logo"></a>
+ </td><td width="500">
+ <h1>lwIP - A Lightweight TCP/IP Stack</h1>
+ <h2>404 - Page not found</h2>
+ <p>
+ Sorry, the page you are requesting was not found on this
+ server.
+ </p>
+ </td><td>
+ &nbsp;
+ </td></tr>
+ </table>
+</body>
+</html>
diff --git a/src/apps/http/fs/img/sics.gif b/src/apps/http/fs/img/sics.gif
new file mode 100644
index 00000000000..0a4fc7bb070
--- /dev/null
+++ b/src/apps/http/fs/img/sics.gif
Binary files differ
diff --git a/src/apps/http/fs/index.html b/src/apps/http/fs/index.html
new file mode 100644
index 00000000000..ab575ef0891
--- /dev/null
+++ b/src/apps/http/fs/index.html
@@ -0,0 +1,47 @@
+<html>
+<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
+<body bgcolor="white" text="black">
+
+ <table width="100%">
+ <tr valign="top"><td width="80">
+ <a href="http://www.sics.se/"><img src="/img/sics.gif"
+ border="0" alt="SICS logo" title="SICS logo"></a>
+ </td><td width="500">
+ <h1>lwIP - A Lightweight TCP/IP Stack</h1>
+ <p>
+ The web page you are watching was served by a simple web
+ server running on top of the lightweight TCP/IP stack <a
+ href="http://www.sics.se/~adam/lwip/">lwIP</a>.
+ </p>
+ <p>
+ lwIP is an open source implementation of the TCP/IP
+ protocol suite that was originally written by <a
+ href="http://www.sics.se/~adam/lwip/">Adam Dunkels
+ of the Swedish Institute of Computer Science</a> but now is
+ being actively developed by a team of developers
+ distributed world-wide. Since it's release, lwIP has
+ spurred a lot of interest and has been ported to several
+ platforms and operating systems. lwIP can be used either
+ with or without an underlying OS.
+ </p>
+ <p>
+ The focus of the lwIP TCP/IP implementation is to reduce
+ the RAM usage while still having a full scale TCP. This
+ makes lwIP suitable for use in embedded systems with tens
+ of kilobytes of free RAM and room for around 40 kilobytes
+ of code ROM.
+ </p>
+ <p>
+ More information about lwIP can be found at the lwIP
+ homepage at <a
+ href="http://savannah.nongnu.org/projects/lwip/">http://savannah.nongnu.org/projects/lwip/</a>
+ or at the lwIP wiki at <a
+ href="http://lwip.wikia.com/">http://lwip.wikia.com/</a>.
+ </p>
+ </td><td>
+ &nbsp;
+ </td></tr>
+ </table>
+</body>
+</html>
+
diff --git a/src/apps/http/fsdata.c b/src/apps/http/fsdata.c
new file mode 100644
index 00000000000..50bc87acfff
--- /dev/null
+++ b/src/apps/http/fsdata.c
@@ -0,0 +1,336 @@
+#include "lwip/apps/fs.h"
+#include "lwip/def.h"
+
+
+#define file_NULL (struct fsdata_file *) NULL
+
+
+#ifndef FS_FILE_FLAGS_HEADER_INCLUDED
+#define FS_FILE_FLAGS_HEADER_INCLUDED 1
+#endif
+#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT
+#define FS_FILE_FLAGS_HEADER_PERSISTENT 0
+#endif
+/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */
+#ifndef FSDATA_FILE_ALIGNMENT
+#define FSDATA_FILE_ALIGNMENT 0
+#endif
+#ifndef FSDATA_ALIGN_PRE
+#define FSDATA_ALIGN_PRE
+#endif
+#ifndef FSDATA_ALIGN_POST
+#define FSDATA_ALIGN_POST
+#endif
+#if FSDATA_FILE_ALIGNMENT==2
+#include "fsdata_alignment.h"
+#endif
+#if FSDATA_FILE_ALIGNMENT==1
+static const unsigned int dummy_align__img_sics_gif = 0;
+#endif
+static const unsigned char FSDATA_ALIGN_PRE data__img_sics_gif[] FSDATA_ALIGN_POST = {
+/* /img/sics.gif (14 chars) */
+0x2f,0x69,0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x00,0x00,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 200 OK
+" (17 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d,
+0x0a,
+/* "Server: lwIP/2.0.3d (http://savannah.nongnu.org/projects/lwip)
+" (64 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x32,0x2e,0x30,
+0x2e,0x33,0x64,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,
+0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,
+0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+
+/* "Content-Length: 724
+" (18+ bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20,
+0x37,0x32,0x34,0x0d,0x0a,
+/* "Content-Type: image/gif
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x69,0x6d,
+0x61,0x67,0x65,0x2f,0x67,0x69,0x66,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (724 bytes) */
+0x47,0x49,0x46,0x38,0x39,0x61,0x46,0x00,0x22,0x00,0xa5,0x00,0x00,0xd9,0x2b,0x39,
+0x6a,0x6a,0x6a,0xbf,0xbf,0xbf,0x93,0x93,0x93,0x0f,0x0f,0x0f,0xb0,0xb0,0xb0,0xa6,
+0xa6,0xa6,0x80,0x80,0x80,0x76,0x76,0x76,0x1e,0x1e,0x1e,0x9d,0x9d,0x9d,0x2e,0x2e,
+0x2e,0x49,0x49,0x49,0x54,0x54,0x54,0x8a,0x8a,0x8a,0x60,0x60,0x60,0xc6,0xa6,0x99,
+0xbd,0xb5,0xb2,0xc2,0xab,0xa1,0xd9,0x41,0x40,0xd5,0x67,0x55,0xc0,0xb0,0xaa,0xd5,
+0x5e,0x4e,0xd6,0x50,0x45,0xcc,0x93,0x7d,0xc8,0xa1,0x90,0xce,0x8b,0x76,0xd2,0x7b,
+0x65,0xd1,0x84,0x6d,0xc9,0x99,0x86,0x3a,0x3a,0x3a,0x00,0x00,0x00,0xb8,0xb8,0xb8,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x2c,0x00,0x00,
+0x00,0x00,0x46,0x00,0x22,0x00,0x00,0x06,0xfe,0x40,0x90,0x70,0x48,0x2c,0x1a,0x8f,
+0xc8,0xa4,0x72,0xc9,0x6c,0x3a,0x9f,0xd0,0xa8,0x74,0x4a,0xad,0x5a,0xaf,0xd8,0xac,
+0x76,0xa9,0x40,0x04,0xbe,0x83,0xe2,0x60,0x3c,0x50,0x20,0x0d,0x8e,0x6f,0x00,0x31,
+0x28,0x1c,0x0d,0x07,0xb5,0xc3,0x60,0x75,0x24,0x3e,0xf8,0xfc,0x87,0x11,0x06,0xe9,
+0x3d,0x46,0x07,0x0b,0x7a,0x7a,0x7c,0x43,0x06,0x1e,0x84,0x78,0x0b,0x07,0x6e,0x51,
+0x01,0x8a,0x84,0x08,0x7e,0x79,0x80,0x87,0x89,0x91,0x7a,0x93,0x0a,0x04,0x99,0x78,
+0x96,0x4f,0x03,0x9e,0x79,0x01,0x94,0x9f,0x43,0x9c,0xa3,0xa4,0x05,0x77,0xa3,0xa0,
+0x4e,0x98,0x79,0x0b,0x1e,0x83,0xa4,0xa6,0x1f,0x96,0x05,0x9d,0xaa,0x78,0x01,0x07,
+0x84,0x04,0x1e,0x1e,0xbb,0xb8,0x51,0x84,0x0e,0x43,0x05,0x07,0x77,0xa5,0x7f,0x42,
+0xb1,0xb2,0x01,0x63,0x08,0x0d,0xbb,0x01,0x0c,0x7a,0x0d,0x44,0x0e,0xd8,0xaf,0x4c,
+0x05,0x7a,0x04,0x47,0x07,0x07,0xb7,0x80,0xa2,0xe1,0x7d,0x44,0x05,0x01,0x04,0x01,
+0xd0,0xea,0x87,0x93,0x4f,0xe0,0x9a,0x49,0xce,0xd8,0x79,0x04,0x66,0x20,0x15,0x10,
+0x10,0x11,0x92,0x29,0x80,0xb6,0xc0,0x91,0x15,0x45,0x1e,0x90,0x19,0x71,0x46,0xa8,
+0x5c,0x04,0x0e,0x00,0x22,0x4e,0xe8,0x40,0x24,0x9f,0x3e,0x04,0x06,0xa7,0x58,0xd4,
+0x93,0xa0,0x1c,0x91,0x3f,0xe8,0xf0,0x88,0x03,0xb1,0x21,0xa2,0x49,0x00,0x19,0x86,
+0xfc,0x52,0x44,0xe0,0x01,0x9d,0x29,0x21,0x15,0x25,0x50,0xf7,0x67,0x25,0x1e,0x06,
+0xfd,0x4e,0x9a,0xb4,0x90,0xac,0x15,0xfa,0xcb,0x52,0x53,0x1e,0x8c,0xf2,0xf8,0x07,
+0x92,0x2d,0x08,0x3a,0x4d,0x12,0x49,0x95,0x49,0xdb,0x14,0x04,0xc4,0x14,0x85,0x29,
+0xaa,0xe7,0x01,0x08,0xa4,0x49,0x01,0x14,0x51,0xe0,0x53,0x91,0xd5,0x29,0x06,0x1a,
+0x64,0x02,0xf4,0xc7,0x81,0x9e,0x05,0x20,0x22,0x64,0xa5,0x30,0xae,0xab,0x9e,0x97,
+0x53,0xd8,0xb9,0xfd,0x50,0xef,0x93,0x02,0x42,0x74,0x34,0xe8,0x9c,0x20,0x21,0xc9,
+0x01,0x68,0x78,0xe6,0x55,0x29,0x20,0x56,0x4f,0x4c,0x40,0x51,0x71,0x82,0xc0,0x70,
+0x21,0x22,0x85,0xbe,0x4b,0x1c,0x44,0x05,0xea,0xa4,0x01,0xbf,0x22,0xb5,0xf0,0x1c,
+0x06,0x51,0x38,0x8f,0xe0,0x22,0xec,0x18,0xac,0x39,0x22,0xd4,0xd6,0x93,0x44,0x01,
+0x32,0x82,0xc8,0xfc,0x61,0xb3,0x01,0x45,0x0c,0x2e,0x83,0x30,0xd0,0x0e,0x17,0x24,
+0x0f,0x70,0x85,0x94,0xee,0x05,0x05,0x53,0x4b,0x32,0x1b,0x3f,0x98,0xd3,0x1d,0x29,
+0x81,0xb0,0xae,0x1e,0x8c,0x7e,0x68,0xe0,0x60,0x5a,0x54,0x8f,0xb0,0x78,0x69,0x73,
+0x06,0xa2,0x00,0x6b,0x57,0xca,0x3d,0x11,0x50,0xbd,0x04,0x30,0x4b,0x3a,0xd4,0xab,
+0x5f,0x1f,0x9b,0x3d,0x13,0x74,0x27,0x88,0x3c,0x25,0xe0,0x17,0xbe,0x7a,0x79,0x45,
+0x0d,0x0c,0xb0,0x8b,0xda,0x90,0xca,0x80,0x06,0x5d,0x17,0x60,0x1c,0x22,0x4c,0xd8,
+0x57,0x22,0x06,0x20,0x00,0x98,0x07,0x08,0xe4,0x56,0x80,0x80,0x1c,0xc5,0xb7,0xc5,
+0x82,0x0c,0x36,0xe8,0xe0,0x83,0x10,0x46,0x28,0xe1,0x84,0x14,0x56,0x68,0xa1,0x10,
+0x41,0x00,0x00,0x3b,};
+
+#if FSDATA_FILE_ALIGNMENT==1
+static const unsigned int dummy_align__404_html = 1;
+#endif
+static const unsigned char FSDATA_ALIGN_PRE data__404_html[] FSDATA_ALIGN_POST = {
+/* /404.html (10 chars) */
+0x2f,0x34,0x30,0x34,0x2e,0x68,0x74,0x6d,0x6c,0x00,0x00,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 404 File not found
+" (29 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x34,0x30,0x34,0x20,0x46,0x69,0x6c,
+0x65,0x20,0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x0d,0x0a,
+/* "Server: lwIP/2.0.3d (http://savannah.nongnu.org/projects/lwip)
+" (64 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x32,0x2e,0x30,
+0x2e,0x33,0x64,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,
+0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,
+0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+
+/* "Content-Length: 565
+" (18+ bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20,
+0x35,0x36,0x35,0x0d,0x0a,
+/* "Content-Type: text/html
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x74,0x65,
+0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (565 bytes) */
+0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0d,0x0a,0x3c,0x68,0x65,0x61,0x64,0x3e,0x3c,0x74,
+0x69,0x74,0x6c,0x65,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,0x69,
+0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,
+0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,0x3c,0x2f,
+0x68,0x65,0x61,0x64,0x3e,0x0d,0x0a,0x3c,0x62,0x6f,0x64,0x79,0x20,0x62,0x67,0x63,
+0x6f,0x6c,0x6f,0x72,0x3d,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0x20,0x74,0x65,0x78,
+0x74,0x3d,0x22,0x62,0x6c,0x61,0x63,0x6b,0x22,0x3e,0x0d,0x0a,0x0d,0x0a,0x20,0x20,
+0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,
+0x31,0x30,0x30,0x25,0x22,0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,
+0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x74,0x6f,0x70,0x22,0x3e,0x3c,
+0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x38,0x30,0x22,0x3e,0x09,0x20,
+0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x61,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x22,0x3e,0x3c,0x69,0x6d,0x67,0x20,0x73,0x72,0x63,0x3d,0x22,0x2f,0x69,
+0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x22,0x0d,0x0a,0x09,0x20,
+0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x3d,0x22,0x30,0x22,0x20,0x61,0x6c,0x74,0x3d,
+0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x20,0x74,0x69,0x74,0x6c,
+0x65,0x3d,0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x3e,0x3c,0x2f,
+0x61,0x3e,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x20,0x77,0x69,
+0x64,0x74,0x68,0x3d,0x22,0x35,0x30,0x30,0x22,0x3e,0x09,0x20,0x20,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x68,0x31,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,
+0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x09,0x20,
+0x20,0x3c,0x68,0x32,0x3e,0x34,0x30,0x34,0x20,0x2d,0x20,0x50,0x61,0x67,0x65,0x20,
+0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x68,0x32,0x3e,0x0d,0x0a,
+0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x53,0x6f,0x72,
+0x72,0x79,0x2c,0x20,0x74,0x68,0x65,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,
+0x20,0x61,0x72,0x65,0x20,0x72,0x65,0x71,0x75,0x65,0x73,0x74,0x69,0x6e,0x67,0x20,
+0x77,0x61,0x73,0x20,0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x20,0x6f,0x6e,
+0x20,0x74,0x68,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x65,0x72,0x76,
+0x65,0x72,0x2e,0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,
+0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x26,0x6e,
+0x62,0x73,0x70,0x3b,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,
+0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,
+0x3e,0x0d,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0d,0x0a,0x3c,0x2f,0x68,0x74,
+0x6d,0x6c,0x3e,0x0d,0x0a,};
+
+#if FSDATA_FILE_ALIGNMENT==1
+static const unsigned int dummy_align__index_html = 2;
+#endif
+static const unsigned char FSDATA_ALIGN_PRE data__index_html[] FSDATA_ALIGN_POST = {
+/* /index.html (12 chars) */
+0x2f,0x69,0x6e,0x64,0x65,0x78,0x2e,0x68,0x74,0x6d,0x6c,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 200 OK
+" (17 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d,
+0x0a,
+/* "Server: lwIP/2.0.3d (http://savannah.nongnu.org/projects/lwip)
+" (64 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x32,0x2e,0x30,
+0x2e,0x33,0x64,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,
+0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,
+0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+
+/* "Content-Length: 1751
+" (18+ bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20,
+0x31,0x37,0x35,0x31,0x0d,0x0a,
+/* "Content-Type: text/html
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x74,0x65,
+0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (1751 bytes) */
+0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0d,0x0a,0x3c,0x68,0x65,0x61,0x64,0x3e,0x3c,0x74,
+0x69,0x74,0x6c,0x65,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,0x69,
+0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,
+0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,0x3c,0x2f,
+0x68,0x65,0x61,0x64,0x3e,0x0d,0x0a,0x3c,0x62,0x6f,0x64,0x79,0x20,0x62,0x67,0x63,
+0x6f,0x6c,0x6f,0x72,0x3d,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0x20,0x74,0x65,0x78,
+0x74,0x3d,0x22,0x62,0x6c,0x61,0x63,0x6b,0x22,0x3e,0x0d,0x0a,0x0d,0x0a,0x20,0x20,
+0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,
+0x31,0x30,0x30,0x25,0x22,0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,
+0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x74,0x6f,0x70,0x22,0x3e,0x3c,
+0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x38,0x30,0x22,0x3e,0x09,0x20,
+0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x61,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x22,0x3e,0x3c,0x69,0x6d,0x67,0x20,0x73,0x72,0x63,0x3d,0x22,0x2f,0x69,
+0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x22,0x0d,0x0a,0x09,0x20,
+0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x3d,0x22,0x30,0x22,0x20,0x61,0x6c,0x74,0x3d,
+0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x20,0x74,0x69,0x74,0x6c,
+0x65,0x3d,0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x3e,0x3c,0x2f,
+0x61,0x3e,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x20,0x77,0x69,
+0x64,0x74,0x68,0x3d,0x22,0x35,0x30,0x30,0x22,0x3e,0x09,0x20,0x20,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x68,0x31,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,
+0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x09,0x20,
+0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x54,0x68,0x65,0x20,0x77,
+0x65,0x62,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,0x20,0x61,0x72,0x65,0x20,
+0x77,0x61,0x74,0x63,0x68,0x69,0x6e,0x67,0x20,0x77,0x61,0x73,0x20,0x73,0x65,0x72,
+0x76,0x65,0x64,0x20,0x62,0x79,0x20,0x61,0x20,0x73,0x69,0x6d,0x70,0x6c,0x65,0x20,
+0x77,0x65,0x62,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x65,0x72,0x76,0x65,0x72,
+0x20,0x72,0x75,0x6e,0x6e,0x69,0x6e,0x67,0x20,0x6f,0x6e,0x20,0x74,0x6f,0x70,0x20,
+0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x6c,0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,
+0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,0x20,0x73,0x74,0x61,0x63,0x6b,0x20,
+0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x7e,0x61,0x64,0x61,0x6d,0x2f,0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x6c,
+0x77,0x49,0x50,0x3c,0x2f,0x61,0x3e,0x2e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,
+0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,
+0x6c,0x77,0x49,0x50,0x20,0x69,0x73,0x20,0x61,0x6e,0x20,0x6f,0x70,0x65,0x6e,0x20,
+0x73,0x6f,0x75,0x72,0x63,0x65,0x20,0x69,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,
+0x61,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x54,0x43,0x50,
+0x2f,0x49,0x50,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x70,0x72,0x6f,0x74,0x6f,0x63,
+0x6f,0x6c,0x20,0x73,0x75,0x69,0x74,0x65,0x20,0x74,0x68,0x61,0x74,0x20,0x77,0x61,
+0x73,0x20,0x6f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x6c,0x79,0x20,0x77,0x72,0x69,
+0x74,0x74,0x65,0x6e,0x20,0x62,0x79,0x20,0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,
+0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,
+0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,0x65,0x2f,0x7e,0x61,0x64,0x61,0x6d,0x2f,
+0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x41,0x64,0x61,0x6d,0x20,0x44,0x75,0x6e,0x6b,
+0x65,0x6c,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,
+0x20,0x53,0x77,0x65,0x64,0x69,0x73,0x68,0x20,0x49,0x6e,0x73,0x74,0x69,0x74,0x75,
+0x74,0x65,0x20,0x6f,0x66,0x20,0x43,0x6f,0x6d,0x70,0x75,0x74,0x65,0x72,0x20,0x53,
+0x63,0x69,0x65,0x6e,0x63,0x65,0x3c,0x2f,0x61,0x3e,0x20,0x62,0x75,0x74,0x20,0x6e,
+0x6f,0x77,0x20,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x62,0x65,0x69,0x6e,
+0x67,0x20,0x61,0x63,0x74,0x69,0x76,0x65,0x6c,0x79,0x20,0x64,0x65,0x76,0x65,0x6c,
+0x6f,0x70,0x65,0x64,0x20,0x62,0x79,0x20,0x61,0x20,0x74,0x65,0x61,0x6d,0x20,0x6f,
+0x66,0x20,0x64,0x65,0x76,0x65,0x6c,0x6f,0x70,0x65,0x72,0x73,0x0d,0x0a,0x09,0x20,
+0x20,0x20,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x77,
+0x6f,0x72,0x6c,0x64,0x2d,0x77,0x69,0x64,0x65,0x2e,0x20,0x53,0x69,0x6e,0x63,0x65,
+0x20,0x69,0x74,0x27,0x73,0x20,0x72,0x65,0x6c,0x65,0x61,0x73,0x65,0x2c,0x20,0x6c,
+0x77,0x49,0x50,0x20,0x68,0x61,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x70,
+0x75,0x72,0x72,0x65,0x64,0x20,0x61,0x20,0x6c,0x6f,0x74,0x20,0x6f,0x66,0x20,0x69,
+0x6e,0x74,0x65,0x72,0x65,0x73,0x74,0x20,0x61,0x6e,0x64,0x20,0x68,0x61,0x73,0x20,
+0x62,0x65,0x65,0x6e,0x20,0x70,0x6f,0x72,0x74,0x65,0x64,0x20,0x74,0x6f,0x20,0x73,
+0x65,0x76,0x65,0x72,0x61,0x6c,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x70,0x6c,0x61,
+0x74,0x66,0x6f,0x72,0x6d,0x73,0x20,0x61,0x6e,0x64,0x20,0x6f,0x70,0x65,0x72,0x61,
+0x74,0x69,0x6e,0x67,0x20,0x73,0x79,0x73,0x74,0x65,0x6d,0x73,0x2e,0x20,0x6c,0x77,
+0x49,0x50,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x75,0x73,0x65,0x64,0x20,0x65,
+0x69,0x74,0x68,0x65,0x72,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x77,0x69,0x74,0x68,
+0x20,0x6f,0x72,0x20,0x77,0x69,0x74,0x68,0x6f,0x75,0x74,0x20,0x61,0x6e,0x20,0x75,
+0x6e,0x64,0x65,0x72,0x6c,0x79,0x69,0x6e,0x67,0x20,0x4f,0x53,0x2e,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,
+0x09,0x20,0x20,0x20,0x20,0x54,0x68,0x65,0x20,0x66,0x6f,0x63,0x75,0x73,0x20,0x6f,
+0x66,0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x69,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x61,0x74,0x69,0x6f,0x6e,
+0x20,0x69,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x64,0x75,0x63,0x65,0x0d,0x0a,0x09,
+0x20,0x20,0x20,0x20,0x74,0x68,0x65,0x20,0x52,0x41,0x4d,0x20,0x75,0x73,0x61,0x67,
+0x65,0x20,0x77,0x68,0x69,0x6c,0x65,0x20,0x73,0x74,0x69,0x6c,0x6c,0x20,0x68,0x61,
+0x76,0x69,0x6e,0x67,0x20,0x61,0x20,0x66,0x75,0x6c,0x6c,0x20,0x73,0x63,0x61,0x6c,
+0x65,0x20,0x54,0x43,0x50,0x2e,0x20,0x54,0x68,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6d,0x61,0x6b,0x65,0x73,0x20,0x6c,0x77,0x49,0x50,0x20,0x73,0x75,0x69,
+0x74,0x61,0x62,0x6c,0x65,0x20,0x66,0x6f,0x72,0x20,0x75,0x73,0x65,0x20,0x69,0x6e,
+0x20,0x65,0x6d,0x62,0x65,0x64,0x64,0x65,0x64,0x20,0x73,0x79,0x73,0x74,0x65,0x6d,
+0x73,0x20,0x77,0x69,0x74,0x68,0x20,0x74,0x65,0x6e,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6f,0x66,0x20,0x6b,0x69,0x6c,0x6f,0x62,0x79,0x74,0x65,0x73,0x20,0x6f,
+0x66,0x20,0x66,0x72,0x65,0x65,0x20,0x52,0x41,0x4d,0x20,0x61,0x6e,0x64,0x20,0x72,
+0x6f,0x6f,0x6d,0x20,0x66,0x6f,0x72,0x20,0x61,0x72,0x6f,0x75,0x6e,0x64,0x20,0x34,
+0x30,0x20,0x6b,0x69,0x6c,0x6f,0x62,0x79,0x74,0x65,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6f,0x66,0x20,0x63,0x6f,0x64,0x65,0x20,0x52,0x4f,0x4d,0x2e,0x0d,0x0a,
+0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,
+0x0a,0x09,0x20,0x20,0x20,0x20,0x4d,0x6f,0x72,0x65,0x20,0x69,0x6e,0x66,0x6f,0x72,
+0x6d,0x61,0x74,0x69,0x6f,0x6e,0x20,0x61,0x62,0x6f,0x75,0x74,0x20,0x6c,0x77,0x49,
+0x50,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x66,0x6f,0x75,0x6e,0x64,0x20,0x61,
+0x74,0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x0d,0x0a,0x09,0x20,0x20,0x20,
+0x20,0x68,0x6f,0x6d,0x65,0x70,0x61,0x67,0x65,0x20,0x61,0x74,0x20,0x3c,0x61,0x0d,
+0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,
+0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,
+0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,
+0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,
+0x76,0x61,0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,
+0x67,0x2f,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x2f,
+0x3c,0x2f,0x61,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x6f,0x72,0x20,0x61,0x74,
+0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x20,0x77,0x69,0x6b,0x69,0x20,0x61,
+0x74,0x20,0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,
+0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x6c,0x77,0x69,0x70,0x2e,0x77,0x69,0x6b,
+0x69,0x61,0x2e,0x63,0x6f,0x6d,0x2f,0x22,0x3e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,
+0x6c,0x77,0x69,0x70,0x2e,0x77,0x69,0x6b,0x69,0x61,0x2e,0x63,0x6f,0x6d,0x2f,0x3c,
+0x2f,0x61,0x3e,0x2e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,
+0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x26,0x6e,
+0x62,0x73,0x70,0x3b,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,
+0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,
+0x3e,0x0d,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0d,0x0a,0x3c,0x2f,0x68,0x74,
+0x6d,0x6c,0x3e,0x0d,0x0a,0x0d,0x0a,};
+
+
+
+const struct fsdata_file file__img_sics_gif[] = { {
+file_NULL,
+data__img_sics_gif,
+data__img_sics_gif + 16,
+sizeof(data__img_sics_gif) - 16,
+FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT,
+}};
+
+const struct fsdata_file file__404_html[] = { {
+file__img_sics_gif,
+data__404_html,
+data__404_html + 12,
+sizeof(data__404_html) - 12,
+FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT,
+}};
+
+const struct fsdata_file file__index_html[] = { {
+file__404_html,
+data__index_html,
+data__index_html + 12,
+sizeof(data__index_html) - 12,
+FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT,
+}};
+
+#define FS_ROOT file__index_html
+#define FS_NUMFILES 3
diff --git a/src/apps/http/fsdata.h b/src/apps/http/fsdata.h
new file mode 100644
index 00000000000..d31550d7113
--- /dev/null
+++ b/src/apps/http/fsdata.h
@@ -0,0 +1,41 @@
+/*
+ * 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 <[email protected]>
+ *
+ */
+#ifndef LWIP_FSDATA_H
+#define LWIP_FSDATA_H
+
+#include "lwip/apps/httpd_opts.h"
+#include "lwip/apps/fs.h"
+
+/* THIS FILE IS DEPRECATED AND WILL BE REMOVED IN THE FUTURE */
+/* content was moved to fs.h to simplify #include structure */
+
+#endif /* LWIP_FSDATA_H */
diff --git a/src/apps/http/http_client.c b/src/apps/http/http_client.c
new file mode 100644
index 00000000000..1973e79e723
--- /dev/null
+++ b/src/apps/http/http_client.c
@@ -0,0 +1,911 @@
+/**
+ * @file
+ * HTTP client
+ */
+
+/*
+ * Copyright (c) 2018 Simon Goldschmidt <[email protected]>
+ * 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: Simon Goldschmidt <[email protected]>
+ */
+
+/**
+ * @defgroup httpc HTTP client
+ * @ingroup apps
+ * @todo:
+ * - persistent connections
+ * - select outgoing http version
+ * - optionally follow redirect
+ * - check request uri for invalid characters? (e.g. encode spaces)
+ * - IPv6 support
+ */
+
+#include "lwip/apps/http_client.h"
+
+#include "lwip/altcp_tcp.h"
+#include "lwip/dns.h"
+#include "lwip/debug.h"
+#include "lwip/mem.h"
+#include "lwip/altcp_tls.h"
+#include "lwip/init.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/**
+ * HTTPC_DEBUG: Enable debugging for HTTP client.
+ */
+#ifndef HTTPC_DEBUG
+#define HTTPC_DEBUG LWIP_DBG_OFF
+#endif
+
+/** Set this to 1 to keep server name and uri in request state */
+#ifndef HTTPC_DEBUG_REQUEST
+#define HTTPC_DEBUG_REQUEST 0
+#endif
+
+/** This string is passed in the HTTP header as "User-Agent: " */
+#ifndef HTTPC_CLIENT_AGENT
+#define HTTPC_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)"
+#endif
+
+/* the various debug levels for this file */
+#define HTTPC_DEBUG_TRACE (HTTPC_DEBUG | LWIP_DBG_TRACE)
+#define HTTPC_DEBUG_STATE (HTTPC_DEBUG | LWIP_DBG_STATE)
+#define HTTPC_DEBUG_WARN (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define HTTPC_DEBUG_WARN_STATE (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define HTTPC_DEBUG_SERIOUS (HTTPC_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+#define HTTPC_POLL_INTERVAL 1
+#define HTTPC_POLL_TIMEOUT 30 /* 15 seconds */
+
+#define HTTPC_CONTENT_LEN_INVALID 0xFFFFFFFF
+
+/* GET request basic */
+#define HTTPC_REQ_11 "GET %s HTTP/1.1\r\n" /* URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_FORMAT(uri) HTTPC_REQ_11, uri, HTTPC_CLIENT_AGENT
+
+/* GET request with host */
+#define HTTPC_REQ_11_HOST "GET %s HTTP/1.1\r\n" /* URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Host: %s\r\n" /* server name */ \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_HOST_FORMAT(uri, srv_name) HTTPC_REQ_11_HOST, uri, HTTPC_CLIENT_AGENT, srv_name
+
+/* GET request with proxy */
+#define HTTPC_REQ_11_PROXY "GET http://%s%s HTTP/1.1\r\n" /* HOST, URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Host: %s\r\n" /* server name */ \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_PROXY_FORMAT(host, uri, srv_name) HTTPC_REQ_11_PROXY, host, uri, HTTPC_CLIENT_AGENT, srv_name
+
+/* GET request with proxy (non-default server port) */
+#define HTTPC_REQ_11_PROXY_PORT "GET http://%s:%d%s HTTP/1.1\r\n" /* HOST, host-port, URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Host: %s\r\n" /* server name */ \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_PROXY_PORT_FORMAT(host, host_port, uri, srv_name) HTTPC_REQ_11_PROXY_PORT, host, host_port, uri, HTTPC_CLIENT_AGENT, srv_name
+
+typedef enum ehttpc_parse_state {
+ HTTPC_PARSE_WAIT_FIRST_LINE = 0,
+ HTTPC_PARSE_WAIT_HEADERS,
+ HTTPC_PARSE_RX_DATA
+} httpc_parse_state_t;
+
+typedef struct _httpc_state
+{
+ struct altcp_pcb* pcb;
+ ip_addr_t remote_addr;
+ u16_t remote_port;
+ int timeout_ticks;
+ struct pbuf *request;
+ struct pbuf *rx_hdrs;
+ u16_t rx_http_version;
+ u16_t rx_status;
+ altcp_recv_fn recv_fn;
+ const httpc_connection_t *conn_settings;
+ void* callback_arg;
+ u32_t rx_content_len;
+ u32_t hdr_content_len;
+ httpc_parse_state_t parse_state;
+#if HTTPC_DEBUG_REQUEST
+ char* server_name;
+ char* uri;
+#endif
+} httpc_state_t;
+
+/** Free http client state and deallocate all resources within */
+static err_t
+httpc_free_state(httpc_state_t* req)
+{
+ struct altcp_pcb* tpcb;
+
+ if (req->request != NULL) {
+ pbuf_free(req->request);
+ req->request = NULL;
+ }
+ if (req->rx_hdrs != NULL) {
+ pbuf_free(req->rx_hdrs);
+ req->rx_hdrs = NULL;
+ }
+
+ tpcb = req->pcb;
+ mem_free(req);
+ req = NULL;
+
+ if (tpcb != NULL) {
+ err_t r;
+ altcp_arg(tpcb, NULL);
+ altcp_recv(tpcb, NULL);
+ altcp_err(tpcb, NULL);
+ altcp_poll(tpcb, NULL, 0);
+ altcp_sent(tpcb, NULL);
+ r = altcp_close(tpcb);
+ if (r != ERR_OK) {
+ altcp_abort(tpcb);
+ return ERR_ABRT;
+ }
+ }
+ return ERR_OK;
+}
+
+/** Close the connection: call finished callback and free the state */
+static err_t
+httpc_close(httpc_state_t* req, httpc_result_t result, u32_t server_response, err_t err)
+{
+ if (req != NULL) {
+ if (req->conn_settings != NULL) {
+ if (req->conn_settings->result_fn != NULL) {
+ req->conn_settings->result_fn(req->callback_arg, result, req->rx_content_len, server_response, err);
+ }
+ }
+ return httpc_free_state(req);
+ }
+ return ERR_OK;
+}
+
+/** Parse http header response line 1 */
+static err_t
+http_parse_response_status(struct pbuf *p, u16_t *http_version, u16_t *http_status, u16_t *http_status_str_offset)
+{
+ u16_t end1 = pbuf_memfind(p, "\r\n", 2, 0);
+ if (end1 != 0xFFFF) {
+ /* get parts of first line */
+ u16_t space1, space2;
+ space1 = pbuf_memfind(p, " ", 1, 0);
+ if (space1 != 0xFFFF) {
+ if ((pbuf_memcmp(p, 0, "HTTP/", 5) == 0) && (pbuf_get_at(p, 6) == '.')) {
+ char status_num[10];
+ size_t status_num_len;
+ /* parse http version */
+ u16_t version = pbuf_get_at(p, 5) - '0';
+ version <<= 8;
+ version |= pbuf_get_at(p, 7) - '0';
+ *http_version = version;
+
+ /* parse http status number */
+ space2 = pbuf_memfind(p, " ", 1, space1 + 1);
+ if (space2 != 0xFFFF) {
+ *http_status_str_offset = space2 + 1;
+ status_num_len = space2 - space1 - 1;
+ } else {
+ status_num_len = end1 - space1 - 1;
+ }
+ memset(status_num, 0, sizeof(status_num));
+ if (pbuf_copy_partial(p, status_num, (u16_t)status_num_len, space1 + 1) == status_num_len) {
+ int status = atoi(status_num);
+ if ((status > 0) && (status <= 0xFFFF)) {
+ *http_status = (u16_t)status;
+ return ERR_OK;
+ }
+ }
+ }
+ }
+ }
+ return ERR_VAL;
+}
+
+/** Wait for all headers to be received, return its length and content-length (if available) */
+static err_t
+http_wait_headers(struct pbuf *p, u32_t *content_length, u16_t *total_header_len)
+{
+ u16_t end1 = pbuf_memfind(p, "\r\n\r\n", 4, 0);
+ if (end1 < (0xFFFF - 2)) {
+ /* all headers received */
+ /* check if we have a content length (@todo: case insensitive?) */
+ u16_t content_len_hdr;
+ *content_length = HTTPC_CONTENT_LEN_INVALID;
+ *total_header_len = end1 + 4;
+
+ content_len_hdr = pbuf_memfind(p, "Content-Length: ", 16, 0);
+ if (content_len_hdr != 0xFFFF) {
+ u16_t content_len_line_end = pbuf_memfind(p, "\r\n", 2, content_len_hdr);
+ if (content_len_line_end != 0xFFFF) {
+ char content_len_num[16];
+ u16_t content_len_num_len = (u16_t)(content_len_line_end - content_len_hdr - 16);
+ memset(content_len_num, 0, sizeof(content_len_num));
+ if (pbuf_copy_partial(p, content_len_num, content_len_num_len, content_len_hdr + 16) == content_len_num_len) {
+ int len = atoi(content_len_num);
+ if ((len >= 0) && ((u32_t)len < HTTPC_CONTENT_LEN_INVALID)) {
+ *content_length = (u32_t)len;
+ }
+ }
+ }
+ }
+ return ERR_OK;
+ }
+ return ERR_VAL;
+}
+
+/** http client tcp recv callback */
+static err_t
+httpc_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t r)
+{
+ httpc_state_t* req = (httpc_state_t*)arg;
+ LWIP_UNUSED_ARG(r);
+
+ if (p == NULL) {
+ httpc_result_t result;
+ if (req->parse_state != HTTPC_PARSE_RX_DATA) {
+ /* did not get RX data yet */
+ result = HTTPC_RESULT_ERR_CLOSED;
+ } else if ((req->hdr_content_len != HTTPC_CONTENT_LEN_INVALID) &&
+ (req->hdr_content_len != req->rx_content_len)) {
+ /* header has been received with content length but not all data received */
+ result = HTTPC_RESULT_ERR_CONTENT_LEN;
+ } else {
+ /* receiving data and either all data received or no content length header */
+ result = HTTPC_RESULT_OK;
+ }
+ return httpc_close(req, result, req->rx_status, ERR_OK);
+ }
+ if (req->parse_state != HTTPC_PARSE_RX_DATA) {
+ if (req->rx_hdrs == NULL) {
+ req->rx_hdrs = p;
+ } else {
+ pbuf_cat(req->rx_hdrs, p);
+ }
+ if (req->parse_state == HTTPC_PARSE_WAIT_FIRST_LINE) {
+ u16_t status_str_off;
+ err_t err = http_parse_response_status(req->rx_hdrs, &req->rx_http_version, &req->rx_status, &status_str_off);
+ if (err == ERR_OK) {
+ /* don't care status string */
+ req->parse_state = HTTPC_PARSE_WAIT_HEADERS;
+ }
+ }
+ if (req->parse_state == HTTPC_PARSE_WAIT_HEADERS) {
+ u16_t total_header_len;
+ err_t err = http_wait_headers(req->rx_hdrs, &req->hdr_content_len, &total_header_len);
+ if (err == ERR_OK) {
+ struct pbuf *q;
+ /* full header received, send window update for header bytes and call into client callback */
+ altcp_recved(pcb, total_header_len);
+ if (req->conn_settings) {
+ if (req->conn_settings->headers_done_fn) {
+ err = req->conn_settings->headers_done_fn(req, req->callback_arg, req->rx_hdrs, total_header_len, req->hdr_content_len);
+ if (err != ERR_OK) {
+ return httpc_close(req, HTTPC_RESULT_LOCAL_ABORT, req->rx_status, err);
+ }
+ }
+ }
+ /* hide header bytes in pbuf */
+ q = pbuf_free_header(req->rx_hdrs, total_header_len);
+ p = q;
+ req->rx_hdrs = NULL;
+ /* go on with data */
+ req->parse_state = HTTPC_PARSE_RX_DATA;
+ }
+ }
+ }
+ if ((p != NULL) && (req->parse_state == HTTPC_PARSE_RX_DATA)) {
+ req->rx_content_len += p->tot_len;
+ /* received valid data: reset timeout */
+ req->timeout_ticks = HTTPC_POLL_TIMEOUT;
+ if (req->recv_fn != NULL) {
+ /* directly return here: the connection might already be aborted from the callback! */
+ return req->recv_fn(req->callback_arg, pcb, p, r);
+ } else {
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ }
+ }
+ return ERR_OK;
+}
+
+/** http client tcp err callback */
+static void
+httpc_tcp_err(void *arg, err_t err)
+{
+ httpc_state_t* req = (httpc_state_t*)arg;
+ if (req != NULL) {
+ /* pcb has already been deallocated */
+ req->pcb = NULL;
+ httpc_close(req, HTTPC_RESULT_ERR_CLOSED, 0, err);
+ }
+}
+
+/** http client tcp poll callback */
+static err_t
+httpc_tcp_poll(void *arg, struct altcp_pcb *pcb)
+{
+ /* implement timeout */
+ httpc_state_t* req = (httpc_state_t*)arg;
+ LWIP_UNUSED_ARG(pcb);
+ if (req != NULL) {
+ if (req->timeout_ticks) {
+ req->timeout_ticks--;
+ }
+ if (!req->timeout_ticks) {
+ return httpc_close(req, HTTPC_RESULT_ERR_TIMEOUT, 0, ERR_OK);
+ }
+ }
+ return ERR_OK;
+}
+
+/** http client tcp sent callback */
+static err_t
+httpc_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len)
+{
+ /* nothing to do here for now */
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(len);
+ return ERR_OK;
+}
+
+/** http client tcp connected callback */
+static err_t
+httpc_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err)
+{
+ err_t r;
+ httpc_state_t* req = (httpc_state_t*)arg;
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(err);
+
+ /* send request; last char is zero termination */
+ r = altcp_write(req->pcb, req->request->payload, req->request->len - 1, TCP_WRITE_FLAG_COPY);
+ if (r != ERR_OK) {
+ /* could not write the single small request -> fail, don't retry */
+ return httpc_close(req, HTTPC_RESULT_ERR_MEM, 0, r);
+ }
+ /* everything written, we can free the request */
+ pbuf_free(req->request);
+ req->request = NULL;
+
+ altcp_output(req->pcb);
+ return ERR_OK;
+}
+
+/** Start the http request when the server IP addr is known */
+static err_t
+httpc_get_internal_addr(httpc_state_t* req, const ip_addr_t *ipaddr)
+{
+ err_t err;
+ LWIP_ASSERT("req != NULL", req != NULL);
+
+ if (&req->remote_addr != ipaddr) {
+ /* fill in remote addr if called externally */
+ req->remote_addr = *ipaddr;
+ }
+
+ err = altcp_connect(req->pcb, &req->remote_addr, req->remote_port, httpc_tcp_connected);
+ if (err == ERR_OK) {
+ return ERR_OK;
+ }
+ LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
+ return err;
+}
+
+#if LWIP_DNS
+/** DNS callback
+ * If ipaddr is non-NULL, resolving succeeded and the request can be sent, otherwise it failed.
+ */
+static void
+httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
+{
+ httpc_state_t* req = (httpc_state_t*)arg;
+ err_t err;
+ httpc_result_t result;
+
+ LWIP_UNUSED_ARG(hostname);
+
+ if (ipaddr != NULL) {
+ err = httpc_get_internal_addr(req, ipaddr);
+ if (err == ERR_OK) {
+ return;
+ }
+ result = HTTPC_RESULT_ERR_CONNECT;
+ } else {
+ LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("httpc_dns_found: failed to resolve hostname: %s\n",
+ hostname));
+ result = HTTPC_RESULT_ERR_HOSTNAME;
+ err = ERR_ARG;
+ }
+ httpc_close(req, result, 0, err);
+}
+#endif /* LWIP_DNS */
+
+/** Start the http request after converting 'server_name' to ip address (DNS or address string) */
+static err_t
+httpc_get_internal_dns(httpc_state_t* req, const char* server_name)
+{
+ err_t err;
+ LWIP_ASSERT("req != NULL", req != NULL);
+
+#if LWIP_DNS
+ err = dns_gethostbyname(server_name, &req->remote_addr, httpc_dns_found, req);
+#else
+ err = ipaddr_aton(server_name, &req->remote_addr) ? ERR_OK : ERR_ARG;
+#endif
+
+ if (err == ERR_OK) {
+ /* cached or IP-string */
+ err = httpc_get_internal_addr(req, &req->remote_addr);
+ } else if (err == ERR_INPROGRESS) {
+ return ERR_OK;
+ }
+ return err;
+}
+
+static int
+httpc_create_request_string(const httpc_connection_t *settings, const char* server_name, int server_port, const char* uri,
+ int use_host, char *buffer, size_t buffer_size)
+{
+ if (settings->use_proxy) {
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ if (server_port != HTTP_DEFAULT_PORT) {
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_PORT_FORMAT(server_name, server_port, uri, server_name));
+ } else {
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_FORMAT(server_name, uri, server_name));
+ }
+ } else if (use_host) {
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_HOST_FORMAT(uri, server_name));
+ } else {
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_FORMAT(uri));
+ }
+}
+
+/** Initialize the connection struct */
+static err_t
+httpc_init_connection_common(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name,
+ u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg, int use_host)
+{
+ size_t alloc_len;
+ mem_size_t mem_alloc_len;
+ int req_len, req_len2;
+ httpc_state_t *req;
+#if HTTPC_DEBUG_REQUEST
+ size_t server_name_len, uri_len;
+#endif
+
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+
+ /* get request len */
+ req_len = httpc_create_request_string(settings, server_name, server_port, uri, use_host, NULL, 0);
+ if ((req_len < 0) || (req_len > 0xFFFF)) {
+ return ERR_VAL;
+ }
+ /* alloc state and request in one block */
+ alloc_len = sizeof(httpc_state_t);
+#if HTTPC_DEBUG_REQUEST
+ server_name_len = server_name ? strlen(server_name) : 0;
+ uri_len = strlen(uri);
+ alloc_len += server_name_len + 1 + uri_len + 1;
+#endif
+ mem_alloc_len = (mem_size_t)alloc_len;
+ if ((mem_alloc_len < alloc_len) || (req_len + 1 > 0xFFFF)) {
+ return ERR_VAL;
+ }
+
+ req = (httpc_state_t*)mem_malloc((mem_size_t)alloc_len);
+ if(req == NULL) {
+ return ERR_MEM;
+ }
+ memset(req, 0, sizeof(httpc_state_t));
+ req->timeout_ticks = HTTPC_POLL_TIMEOUT;
+ req->request = pbuf_alloc(PBUF_RAW, (u16_t)(req_len + 1), PBUF_RAM);
+ if (req->request == NULL) {
+ httpc_free_state(req);
+ return ERR_MEM;
+ }
+ if (req->request->next != NULL) {
+ /* need a pbuf in one piece */
+ httpc_free_state(req);
+ return ERR_MEM;
+ }
+ req->hdr_content_len = HTTPC_CONTENT_LEN_INVALID;
+#if HTTPC_DEBUG_REQUEST
+ req->server_name = (char*)(req + 1);
+ if (server_name) {
+ memcpy(req->server_name, server_name, server_name_len + 1);
+ }
+ req->uri = req->server_name + server_name_len + 1;
+ memcpy(req->uri, uri, uri_len + 1);
+#endif
+ req->pcb = altcp_new(settings->altcp_allocator);
+ if(req->pcb == NULL) {
+ httpc_free_state(req);
+ return ERR_MEM;
+ }
+ req->remote_port = settings->use_proxy ? settings->proxy_port : server_port;
+ altcp_arg(req->pcb, req);
+ altcp_recv(req->pcb, httpc_tcp_recv);
+ altcp_err(req->pcb, httpc_tcp_err);
+ altcp_poll(req->pcb, httpc_tcp_poll, HTTPC_POLL_INTERVAL);
+ altcp_sent(req->pcb, httpc_tcp_sent);
+
+ /* set up request buffer */
+ req_len2 = httpc_create_request_string(settings, server_name, server_port, uri, use_host,
+ (char *)req->request->payload, req_len + 1);
+ if (req_len2 != req_len) {
+ httpc_free_state(req);
+ return ERR_VAL;
+ }
+
+ req->recv_fn = recv_fn;
+ req->conn_settings = settings;
+ req->callback_arg = callback_arg;
+
+ *connection = req;
+ return ERR_OK;
+}
+
+/**
+ * Initialize the connection struct
+ */
+static err_t
+httpc_init_connection(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name,
+ u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg)
+{
+ return httpc_init_connection_common(connection, settings, server_name, server_port, uri, recv_fn, callback_arg, 1);
+}
+
+
+/**
+ * Initialize the connection struct (from IP address)
+ */
+static err_t
+httpc_init_connection_addr(httpc_state_t **connection, const httpc_connection_t *settings,
+ const ip_addr_t* server_addr, u16_t server_port, const char* uri,
+ altcp_recv_fn recv_fn, void* callback_arg)
+{
+ char *server_addr_str = ipaddr_ntoa(server_addr);
+ if (server_addr_str == NULL) {
+ return ERR_VAL;
+ }
+ return httpc_init_connection_common(connection, settings, server_addr_str, server_port, uri,
+ recv_fn, callback_arg, 1);
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file by passing server IP address
+ *
+ * @param server_addr IP address of the server to connect
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param recv_fn the http body (not the headers) are passed to this callback
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings,
+ altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+
+ LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;);
+
+ err = httpc_init_connection_addr(&req, settings, server_addr, port,
+ uri, recv_fn, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_addr(req, server_addr);
+ }
+ if(err != ERR_OK) {
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file by passing server name as string (DNS name or IP address string)
+ *
+ * @param server_name server name as string (DNS name or IP address string)
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param recv_fn the http body (not the headers) are passed to this callback
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings,
+ altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+
+ LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;);
+
+ err = httpc_init_connection(&req, settings, server_name, port, uri, recv_fn, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_dns(req, server_name);
+ }
+ if(err != ERR_OK) {
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+
+#if LWIP_HTTPC_HAVE_FILE_IO
+/* Implementation to disk via fopen/fwrite/fclose follows */
+
+typedef struct _httpc_filestate
+{
+ const char* local_file_name;
+ FILE *file;
+ httpc_connection_t settings;
+ const httpc_connection_t *client_settings;
+ void *callback_arg;
+} httpc_filestate_t;
+
+static void httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len,
+ u32_t srv_res, err_t err);
+
+/** Initialize http client state for download to file system */
+static err_t
+httpc_fs_init(httpc_filestate_t **filestate_out, const char* local_file_name,
+ const httpc_connection_t *settings, void* callback_arg)
+{
+ httpc_filestate_t *filestate;
+ size_t file_len, alloc_len;
+ FILE *f;
+
+ file_len = strlen(local_file_name);
+ alloc_len = sizeof(httpc_filestate_t) + file_len + 1;
+
+ filestate = (httpc_filestate_t *)mem_malloc((mem_size_t)alloc_len);
+ if (filestate == NULL) {
+ return ERR_MEM;
+ }
+ memset(filestate, 0, sizeof(httpc_filestate_t));
+ filestate->local_file_name = (const char *)(filestate + 1);
+ memcpy((char *)(filestate + 1), local_file_name, file_len + 1);
+ filestate->file = NULL;
+ filestate->client_settings = settings;
+ filestate->callback_arg = callback_arg;
+ /* copy client settings but override result callback */
+ memcpy(&filestate->settings, settings, sizeof(httpc_connection_t));
+ filestate->settings.result_fn = httpc_fs_result;
+
+ f = fopen(local_file_name, "wb");
+ if(f == NULL) {
+ /* could not open file */
+ mem_free(filestate);
+ return ERR_VAL;
+ }
+ filestate->file = f;
+ *filestate_out = filestate;
+ return ERR_OK;
+}
+
+/** Free http client state for download to file system */
+static void
+httpc_fs_free(httpc_filestate_t *filestate)
+{
+ if (filestate != NULL) {
+ if (filestate->file != NULL) {
+ fclose(filestate->file);
+ filestate->file = NULL;
+ }
+ mem_free(filestate);
+ }
+}
+
+/** Connection closed (success or error) */
+static void
+httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len,
+ u32_t srv_res, err_t err)
+{
+ httpc_filestate_t *filestate = (httpc_filestate_t *)arg;
+ if (filestate != NULL) {
+ if (filestate->client_settings->result_fn != NULL) {
+ filestate->client_settings->result_fn(filestate->callback_arg, httpc_result, rx_content_len,
+ srv_res, err);
+ }
+ httpc_fs_free(filestate);
+ }
+}
+
+/** tcp recv callback */
+static err_t
+httpc_fs_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+ httpc_filestate_t *filestate = (httpc_filestate_t*)arg;
+ struct pbuf* q;
+ LWIP_UNUSED_ARG(err);
+
+ LWIP_ASSERT("p != NULL", p != NULL);
+
+ for (q = p; q != NULL; q = q->next) {
+ fwrite(q->payload, 1, q->len, filestate->file);
+ }
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ return ERR_OK;
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file to disk by passing server IP address
+ *
+ * @param server_addr IP address of the server to connect
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings,
+ void* callback_arg, const char* local_file_name, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+ httpc_filestate_t *filestate;
+
+ LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;);
+
+ err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ err = httpc_init_connection_addr(&req, &filestate->settings, server_addr, port,
+ uri, httpc_fs_tcp_recv, filestate);
+ if (err != ERR_OK) {
+ httpc_fs_free(filestate);
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_addr(req, server_addr);
+ }
+ if(err != ERR_OK) {
+ httpc_fs_free(filestate);
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file to disk by passing server name as string (DNS name or IP address string)
+ *
+ * @param server_name server name as string (DNS name or IP address string)
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file_dns_to_disk(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings,
+ void* callback_arg, const char* local_file_name, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+ httpc_filestate_t *filestate;
+
+ LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;);
+
+ err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ err = httpc_init_connection(&req, &filestate->settings, server_name, port,
+ uri, httpc_fs_tcp_recv, filestate);
+ if (err != ERR_OK) {
+ httpc_fs_free(filestate);
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_dns(req, server_name);
+ }
+ if(err != ERR_OK) {
+ httpc_fs_free(filestate);
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+#endif /* LWIP_HTTPC_HAVE_FILE_IO */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/http/httpd.c b/src/apps/http/httpd.c
new file mode 100644
index 00000000000..9477e855e77
--- /dev/null
+++ b/src/apps/http/httpd.c
@@ -0,0 +1,2770 @@
+/**
+ * @file
+ * LWIP HTTP server implementation
+ */
+
+/*
+ * 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 <[email protected]>
+ * Simon Goldschmidt
+ *
+ */
+
+/**
+ * @defgroup httpd HTTP server
+ * @ingroup apps
+ *
+ * This httpd supports for a
+ * rudimentary server-side-include facility which will replace tags of the form
+ * <!--#tag--> in any file whose extension is .shtml, .shtm or .ssi with
+ * strings provided by an include handler whose pointer is provided to the
+ * module via function http_set_ssi_handler().
+ * Additionally, a simple common
+ * gateway interface (CGI) handling mechanism has been added to allow clients
+ * to hook functions to particular request URIs.
+ *
+ * To enable SSI support, define label LWIP_HTTPD_SSI in lwipopts.h.
+ * To enable CGI support, define label LWIP_HTTPD_CGI in lwipopts.h.
+ *
+ * By default, the server assumes that HTTP headers are already present in
+ * each file stored in the file system. By defining LWIP_HTTPD_DYNAMIC_HEADERS in
+ * lwipopts.h, this behavior can be changed such that the server inserts the
+ * headers automatically based on the extension of the file being served. If
+ * this mode is used, be careful to ensure that the file system image used
+ * does not already contain the header information.
+ *
+ * File system images without headers can be created using the makefsfile
+ * tool with the -h command line option.
+ *
+ *
+ * Notes about valid SSI tags
+ * --------------------------
+ *
+ * The following assumptions are made about tags used in SSI markers:
+ *
+ * 1. No tag may contain '-' or whitespace characters within the tag name.
+ * 2. Whitespace is allowed between the tag leadin "<!--#" and the start of
+ * the tag name and between the tag name and the leadout string "-->".
+ * 3. The maximum tag name length is LWIP_HTTPD_MAX_TAG_NAME_LEN, currently 8 characters.
+ *
+ * Notes on CGI usage
+ * ------------------
+ *
+ * The simple CGI support offered here works with GET method requests only
+ * and can handle up to 16 parameters encoded into the URI. The handler
+ * function may not write directly to the HTTP output but must return a
+ * filename that the HTTP server will send to the browser as a response to
+ * the incoming CGI request.
+ *
+ *
+ *
+ * The list of supported file types is quite short, so if makefsdata complains
+ * about an unknown extension, make sure to add it (and its doctype) to
+ * the 'g_psHTTPHeaders' list.
+ */
+#include "lwip/init.h"
+#include "lwip/apps/httpd.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+#include "lwip/apps/fs.h"
+#include "httpd_structs.h"
+#include "lwip/def.h"
+
+#include "lwip/altcp.h"
+#include "lwip/altcp_tcp.h"
+#if HTTPD_ENABLE_HTTPS
+#include "lwip/altcp_tls.h"
+#endif
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+#if LWIP_HTTPD_TIMING
+#include "lwip/sys.h"
+#endif /* LWIP_HTTPD_TIMING */
+
+#include <string.h> /* memset */
+#include <stdlib.h> /* atoi */
+#include <stdio.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/** Minimum length for a valid HTTP/0.9 request: "GET /\r\n" -> 7 bytes */
+#define MIN_REQ_LEN 7
+
+#define CRLF "\r\n"
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+#define HTTP11_CONNECTIONKEEPALIVE "Connection: keep-alive"
+#define HTTP11_CONNECTIONKEEPALIVE2 "Connection: Keep-Alive"
+#endif
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+#define HTTP_IS_DYNAMIC_FILE(hs) ((hs)->buf != NULL)
+#else
+#define HTTP_IS_DYNAMIC_FILE(hs) 0
+#endif
+
+/* This defines checks whether tcp_write has to copy data or not */
+
+#ifndef HTTP_IS_DATA_VOLATILE
+/** tcp_write does not have to copy data when sent from rom-file-system directly */
+#define HTTP_IS_DATA_VOLATILE(hs) (HTTP_IS_DYNAMIC_FILE(hs) ? TCP_WRITE_FLAG_COPY : 0)
+#endif
+/** Default: dynamic headers are sent from ROM (non-dynamic headers are handled like file data) */
+#ifndef HTTP_IS_HDR_VOLATILE
+#define HTTP_IS_HDR_VOLATILE(hs, ptr) 0
+#endif
+
+/* Return values for http_send_*() */
+#define HTTP_DATA_TO_SEND_FREED 3
+#define HTTP_DATA_TO_SEND_BREAK 2
+#define HTTP_DATA_TO_SEND_CONTINUE 1
+#define HTTP_NO_DATA_TO_SEND 0
+
+typedef struct {
+ const char *name;
+ u8_t shtml;
+} default_filename;
+
+static const default_filename httpd_default_filenames[] = {
+ {"/index.shtml", 1 },
+ {"/index.ssi", 1 },
+ {"/index.shtm", 1 },
+ {"/index.html", 0 },
+ {"/index.htm", 0 }
+};
+
+#define NUM_DEFAULT_FILENAMES LWIP_ARRAYSIZE(httpd_default_filenames)
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+/** HTTP request is copied here from pbufs for simple parsing */
+static char httpd_req_buf[LWIP_HTTPD_MAX_REQ_LENGTH + 1];
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+#if LWIP_HTTPD_SUPPORT_POST
+#if LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN > LWIP_HTTPD_MAX_REQUEST_URI_LEN
+#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN
+#endif
+#endif
+#ifndef LWIP_HTTPD_URI_BUF_LEN
+#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_MAX_REQUEST_URI_LEN
+#endif
+#if LWIP_HTTPD_URI_BUF_LEN
+/* Filename for response file to send when POST is finished or
+ * search for default files when a directory is requested. */
+static char http_uri_buf[LWIP_HTTPD_URI_BUF_LEN + 1];
+#endif
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/* The number of individual strings that comprise the headers sent before each
+ * requested file.
+ */
+#define NUM_FILE_HDR_STRINGS 5
+#define HDR_STRINGS_IDX_HTTP_STATUS 0 /* e.g. "HTTP/1.0 200 OK\r\n" */
+#define HDR_STRINGS_IDX_SERVER_NAME 1 /* e.g. "Server: "HTTPD_SERVER_AGENT"\r\n" */
+#define HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE 2 /* e.g. "Content-Length: xy\r\n" and/or "Connection: keep-alive\r\n" */
+#define HDR_STRINGS_IDX_CONTENT_LEN_NR 3 /* the byte count, when content-length is used */
+#define HDR_STRINGS_IDX_CONTENT_TYPE 4 /* the content type (or default answer content type including default document) */
+
+/* The dynamically generated Content-Length buffer needs space for CRLF + NULL */
+#define LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET 3
+#ifndef LWIP_HTTPD_MAX_CONTENT_LEN_SIZE
+/* The dynamically generated Content-Length buffer shall be able to work with
+ ~953 MB (9 digits) */
+#define LWIP_HTTPD_MAX_CONTENT_LEN_SIZE (9 + LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET)
+#endif
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI
+
+#define HTTPD_LAST_TAG_PART 0xFFFF
+
+enum tag_check_state {
+ TAG_NONE, /* Not processing an SSI tag */
+ TAG_LEADIN, /* Tag lead in "<!--#" being processed */
+ TAG_FOUND, /* Tag name being read, looking for lead-out start */
+ TAG_LEADOUT, /* Tag lead out "-->" being processed */
+ TAG_SENDING /* Sending tag replacement string */
+};
+
+struct http_ssi_state {
+ const char *parsed; /* Pointer to the first unparsed byte in buf. */
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ const char *tag_started;/* Pointer to the first opening '<' of the tag. */
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
+ const char *tag_end; /* Pointer to char after the closing '>' of the tag. */
+ u32_t parse_left; /* Number of unparsed bytes in buf. */
+ u16_t tag_index; /* Counter used by tag parsing state machine */
+ u16_t tag_insert_len; /* Length of insert in string tag_insert */
+#if LWIP_HTTPD_SSI_MULTIPART
+ u16_t tag_part; /* Counter passed to and changed by tag insertion function to insert multiple times */
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+ u8_t tag_type; /* index into http_ssi_tag_desc array */
+ u8_t tag_name_len; /* Length of the tag name in string tag_name */
+ char tag_name[LWIP_HTTPD_MAX_TAG_NAME_LEN + 1]; /* Last tag name extracted */
+ char tag_insert[LWIP_HTTPD_MAX_TAG_INSERT_LEN + 1]; /* Insert string for tag_name */
+ enum tag_check_state tag_state; /* State of the tag processor */
+};
+
+struct http_ssi_tag_description {
+ const char *lead_in;
+ const char *lead_out;
+};
+
+#endif /* LWIP_HTTPD_SSI */
+
+struct http_state {
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+ struct http_state *next;
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+ struct fs_file file_handle;
+ struct fs_file *handle;
+ const char *file; /* Pointer to first unsent byte in buf. */
+
+ struct altcp_pcb *pcb;
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ struct pbuf *req;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ char *buf; /* File read buffer. */
+ int buf_len; /* Size of file read buffer, buf. */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+ u32_t left; /* Number of unsent bytes in buf. */
+ u8_t retries;
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ u8_t keepalive;
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+#if LWIP_HTTPD_SSI
+ struct http_ssi_state *ssi;
+#endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_CGI
+ char *params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
+ char *param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
+#endif /* LWIP_HTTPD_CGI */
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ const char *hdrs[NUM_FILE_HDR_STRINGS]; /* HTTP headers to be sent. */
+ char hdr_content_len[LWIP_HTTPD_MAX_CONTENT_LEN_SIZE];
+ u16_t hdr_pos; /* The position of the first unsent header byte in the
+ current string */
+ u16_t hdr_index; /* The index of the hdr string currently being sent. */
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_TIMING
+ u32_t time_started;
+#endif /* LWIP_HTTPD_TIMING */
+#if LWIP_HTTPD_SUPPORT_POST
+ u32_t post_content_len_left;
+#if LWIP_HTTPD_POST_MANUAL_WND
+ u32_t unrecved_bytes;
+ u8_t no_auto_wnd;
+ u8_t post_finished;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+#endif /* LWIP_HTTPD_SUPPORT_POST*/
+};
+
+#if HTTPD_USE_MEM_POOL
+LWIP_MEMPOOL_DECLARE(HTTPD_STATE, MEMP_NUM_PARALLEL_HTTPD_CONNS, sizeof(struct http_state), "HTTPD_STATE")
+#if LWIP_HTTPD_SSI
+LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS, sizeof(struct http_ssi_state), "HTTPD_SSI_STATE")
+#define HTTP_FREE_SSI_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_SSI_STATE, (x))
+#define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)LWIP_MEMPOOL_ALLOC(HTTPD_SSI_STATE)
+#endif /* LWIP_HTTPD_SSI */
+#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)LWIP_MEMPOOL_ALLOC(HTTPD_STATE)
+#define HTTP_FREE_HTTP_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_STATE, (x))
+#else /* HTTPD_USE_MEM_POOL */
+#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state))
+#define HTTP_FREE_HTTP_STATE(x) mem_free(x)
+#if LWIP_HTTPD_SSI
+#define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)mem_malloc(sizeof(struct http_ssi_state))
+#define HTTP_FREE_SSI_STATE(x) mem_free(x)
+#endif /* LWIP_HTTPD_SSI */
+#endif /* HTTPD_USE_MEM_POOL */
+
+static err_t http_close_conn(struct altcp_pcb *pcb, struct http_state *hs);
+static err_t http_close_or_abort_conn(struct altcp_pcb *pcb, struct http_state *hs, u8_t abort_conn);
+static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
+static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check, char *params);
+static err_t http_poll(void *arg, struct altcp_pcb *pcb);
+static u8_t http_check_eof(struct altcp_pcb *pcb, struct http_state *hs);
+#if LWIP_HTTPD_FS_ASYNC_READ
+static void http_continue(void *connection);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+#if LWIP_HTTPD_SSI
+/* SSI insert handler function pointer. */
+static tSSIHandler httpd_ssi_handler;
+#if !LWIP_HTTPD_SSI_RAW
+static int httpd_num_tags;
+static const char **httpd_tags;
+#endif /* !LWIP_HTTPD_SSI_RAW */
+
+/* Define the available tag lead-ins and corresponding lead-outs.
+ * ATTENTION: for the algorithm below using this array, it is essential
+ * that the lead in differs in the first character! */
+const struct http_ssi_tag_description http_ssi_tag_desc[] = {
+ {"<!--#", "-->"},
+ {"/*#", "*/"}
+};
+
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_CGI
+/* CGI handler information */
+static const tCGI *httpd_cgis;
+static int httpd_num_cgis;
+static int http_cgi_paramcount;
+#define http_cgi_params hs->params
+#define http_cgi_param_vals hs->param_vals
+#elif LWIP_HTTPD_CGI_SSI
+static char *http_cgi_params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
+static char *http_cgi_param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
+#endif /* LWIP_HTTPD_CGI */
+
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+/** global list of active HTTP connections, use to kill the oldest when
+ running out of memory */
+static struct http_state *http_connections;
+
+static void
+http_add_connection(struct http_state *hs)
+{
+ /* add the connection to the list */
+ hs->next = http_connections;
+ http_connections = hs;
+}
+
+static void
+http_remove_connection(struct http_state *hs)
+{
+ /* take the connection off the list */
+ if (http_connections) {
+ if (http_connections == hs) {
+ http_connections = hs->next;
+ } else {
+ struct http_state *last;
+ for (last = http_connections; last->next != NULL; last = last->next) {
+ if (last->next == hs) {
+ last->next = hs->next;
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+http_kill_oldest_connection(u8_t ssi_required)
+{
+ struct http_state *hs = http_connections;
+ struct http_state *hs_free_next = NULL;
+ while (hs && hs->next) {
+#if LWIP_HTTPD_SSI
+ if (ssi_required) {
+ if (hs->next->ssi != NULL) {
+ hs_free_next = hs;
+ }
+ } else
+#else /* LWIP_HTTPD_SSI */
+ LWIP_UNUSED_ARG(ssi_required);
+#endif /* LWIP_HTTPD_SSI */
+ {
+ hs_free_next = hs;
+ }
+ LWIP_ASSERT("broken list", hs != hs->next);
+ hs = hs->next;
+ }
+ if (hs_free_next != NULL) {
+ LWIP_ASSERT("hs_free_next->next != NULL", hs_free_next->next != NULL);
+ LWIP_ASSERT("hs_free_next->next->pcb != NULL", hs_free_next->next->pcb != NULL);
+ /* send RST when killing a connection because of memory shortage */
+ http_close_or_abort_conn(hs_free_next->next->pcb, hs_free_next->next, 1); /* this also unlinks the http_state from the list */
+ }
+}
+#else /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+
+#define http_add_connection(hs)
+#define http_remove_connection(hs)
+
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+
+#if LWIP_HTTPD_SSI
+/** Allocate as struct http_ssi_state. */
+static struct http_ssi_state *
+http_ssi_state_alloc(void)
+{
+ struct http_ssi_state *ret = HTTP_ALLOC_SSI_STATE();
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+ if (ret == NULL) {
+ http_kill_oldest_connection(1);
+ ret = HTTP_ALLOC_SSI_STATE();
+ }
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+ if (ret != NULL) {
+ memset(ret, 0, sizeof(struct http_ssi_state));
+ }
+ return ret;
+}
+
+/** Free a struct http_ssi_state. */
+static void
+http_ssi_state_free(struct http_ssi_state *ssi)
+{
+ if (ssi != NULL) {
+ HTTP_FREE_SSI_STATE(ssi);
+ }
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/** Initialize a struct http_state.
+ */
+static void
+http_state_init(struct http_state *hs)
+{
+ /* Initialize the structure. */
+ memset(hs, 0, sizeof(struct http_state));
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ /* Indicate that the headers are not yet valid */
+ hs->hdr_index = NUM_FILE_HDR_STRINGS;
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+}
+
+/** Allocate a struct http_state. */
+static struct http_state *
+http_state_alloc(void)
+{
+ struct http_state *ret = HTTP_ALLOC_HTTP_STATE();
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+ if (ret == NULL) {
+ http_kill_oldest_connection(0);
+ ret = HTTP_ALLOC_HTTP_STATE();
+ }
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+ if (ret != NULL) {
+ http_state_init(ret);
+ http_add_connection(ret);
+ }
+ return ret;
+}
+
+/** Free a struct http_state.
+ * Also frees the file data if dynamic.
+ */
+static void
+http_state_eof(struct http_state *hs)
+{
+ if (hs->handle) {
+#if LWIP_HTTPD_TIMING
+ u32_t ms_needed = sys_now() - hs->time_started;
+ u32_t needed = LWIP_MAX(1, (ms_needed / 100));
+ LWIP_DEBUGF(HTTPD_DEBUG_TIMING, ("httpd: needed %"U32_F" ms to send file of %d bytes -> %"U32_F" bytes/sec\n",
+ ms_needed, hs->handle->len, ((((u32_t)hs->handle->len) * 10) / needed)));
+#endif /* LWIP_HTTPD_TIMING */
+ fs_close(hs->handle);
+ hs->handle = NULL;
+ }
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ if (hs->buf != NULL) {
+ mem_free(hs->buf);
+ hs->buf = NULL;
+ }
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+#if LWIP_HTTPD_SSI
+ if (hs->ssi) {
+ http_ssi_state_free(hs->ssi);
+ hs->ssi = NULL;
+ }
+#endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ if (hs->req) {
+ pbuf_free(hs->req);
+ hs->req = NULL;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+}
+
+/** Free a struct http_state.
+ * Also frees the file data if dynamic.
+ */
+static void
+http_state_free(struct http_state *hs)
+{
+ if (hs != NULL) {
+ http_state_eof(hs);
+ http_remove_connection(hs);
+ HTTP_FREE_HTTP_STATE(hs);
+ }
+}
+
+/** Call tcp_write() in a loop trying smaller and smaller length
+ *
+ * @param pcb altcp_pcb to send
+ * @param ptr Data to send
+ * @param length Length of data to send (in/out: on return, contains the
+ * amount of data sent)
+ * @param apiflags directly passed to tcp_write
+ * @return the return value of tcp_write
+ */
+static err_t
+http_write(struct altcp_pcb *pcb, const void *ptr, u16_t *length, u8_t apiflags)
+{
+ u16_t len, max_len;
+ err_t err;
+ LWIP_ASSERT("length != NULL", length != NULL);
+ len = *length;
+ if (len == 0) {
+ return ERR_OK;
+ }
+ /* We cannot send more data than space available in the send buffer. */
+ max_len = altcp_sndbuf(pcb);
+ if (max_len < len) {
+ len = max_len;
+ }
+#ifdef HTTPD_MAX_WRITE_LEN
+ /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
+ max_len = HTTPD_MAX_WRITE_LEN(pcb);
+ if (len > max_len) {
+ len = max_len;
+ }
+#endif /* HTTPD_MAX_WRITE_LEN */
+ do {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Trying to send %d bytes\n", len));
+ err = altcp_write(pcb, ptr, len, apiflags);
+ if (err == ERR_MEM) {
+ if ((altcp_sndbuf(pcb) == 0) ||
+ (altcp_sndqueuelen(pcb) >= TCP_SND_QUEUELEN)) {
+ /* no need to try smaller sizes */
+ len = 1;
+ } else {
+ len /= 2;
+ }
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE,
+ ("Send failed, trying less (%d bytes)\n", len));
+ }
+ } while ((err == ERR_MEM) && (len > 1));
+
+ if (err == ERR_OK) {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Sent %d bytes\n", len));
+ *length = len;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
+ *length = 0;
+ }
+
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ /* ensure nagle is normally enabled (only disabled for persistent connections
+ when all data has been enqueued but the connection stays open for the next
+ request */
+ altcp_nagle_enable(pcb);
+#endif
+
+ return err;
+}
+
+/**
+ * The connection shall be actively closed (using RST to close from fault states).
+ * Reset the sent- and recv-callbacks.
+ *
+ * @param pcb the tcp pcb to reset callbacks
+ * @param hs connection state to free
+ */
+static err_t
+http_close_or_abort_conn(struct altcp_pcb *pcb, struct http_state *hs, u8_t abort_conn)
+{
+ err_t err;
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Closing connection %p\n", (void *)pcb));
+
+#if LWIP_HTTPD_SUPPORT_POST
+ if (hs != NULL) {
+ if ((hs->post_content_len_left != 0)
+#if LWIP_HTTPD_POST_MANUAL_WND
+ || ((hs->no_auto_wnd != 0) && (hs->unrecved_bytes != 0))
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ ) {
+ /* make sure the post code knows that the connection is closed */
+ http_uri_buf[0] = 0;
+ httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN);
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST*/
+
+
+ altcp_arg(pcb, NULL);
+ altcp_recv(pcb, NULL);
+ altcp_err(pcb, NULL);
+ altcp_poll(pcb, NULL, 0);
+ altcp_sent(pcb, NULL);
+ if (hs != NULL) {
+ http_state_free(hs);
+ }
+
+ if (abort_conn) {
+ altcp_abort(pcb);
+ return ERR_OK;
+ }
+ err = altcp_close(pcb);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error %d closing %p\n", err, (void *)pcb));
+ /* error closing, try again later in poll */
+ altcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
+ }
+ return err;
+}
+
+/**
+ * The connection shall be actively closed.
+ * Reset the sent- and recv-callbacks.
+ *
+ * @param pcb the tcp pcb to reset callbacks
+ * @param hs connection state to free
+ */
+static err_t
+http_close_conn(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ return http_close_or_abort_conn(pcb, hs, 0);
+}
+
+/** End of file: either close the connection (Connection: close) or
+ * close the file (Connection: keep-alive)
+ */
+static void
+http_eof(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ /* HTTP/1.1 persistent connection? (Not supported for SSI) */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (hs->keepalive) {
+ http_remove_connection(hs);
+
+ http_state_eof(hs);
+ http_state_init(hs);
+ /* restore state: */
+ hs->pcb = pcb;
+ hs->keepalive = 1;
+ http_add_connection(hs);
+ /* ensure nagle doesn't interfere with sending all data as fast as possible: */
+ altcp_nagle_disable(pcb);
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ {
+ http_close_conn(pcb, hs);
+ }
+}
+
+#if LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI
+/**
+ * Extract URI parameters from the parameter-part of an URI in the form
+ * "test.cgi?x=y" @todo: better explanation!
+ * Pointers to the parameters are stored in hs->param_vals.
+ *
+ * @param hs http connection state
+ * @param params pointer to the NULL-terminated parameter string from the URI
+ * @return number of parameters extracted
+ */
+static int
+extract_uri_parameters(struct http_state *hs, char *params)
+{
+ char *pair;
+ char *equals;
+ int loop;
+
+ LWIP_UNUSED_ARG(hs);
+
+ /* If we have no parameters at all, return immediately. */
+ if (!params || (params[0] == '\0')) {
+ return (0);
+ }
+
+ /* Get a pointer to our first parameter */
+ pair = params;
+
+ /* Parse up to LWIP_HTTPD_MAX_CGI_PARAMETERS from the passed string and ignore the
+ * remainder (if any) */
+ for (loop = 0; (loop < LWIP_HTTPD_MAX_CGI_PARAMETERS) && pair; loop++) {
+
+ /* Save the name of the parameter */
+ http_cgi_params[loop] = pair;
+
+ /* Remember the start of this name=value pair */
+ equals = pair;
+
+ /* Find the start of the next name=value pair and replace the delimiter
+ * with a 0 to terminate the previous pair string. */
+ pair = strchr(pair, '&');
+ if (pair) {
+ *pair = '\0';
+ pair++;
+ } else {
+ /* We didn't find a new parameter so find the end of the URI and
+ * replace the space with a '\0' */
+ pair = strchr(equals, ' ');
+ if (pair) {
+ *pair = '\0';
+ }
+
+ /* Revert to NULL so that we exit the loop as expected. */
+ pair = NULL;
+ }
+
+ /* Now find the '=' in the previous pair, replace it with '\0' and save
+ * the parameter value string. */
+ equals = strchr(equals, '=');
+ if (equals) {
+ *equals = '\0';
+ http_cgi_param_vals[loop] = equals + 1;
+ } else {
+ http_cgi_param_vals[loop] = NULL;
+ }
+ }
+
+ return loop;
+}
+#endif /* LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI */
+
+#if LWIP_HTTPD_SSI
+/**
+ * Insert a tag (found in an shtml in the form of "<!--#tagname-->" into the file.
+ * The tag's name is stored in ssi->tag_name (NULL-terminated), the replacement
+ * should be written to hs->tag_insert (up to a length of LWIP_HTTPD_MAX_TAG_INSERT_LEN).
+ * The amount of data written is stored to ssi->tag_insert_len.
+ *
+ * @todo: return tag_insert_len - maybe it can be removed from struct http_state?
+ *
+ * @param hs http connection state
+ */
+static void
+get_tag_insert(struct http_state *hs)
+{
+#if LWIP_HTTPD_SSI_RAW
+ const char *tag;
+#else /* LWIP_HTTPD_SSI_RAW */
+ int tag;
+#endif /* LWIP_HTTPD_SSI_RAW */
+ size_t len;
+ struct http_ssi_state *ssi;
+#if LWIP_HTTPD_SSI_MULTIPART
+ u16_t current_tag_part;
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+
+ LWIP_ASSERT("hs != NULL", hs != NULL);
+ ssi = hs->ssi;
+ LWIP_ASSERT("ssi != NULL", ssi != NULL);
+#if LWIP_HTTPD_SSI_MULTIPART
+ current_tag_part = ssi->tag_part;
+ ssi->tag_part = HTTPD_LAST_TAG_PART;
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if LWIP_HTTPD_SSI_RAW
+ tag = ssi->tag_name;
+#endif
+
+ if (httpd_ssi_handler
+#if !LWIP_HTTPD_SSI_RAW
+ && httpd_tags && httpd_num_tags
+#endif /* !LWIP_HTTPD_SSI_RAW */
+ ) {
+
+ /* Find this tag in the list we have been provided. */
+#if LWIP_HTTPD_SSI_RAW
+ {
+#else /* LWIP_HTTPD_SSI_RAW */
+ for (tag = 0; tag < httpd_num_tags; tag++) {
+ if (strcmp(ssi->tag_name, httpd_tags[tag]) == 0)
+#endif /* LWIP_HTTPD_SSI_RAW */
+ {
+ ssi->tag_insert_len = httpd_ssi_handler(tag, ssi->tag_insert,
+ LWIP_HTTPD_MAX_TAG_INSERT_LEN
+#if LWIP_HTTPD_SSI_MULTIPART
+ , current_tag_part, &ssi->tag_part
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if LWIP_HTTPD_FILE_STATE
+ , (hs->handle ? hs->handle->state : NULL)
+#endif /* LWIP_HTTPD_FILE_STATE */
+ );
+#if LWIP_HTTPD_SSI_RAW
+ if (ssi->tag_insert_len != HTTPD_SSI_TAG_UNKNOWN)
+#endif /* LWIP_HTTPD_SSI_RAW */
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ /* If we drop out, we were asked to serve a page which contains tags that
+ * we don't have a handler for. Merely echo back the tags with an error
+ * marker. */
+#define UNKNOWN_TAG1_TEXT "<b>***UNKNOWN TAG "
+#define UNKNOWN_TAG1_LEN 18
+#define UNKNOWN_TAG2_TEXT "***</b>"
+#define UNKNOWN_TAG2_LEN 7
+ len = LWIP_MIN(sizeof(ssi->tag_name), LWIP_MIN(strlen(ssi->tag_name),
+ LWIP_HTTPD_MAX_TAG_INSERT_LEN - (UNKNOWN_TAG1_LEN + UNKNOWN_TAG2_LEN)));
+ MEMCPY(ssi->tag_insert, UNKNOWN_TAG1_TEXT, UNKNOWN_TAG1_LEN);
+ MEMCPY(&ssi->tag_insert[UNKNOWN_TAG1_LEN], ssi->tag_name, len);
+ MEMCPY(&ssi->tag_insert[UNKNOWN_TAG1_LEN + len], UNKNOWN_TAG2_TEXT, UNKNOWN_TAG2_LEN);
+ ssi->tag_insert[UNKNOWN_TAG1_LEN + len + UNKNOWN_TAG2_LEN] = 0;
+
+ len = strlen(ssi->tag_insert);
+ LWIP_ASSERT("len <= 0xffff", len <= 0xffff);
+ ssi->tag_insert_len = (u16_t)len;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/**
+ * Generate the relevant HTTP headers for the given filename and write
+ * them into the supplied buffer.
+ */
+static void
+get_http_headers(struct http_state *hs, const char *uri)
+{
+ size_t content_type;
+ char *tmp;
+ char *ext;
+ char *vars;
+
+ /* In all cases, the second header we send is the server identification
+ so set it here. */
+ hs->hdrs[HDR_STRINGS_IDX_SERVER_NAME] = g_psHTTPHeaderStrings[HTTP_HDR_SERVER];
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = NULL;
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = NULL;
+
+ /* Is this a normal file or the special case we use to send back the
+ default "404: Page not found" response? */
+ if (uri == NULL) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND];
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (hs->keepalive) {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML_PERSISTENT];
+ } else
+#endif
+ {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML];
+ }
+
+ /* Set up to send the first header string. */
+ hs->hdr_index = 0;
+ hs->hdr_pos = 0;
+ return;
+ }
+ /* We are dealing with a particular filename. Look for one other
+ special case. We assume that any filename with "404" in it must be
+ indicative of a 404 server error whereas all other files require
+ the 200 OK header. */
+ if (memcmp(uri, "/404.", 5) == 0) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND];
+ } else if (memcmp(uri, "/400.", 5) == 0) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_BAD_REQUEST];
+ } else if (memcmp(uri, "/501.", 5) == 0) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_IMPL];
+ } else {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_OK];
+ }
+
+ /* Determine if the URI has any variables and, if so, temporarily remove
+ them. */
+ vars = strchr(uri, '?');
+ if (vars) {
+ *vars = '\0';
+ }
+
+ /* Get a pointer to the file extension. We find this by looking for the
+ last occurrence of "." in the filename passed. */
+ ext = NULL;
+ tmp = strchr(uri, '.');
+ while (tmp) {
+ ext = tmp + 1;
+ tmp = strchr(ext, '.');
+ }
+ if (ext != NULL) {
+ /* Now determine the content type and add the relevant header for that. */
+ for (content_type = 0; content_type < NUM_HTTP_HEADERS; content_type++) {
+ /* Have we found a matching extension? */
+ if (!lwip_stricmp(g_psHTTPHeaders[content_type].extension, ext)) {
+ break;
+ }
+ }
+ } else {
+ content_type = NUM_HTTP_HEADERS;
+ }
+
+ /* Reinstate the parameter marker if there was one in the original URI. */
+ if (vars) {
+ *vars = '?';
+ }
+
+#if LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI
+ /* Does the URL passed have any file extension? If not, we assume it
+ is a special-case URL used for control state notification and we do
+ not send any HTTP headers with the response. */
+ if (!ext) {
+ /* Force the header index to a value indicating that all headers
+ have already been sent. */
+ hs->hdr_index = NUM_FILE_HDR_STRINGS;
+ return;
+ }
+#endif /* LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI */
+ /* Did we find a matching extension? */
+ if (content_type < NUM_HTTP_HEADERS) {
+ /* yes, store it */
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaders[content_type].content_type;
+ } else if (!ext) {
+ /* no, no extension found -> use binary transfer to prevent the browser adding '.txt' on save */
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_APP;
+ } else {
+ /* No - use the default, plain text file type. */
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_DEFAULT_TYPE;
+ }
+ /* Set up to send the first header string. */
+ hs->hdr_index = 0;
+ hs->hdr_pos = 0;
+}
+
+/* Add content-length header? */
+static void
+get_http_content_length(struct http_state *hs)
+{
+ u8_t add_content_len = 0;
+
+ LWIP_ASSERT("already been here?", hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] == NULL);
+
+ add_content_len = 0;
+#if LWIP_HTTPD_SSI
+ if (hs->ssi == NULL) /* @todo: get maximum file length from SSI */
+#endif /* LWIP_HTTPD_SSI */
+ {
+ if ((hs->handle != NULL) && (hs->handle->flags & FS_FILE_FLAGS_HEADER_PERSISTENT)) {
+ add_content_len = 1;
+ }
+ }
+ if (add_content_len) {
+ size_t len;
+ lwip_itoa(hs->hdr_content_len, (size_t)LWIP_HTTPD_MAX_CONTENT_LEN_SIZE,
+ hs->handle->len);
+ len = strlen(hs->hdr_content_len);
+ if (len <= LWIP_HTTPD_MAX_CONTENT_LEN_SIZE - LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET) {
+ SMEMCPY(&hs->hdr_content_len[len], CRLF, 3);
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = hs->hdr_content_len;
+ } else {
+ add_content_len = 0;
+ }
+ }
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (add_content_len) {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_KEEPALIVE_LEN];
+ } else {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE];
+ hs->keepalive = 0;
+ }
+#else /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ if (add_content_len) {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH];
+ }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+}
+
+/** Sub-function of http_send(): send dynamic headers
+ *
+ * @returns: - HTTP_NO_DATA_TO_SEND: no new data has been enqueued
+ * - HTTP_DATA_TO_SEND_CONTINUE: continue with sending HTTP body
+ * - HTTP_DATA_TO_SEND_BREAK: data has been enqueued, headers pending,
+ * so don't send HTTP body yet
+ * - HTTP_DATA_TO_SEND_FREED: http_state and pcb are already freed
+ */
+static u8_t
+http_send_headers(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ err_t err;
+ u16_t len;
+ u8_t data_to_send = HTTP_NO_DATA_TO_SEND;
+ u16_t hdrlen, sendlen;
+
+ if (hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] == NULL) {
+ /* set up "content-length" and "connection:" headers */
+ get_http_content_length(hs);
+ }
+
+ /* How much data can we send? */
+ len = altcp_sndbuf(pcb);
+ sendlen = len;
+
+ while (len && (hs->hdr_index < NUM_FILE_HDR_STRINGS) && sendlen) {
+ const void *ptr;
+ u16_t old_sendlen;
+ u8_t apiflags;
+ /* How much do we have to send from the current header? */
+ hdrlen = (u16_t)strlen(hs->hdrs[hs->hdr_index]);
+
+ /* How much of this can we send? */
+ sendlen = (len < (hdrlen - hs->hdr_pos)) ? len : (hdrlen - hs->hdr_pos);
+
+ /* Send this amount of data or as much as we can given memory
+ * constraints. */
+ ptr = (const void *)(hs->hdrs[hs->hdr_index] + hs->hdr_pos);
+ old_sendlen = sendlen;
+ apiflags = HTTP_IS_HDR_VOLATILE(hs, ptr);
+ if (hs->hdr_index == HDR_STRINGS_IDX_CONTENT_LEN_NR) {
+ /* content-length is always volatile */
+ apiflags |= TCP_WRITE_FLAG_COPY;
+ }
+ if (hs->hdr_index < NUM_FILE_HDR_STRINGS - 1) {
+ apiflags |= TCP_WRITE_FLAG_MORE;
+ }
+ err = http_write(pcb, ptr, &sendlen, apiflags);
+ if ((err == ERR_OK) && (old_sendlen != sendlen)) {
+ /* Remember that we added some more data to be transmitted. */
+ data_to_send = HTTP_DATA_TO_SEND_CONTINUE;
+ } else if (err != ERR_OK) {
+ /* special case: http_write does not try to send 1 byte */
+ sendlen = 0;
+ }
+
+ /* Fix up the header position for the next time round. */
+ hs->hdr_pos += sendlen;
+ len -= sendlen;
+
+ /* Have we finished sending this string? */
+ if (hs->hdr_pos == hdrlen) {
+ /* Yes - move on to the next one */
+ hs->hdr_index++;
+ /* skip headers that are NULL (not all headers are required) */
+ while ((hs->hdr_index < NUM_FILE_HDR_STRINGS) &&
+ (hs->hdrs[hs->hdr_index] == NULL)) {
+ hs->hdr_index++;
+ }
+ hs->hdr_pos = 0;
+ }
+ }
+
+ if ((hs->hdr_index >= NUM_FILE_HDR_STRINGS) && (hs->file == NULL)) {
+ /* When we are at the end of the headers, check for data to send
+ * instead of waiting for ACK from remote side to continue
+ * (which would happen when sending files from async read). */
+ if (http_check_eof(pcb, hs)) {
+ data_to_send = HTTP_DATA_TO_SEND_BREAK;
+ } else {
+ /* At this point, for non-keepalive connections, hs is deallocated an
+ pcb is closed. */
+ return HTTP_DATA_TO_SEND_FREED;
+ }
+ }
+ /* If we get here and there are still header bytes to send, we send
+ * the header information we just wrote immediately. If there are no
+ * more headers to send, but we do have file data to send, drop through
+ * to try to send some file data too. */
+ if ((hs->hdr_index < NUM_FILE_HDR_STRINGS) || !hs->file) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("tcp_output\n"));
+ return HTTP_DATA_TO_SEND_BREAK;
+ }
+ return data_to_send;
+}
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+/** Sub-function of http_send(): end-of-file (or block) is reached,
+ * either close the file or read the next block (if supported).
+ *
+ * @returns: 0 if the file is finished or no data has been read
+ * 1 if the file is not finished and data has been read
+ */
+static u8_t
+http_check_eof(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ int bytes_left;
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ int count;
+#ifdef HTTPD_MAX_WRITE_LEN
+ int max_write_len;
+#endif /* HTTPD_MAX_WRITE_LEN */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+
+ /* Do we have a valid file handle? */
+ if (hs->handle == NULL) {
+ /* No - close the connection. */
+ http_eof(pcb, hs);
+ return 0;
+ }
+ bytes_left = fs_bytes_left(hs->handle);
+ if (bytes_left <= 0) {
+ /* We reached the end of the file so this request is done. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+ http_eof(pcb, hs);
+ return 0;
+ }
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ /* Do we already have a send buffer allocated? */
+ if (hs->buf) {
+ /* Yes - get the length of the buffer */
+ count = LWIP_MIN(hs->buf_len, bytes_left);
+ } else {
+ /* We don't have a send buffer so allocate one now */
+ count = altcp_sndbuf(pcb);
+ if (bytes_left < count) {
+ count = bytes_left;
+ }
+#ifdef HTTPD_MAX_WRITE_LEN
+ /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
+ max_write_len = HTTPD_MAX_WRITE_LEN(pcb);
+ if (count > max_write_len) {
+ count = max_write_len;
+ }
+#endif /* HTTPD_MAX_WRITE_LEN */
+ do {
+ hs->buf = (char *)mem_malloc((mem_size_t)count);
+ if (hs->buf != NULL) {
+ hs->buf_len = count;
+ break;
+ }
+ count = count / 2;
+ } while (count > 100);
+
+ /* Did we get a send buffer? If not, return immediately. */
+ if (hs->buf == NULL) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("No buff\n"));
+ return 0;
+ }
+ }
+
+ /* Read a block of data from the file. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Trying to read %d bytes.\n", count));
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+ count = fs_read_async(hs->handle, hs->buf, count, http_continue, hs);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+ count = fs_read(hs->handle, hs->buf, count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+ if (count < 0) {
+ if (count == FS_READ_DELAYED) {
+ /* Delayed read, wait for FS to unblock us */
+ return 0;
+ }
+ /* We reached the end of the file so this request is done.
+ * @todo: close here for HTTP/1.1 when reading file fails */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+ http_eof(pcb, hs);
+ return 0;
+ }
+
+ /* Set up to send the block of data we just read */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Read %d bytes.\n", count));
+ hs->left = count;
+ hs->file = hs->buf;
+#if LWIP_HTTPD_SSI
+ if (hs->ssi) {
+ hs->ssi->parse_left = count;
+ hs->ssi->parsed = hs->buf;
+ }
+#endif /* LWIP_HTTPD_SSI */
+#else /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+ LWIP_ASSERT("SSI and DYNAMIC_HEADERS turned off but eof not reached", 0);
+#endif /* LWIP_HTTPD_SSI || LWIP_HTTPD_DYNAMIC_HEADERS */
+ return 1;
+}
+
+/** Sub-function of http_send(): This is the normal send-routine for non-ssi files
+ *
+ * @returns: - 1: data has been written (so call tcp_ouput)
+ * - 0: no data has been written (no need to call tcp_output)
+ */
+static u8_t
+http_send_data_nonssi(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ err_t err;
+ u16_t len;
+ u8_t data_to_send = 0;
+
+ /* We are not processing an SHTML file so no tag checking is necessary.
+ * Just send the data as we received it from the file. */
+ len = (u16_t)LWIP_MIN(hs->left, 0xffff);
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ hs->file += len;
+ hs->left -= len;
+ }
+
+ return data_to_send;
+}
+
+#if LWIP_HTTPD_SSI
+/** Sub-function of http_send(): This is the send-routine for ssi files
+ *
+ * @returns: - 1: data has been written (so call tcp_ouput)
+ * - 0: no data has been written (no need to call tcp_output)
+ */
+static u8_t
+http_send_data_ssi(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ err_t err = ERR_OK;
+ u16_t len;
+ u8_t data_to_send = 0;
+ u8_t tag_type;
+
+ struct http_ssi_state *ssi = hs->ssi;
+ LWIP_ASSERT("ssi != NULL", ssi != NULL);
+ /* We are processing an SHTML file so need to scan for tags and replace
+ * them with insert strings. We need to be careful here since a tag may
+ * straddle the boundary of two blocks read from the file and we may also
+ * have to split the insert string between two tcp_write operations. */
+
+ /* How much data could we send? */
+ len = altcp_sndbuf(pcb);
+
+ /* Do we have remaining data to send before parsing more? */
+ if (ssi->parsed > hs->file) {
+ len = (u16_t)LWIP_MIN(ssi->parsed - hs->file, 0xffff);
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ hs->file += len;
+ hs->left -= len;
+ }
+
+ /* If the send buffer is full, return now. */
+ if (altcp_sndbuf(pcb) == 0) {
+ return data_to_send;
+ }
+ }
+
+ LWIP_DEBUGF(HTTPD_DEBUG, ("State %d, %d left\n", ssi->tag_state, (int)ssi->parse_left));
+
+ /* We have sent all the data that was already parsed so continue parsing
+ * the buffer contents looking for SSI tags. */
+ while (((ssi->tag_state == TAG_SENDING) || ssi->parse_left) && (err == ERR_OK)) {
+ if (len == 0) {
+ return data_to_send;
+ }
+ switch (ssi->tag_state) {
+ case TAG_NONE:
+ /* We are not currently processing an SSI tag so scan for the
+ * start of the lead-in marker. */
+ for (tag_type = 0; tag_type < LWIP_ARRAYSIZE(http_ssi_tag_desc); tag_type++) {
+ if (*ssi->parsed == http_ssi_tag_desc[tag_type].lead_in[0]) {
+ /* We found what could be the lead-in for a new tag so change
+ * state appropriately. */
+ ssi->tag_type = tag_type;
+ ssi->tag_state = TAG_LEADIN;
+ ssi->tag_index = 1;
+ #if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ ssi->tag_started = ssi->parsed;
+ #endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
+ break;
+ }
+ }
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ break;
+
+ case TAG_LEADIN:
+ /* We are processing the lead-in marker, looking for the start of
+ * the tag name. */
+
+ /* Have we reached the end of the leadin? */
+ if (http_ssi_tag_desc[ssi->tag_type].lead_in[ssi->tag_index] == 0) {
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_FOUND;
+ } else {
+ /* Have we found the next character we expect for the tag leadin? */
+ if (*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_in[ssi->tag_index]) {
+ /* Yes - move to the next one unless we have found the complete
+ * leadin, in which case we start looking for the tag itself */
+ ssi->tag_index++;
+ } else {
+ /* We found an unexpected character so this is not a tag. Move
+ * back to idle state. */
+ ssi->tag_state = TAG_NONE;
+ }
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ && !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if ((ssi->tag_state == TAG_NONE) &&
+ (ssi->parsed - hs->file < ssi->tag_index)) {
+ for(u16_t i = 0;i < ssi->tag_index;i++) {
+ ssi->tag_insert[i] = http_ssi_tag_desc[ssi->tag_type].lead_in[i];
+ }
+ ssi->tag_insert_len = ssi->tag_index;
+ hs->file += ssi->parsed - hs->file;
+ hs->left -= ssi->parsed - hs->file;
+ ssi->tag_end = hs->file;
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_SENDING;
+ break;
+ }
+#endif
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ }
+ break;
+
+ case TAG_FOUND:
+ /* We are reading the tag name, looking for the start of the
+ * lead-out marker and removing any whitespace found. */
+
+ /* Remove leading whitespace between the tag leading and the first
+ * tag name character. */
+ if ((ssi->tag_index == 0) && ((*ssi->parsed == ' ') ||
+ (*ssi->parsed == '\t') || (*ssi->parsed == '\n') ||
+ (*ssi->parsed == '\r'))) {
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ break;
+ }
+
+ /* Have we found the end of the tag name? This is signalled by
+ * us finding the first leadout character or whitespace */
+ if ((*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_out[0]) ||
+ (*ssi->parsed == ' ') || (*ssi->parsed == '\t') ||
+ (*ssi->parsed == '\n') || (*ssi->parsed == '\r')) {
+
+ if (ssi->tag_index == 0) {
+ /* We read a zero length tag so ignore it. */
+ ssi->tag_state = TAG_NONE;
+ } else {
+ /* We read a non-empty tag so go ahead and look for the
+ * leadout string. */
+ ssi->tag_state = TAG_LEADOUT;
+ LWIP_ASSERT("ssi->tag_index <= 0xff", ssi->tag_index <= 0xff);
+ ssi->tag_name_len = (u8_t)ssi->tag_index;
+ ssi->tag_name[ssi->tag_index] = '\0';
+ if (*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_out[0]) {
+ ssi->tag_index = 1;
+ } else {
+ ssi->tag_index = 0;
+ }
+ }
+ } else {
+ /* This character is part of the tag name so save it */
+ if (ssi->tag_index < LWIP_HTTPD_MAX_TAG_NAME_LEN) {
+ ssi->tag_name[ssi->tag_index++] = *ssi->parsed;
+ } else {
+ /* The tag was too long so ignore it. */
+ ssi->tag_state = TAG_NONE;
+ }
+ }
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+
+ break;
+
+ /* We are looking for the end of the lead-out marker. */
+ case TAG_LEADOUT:
+ /* Remove leading whitespace between the tag leading and the first
+ * tag leadout character. */
+ if ((ssi->tag_index == 0) && ((*ssi->parsed == ' ') ||
+ (*ssi->parsed == '\t') || (*ssi->parsed == '\n') ||
+ (*ssi->parsed == '\r'))) {
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ break;
+ }
+
+ /* Have we found the next character we expect for the tag leadout? */
+ if (*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_out[ssi->tag_index]) {
+ /* Yes - move to the next one unless we have found the complete
+ * leadout, in which case we need to call the client to process
+ * the tag. */
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ ssi->tag_index++;
+
+ if (http_ssi_tag_desc[ssi->tag_type].lead_out[ssi->tag_index] == 0) {
+ /* Call the client to ask for the insert string for the
+ * tag we just found. */
+#if LWIP_HTTPD_SSI_MULTIPART
+ ssi->tag_part = 0; /* start with tag part 0 */
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+ get_tag_insert(hs);
+
+ /* Next time through, we are going to be sending data
+ * immediately, either the end of the block we start
+ * sending here or the insert string. */
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_SENDING;
+ ssi->tag_end = ssi->parsed;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ ssi->parsed = ssi->tag_started;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+
+ /* If there is any unsent data in the buffer prior to the
+ * tag, we need to send it now. */
+ if (ssi->tag_end > hs->file) {
+ /* How much of the data can we send? */
+#if LWIP_HTTPD_SSI_INCLUDE_TAG
+ len = (u16_t)LWIP_MIN(ssi->tag_end - hs->file, 0xffff);
+#else /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ /* we would include the tag in sending */
+ len = (u16_t)LWIP_MIN(ssi->tag_started - hs->file, 0xffff);
+#endif /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if (ssi->tag_started <= hs->file) {
+ /* pretend to have sent the tag, too */
+ len += (u16_t)(ssi->tag_end - ssi->tag_started);
+ }
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ hs->file += len;
+ hs->left -= len;
+ }
+ }
+ }
+ } else {
+ /* We found an unexpected character so this is not a tag. Move
+ * back to idle state. */
+ ssi->parse_left--;
+ ssi->parsed++;
+ ssi->tag_state = TAG_NONE;
+ }
+ break;
+
+ /*
+ * We have found a valid tag and are in the process of sending
+ * data as a result of that discovery. We send either remaining data
+ * from the file prior to the insert point or the insert string itself.
+ */
+ case TAG_SENDING:
+ /* Do we have any remaining file data to send from the buffer prior
+ * to the tag? */
+ if (ssi->tag_end > hs->file) {
+ /* How much of the data can we send? */
+#if LWIP_HTTPD_SSI_INCLUDE_TAG
+ len = (u16_t)LWIP_MIN(ssi->tag_end - hs->file, 0xffff);
+#else /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ LWIP_ASSERT("hs->started >= hs->file", ssi->tag_started >= hs->file);
+ /* we would include the tag in sending */
+ len = (u16_t)LWIP_MIN(ssi->tag_started - hs->file, 0xffff);
+#endif /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ if (len != 0) {
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ } else {
+ err = ERR_OK;
+ }
+ if (err == ERR_OK) {
+ data_to_send = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if (ssi->tag_started <= hs->file) {
+ /* pretend to have sent the tag, too */
+ len += (u16_t)(ssi->tag_end - ssi->tag_started);
+ }
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ hs->file += len;
+ hs->left -= len;
+ }
+ } else {
+#if LWIP_HTTPD_SSI_MULTIPART
+ if (ssi->tag_index >= ssi->tag_insert_len) {
+ /* Did the last SSIHandler have more to send? */
+ if (ssi->tag_part != HTTPD_LAST_TAG_PART) {
+ /* If so, call it again */
+ ssi->tag_index = 0;
+ get_tag_insert(hs);
+ }
+ }
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+
+ /* Do we still have insert data left to send? */
+ if (ssi->tag_index < ssi->tag_insert_len) {
+ /* We are sending the insert string itself. How much of the
+ * insert can we send? */
+ len = (ssi->tag_insert_len - ssi->tag_index);
+
+ /* Note that we set the copy flag here since we only have a
+ * single tag insert buffer per connection. If we don't do
+ * this, insert corruption can occur if more than one insert
+ * is processed before we call tcp_output. */
+ err = http_write(pcb, &(ssi->tag_insert[ssi->tag_index]), &len,
+ HTTP_IS_TAG_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ ssi->tag_index += len;
+ /* Don't return here: keep on sending data */
+ }
+ } else {
+#if LWIP_HTTPD_SSI_MULTIPART
+ if (ssi->tag_part == HTTPD_LAST_TAG_PART)
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+ {
+ /* We have sent all the insert data so go back to looking for
+ * a new tag. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Everything sent.\n"));
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_NONE;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ ssi->parsed = ssi->tag_end;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* If we drop out of the end of the for loop, this implies we must have
+ * file data to send so send it now. In TAG_SENDING state, we've already
+ * handled this so skip the send if that's the case. */
+ if ((ssi->tag_state != TAG_SENDING) && (ssi->parsed > hs->file)) {
+#if LWIP_HTTPD_DYNAMIC_FILE_READ && !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if ((ssi->tag_state != TAG_NONE) && (ssi->tag_started > ssi->tag_end)) {
+ /* If we found tag on the edge of the read buffer: just throw away the first part
+ (we have copied/saved everything required for parsing on later). */
+ len = (u16_t)(ssi->tag_started - hs->file);
+ hs->left -= (ssi->parsed - ssi->tag_started);
+ ssi->parsed = ssi->tag_started;
+ ssi->tag_started = hs->buf;
+ } else
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ && !LWIP_HTTPD_SSI_INCLUDE_TAG */
+ {
+ len = (u16_t)LWIP_MIN(ssi->parsed - hs->file, 0xffff);
+ }
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ hs->file += len;
+ hs->left -= len;
+ }
+ }
+ return data_to_send;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/**
+ * Try to send more data on this pcb.
+ *
+ * @param pcb the pcb to send data
+ * @param hs connection state
+ */
+static u8_t
+http_send(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ u8_t data_to_send = HTTP_NO_DATA_TO_SEND;
+
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_send: pcb=%p hs=%p left=%d\n", (void *)pcb,
+ (void *)hs, hs != NULL ? (int)hs->left : 0));
+
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ if (hs->unrecved_bytes != 0) {
+ return 0;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+
+ /* If we were passed a NULL state structure pointer, ignore the call. */
+ if (hs == NULL) {
+ return 0;
+ }
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+ /* Check if we are allowed to read from this file.
+ (e.g. SSI might want to delay sending until data is available) */
+ if (!fs_is_file_ready(hs->handle, http_continue, hs)) {
+ return 0;
+ }
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ /* Do we have any more header data to send for this file? */
+ if (hs->hdr_index < NUM_FILE_HDR_STRINGS) {
+ data_to_send = http_send_headers(pcb, hs);
+ if ((data_to_send == HTTP_DATA_TO_SEND_FREED) ||
+ ((data_to_send != HTTP_DATA_TO_SEND_CONTINUE) &&
+ (hs->hdr_index < NUM_FILE_HDR_STRINGS))) {
+ return data_to_send;
+ }
+ }
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI
+ if (hs->ssi && (hs->ssi->tag_state == TAG_SENDING)) {
+ /* do not check the condition below */
+ } else
+#endif
+ /* Have we run out of file data to send? If so, we need to read the next
+ * block from the file. */
+ if (hs->left == 0) {
+ if (!http_check_eof(pcb, hs)) {
+ return 0;
+ }
+ }
+
+#if LWIP_HTTPD_SSI
+ if (hs->ssi) {
+ data_to_send = http_send_data_ssi(pcb, hs);
+ if (hs->ssi->tag_state == TAG_SENDING) {
+ return data_to_send;
+ }
+ } else
+#endif /* LWIP_HTTPD_SSI */
+ {
+ data_to_send = http_send_data_nonssi(pcb, hs);
+ }
+
+ if ((hs->left == 0) && (fs_bytes_left(hs->handle) <= 0)) {
+ /* We reached the end of the file so this request is done.
+ * This adds the FIN flag right into the last data segment. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+ http_eof(pcb, hs);
+ return 0;
+ }
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("send_data end.\n"));
+ return data_to_send;
+}
+
+#if LWIP_HTTPD_SUPPORT_EXTSTATUS
+/** Initialize a http connection with a file to send for an error message
+ *
+ * @param hs http connection state
+ * @param error_nr HTTP error number
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ * another err_t otherwise
+ */
+static err_t
+http_find_error_file(struct http_state *hs, u16_t error_nr)
+{
+ const char *uri, *uri1, *uri2, *uri3;
+
+ if (error_nr == 501) {
+ uri1 = "/501.html";
+ uri2 = "/501.htm";
+ uri3 = "/501.shtml";
+ } else {
+ /* 400 (bad request is the default) */
+ uri1 = "/400.html";
+ uri2 = "/400.htm";
+ uri3 = "/400.shtml";
+ }
+ if (fs_open(&hs->file_handle, uri1) == ERR_OK) {
+ uri = uri1;
+ } else if (fs_open(&hs->file_handle, uri2) == ERR_OK) {
+ uri = uri2;
+ } else if (fs_open(&hs->file_handle, uri3) == ERR_OK) {
+ uri = uri3;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error page for error %"U16_F" not found\n",
+ error_nr));
+ return ERR_ARG;
+ }
+ return http_init_file(hs, &hs->file_handle, 0, uri, 0, NULL);
+}
+#else /* LWIP_HTTPD_SUPPORT_EXTSTATUS */
+#define http_find_error_file(hs, error_nr) ERR_ARG
+#endif /* LWIP_HTTPD_SUPPORT_EXTSTATUS */
+
+/**
+ * Get the file struct for a 404 error page.
+ * Tries some file names and returns NULL if none found.
+ *
+ * @param uri pointer that receives the actual file name URI
+ * @return file struct for the error page or NULL no matching file was found
+ */
+static struct fs_file *
+http_get_404_file(struct http_state *hs, const char **uri)
+{
+ err_t err;
+
+ *uri = "/404.html";
+ err = fs_open(&hs->file_handle, *uri);
+ if (err != ERR_OK) {
+ /* 404.html doesn't exist. Try 404.htm instead. */
+ *uri = "/404.htm";
+ err = fs_open(&hs->file_handle, *uri);
+ if (err != ERR_OK) {
+ /* 404.htm doesn't exist either. Try 404.shtml instead. */
+ *uri = "/404.shtml";
+ err = fs_open(&hs->file_handle, *uri);
+ if (err != ERR_OK) {
+ /* 404.htm doesn't exist either. Indicate to the caller that it should
+ * send back a default 404 page.
+ */
+ *uri = NULL;
+ return NULL;
+ }
+ }
+ }
+
+ return &hs->file_handle;
+}
+
+#if LWIP_HTTPD_SUPPORT_POST
+static err_t
+http_handle_post_finished(struct http_state *hs)
+{
+#if LWIP_HTTPD_POST_MANUAL_WND
+ /* Prevent multiple calls to httpd_post_finished, since it might have already
+ been called before from httpd_post_data_recved(). */
+ if (hs->post_finished) {
+ return ERR_OK;
+ }
+ hs->post_finished = 1;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ /* application error or POST finished */
+ /* NULL-terminate the buffer */
+ http_uri_buf[0] = 0;
+ httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN);
+ return http_find_file(hs, http_uri_buf, 0);
+}
+
+/** Pass received POST body data to the application and correctly handle
+ * returning a response document or closing the connection.
+ * ATTENTION: The application is responsible for the pbuf now, so don't free it!
+ *
+ * @param hs http connection state
+ * @param p pbuf to pass to the application
+ * @return ERR_OK if passed successfully, another err_t if the response file
+ * hasn't been found (after POST finished)
+ */
+static err_t
+http_post_rxpbuf(struct http_state *hs, struct pbuf *p)
+{
+ err_t err;
+
+ if (p != NULL) {
+ /* adjust remaining Content-Length */
+ if (hs->post_content_len_left < p->tot_len) {
+ hs->post_content_len_left = 0;
+ } else {
+ hs->post_content_len_left -= p->tot_len;
+ }
+ }
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ /* prevent connection being closed if httpd_post_data_recved() is called nested */
+ hs->unrecved_bytes++;
+#endif
+ if (p != NULL) {
+ err = httpd_post_receive_data(hs, p);
+ } else {
+ err = ERR_OK;
+ }
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ hs->unrecved_bytes--;
+#endif
+ if (err != ERR_OK) {
+ /* Ignore remaining content in case of application error */
+ hs->post_content_len_left = 0;
+ }
+ if (hs->post_content_len_left == 0) {
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ if (hs->unrecved_bytes != 0) {
+ return ERR_OK;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+ /* application error or POST finished */
+ return http_handle_post_finished(hs);
+ }
+
+ return ERR_OK;
+}
+
+/** Handle a post request. Called from http_parse_request when method 'POST'
+ * is found.
+ *
+ * @param p The input pbuf (containing the POST header and body).
+ * @param hs The http connection state.
+ * @param data HTTP request (header and part of body) from input pbuf(s).
+ * @param data_len Size of 'data'.
+ * @param uri The HTTP URI parsed from input pbuf(s).
+ * @param uri_end Pointer to the end of 'uri' (here, the rest of the HTTP
+ * header starts).
+ * @return ERR_OK: POST correctly parsed and accepted by the application.
+ * ERR_INPROGRESS: POST not completely parsed (no error yet)
+ * another err_t: Error parsing POST or denied by the application
+ */
+static err_t
+http_post_request(struct pbuf *inp, struct http_state *hs,
+ char *data, u16_t data_len, char *uri, char *uri_end)
+{
+ err_t err;
+ /* search for end-of-header (first double-CRLF) */
+ char *crlfcrlf = lwip_strnstr(uri_end + 1, CRLF CRLF, data_len - (uri_end + 1 - data));
+
+ if (crlfcrlf != NULL) {
+ /* search for "Content-Length: " */
+#define HTTP_HDR_CONTENT_LEN "Content-Length: "
+#define HTTP_HDR_CONTENT_LEN_LEN 16
+#define HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN 10
+ char *scontent_len = lwip_strnstr(uri_end + 1, HTTP_HDR_CONTENT_LEN, crlfcrlf - (uri_end + 1));
+ if (scontent_len != NULL) {
+ char *scontent_len_end = lwip_strnstr(scontent_len + HTTP_HDR_CONTENT_LEN_LEN, CRLF, HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN);
+ if (scontent_len_end != NULL) {
+ int content_len;
+ char *content_len_num = scontent_len + HTTP_HDR_CONTENT_LEN_LEN;
+ content_len = atoi(content_len_num);
+ if (content_len == 0) {
+ /* if atoi returns 0 on error, fix this */
+ if ((content_len_num[0] != '0') || (content_len_num[1] != '\r')) {
+ content_len = -1;
+ }
+ }
+ if (content_len >= 0) {
+ /* adjust length of HTTP header passed to application */
+ const char *hdr_start_after_uri = uri_end + 1;
+ u16_t hdr_len = (u16_t)LWIP_MIN(data_len, crlfcrlf + 4 - data);
+ u16_t hdr_data_len = (u16_t)LWIP_MIN(data_len, crlfcrlf + 4 - hdr_start_after_uri);
+ u8_t post_auto_wnd = 1;
+ http_uri_buf[0] = 0;
+ /* trim http header */
+ *crlfcrlf = 0;
+ err = httpd_post_begin(hs, uri, hdr_start_after_uri, hdr_data_len, content_len,
+ http_uri_buf, LWIP_HTTPD_URI_BUF_LEN, &post_auto_wnd);
+ if (err == ERR_OK) {
+ /* try to pass in data of the first pbuf(s) */
+ struct pbuf *q = inp;
+ u16_t start_offset = hdr_len;
+#if LWIP_HTTPD_POST_MANUAL_WND
+ hs->no_auto_wnd = !post_auto_wnd;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ /* set the Content-Length to be received for this POST */
+ hs->post_content_len_left = (u32_t)content_len;
+
+ /* get to the pbuf where the body starts */
+ while ((q != NULL) && (q->len <= start_offset)) {
+ start_offset -= q->len;
+ q = q->next;
+ }
+ if (q != NULL) {
+ /* hide the remaining HTTP header */
+ pbuf_remove_header(q, start_offset);
+#if LWIP_HTTPD_POST_MANUAL_WND
+ if (!post_auto_wnd) {
+ /* already tcp_recved() this data... */
+ hs->unrecved_bytes = q->tot_len;
+ }
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ pbuf_ref(q);
+ return http_post_rxpbuf(hs, q);
+ } else if (hs->post_content_len_left == 0) {
+ q = pbuf_alloc(PBUF_RAW, 0, PBUF_REF);
+ return http_post_rxpbuf(hs, q);
+ } else {
+ return ERR_OK;
+ }
+ } else {
+ /* return file passed from application */
+ return http_find_file(hs, http_uri_buf, 0);
+ }
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("POST received invalid Content-Length: %s\n",
+ content_len_num));
+ return ERR_ARG;
+ }
+ }
+ }
+ /* If we come here, headers are fully received (double-crlf), but Content-Length
+ was not included. Since this is currently the only supported method, we have
+ to fail in this case! */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error when parsing Content-Length\n"));
+ return ERR_ARG;
+ }
+ /* if we come here, the POST is incomplete */
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ return ERR_INPROGRESS;
+#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ return ERR_ARG;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+}
+
+#if LWIP_HTTPD_POST_MANUAL_WND
+/**
+ * @ingroup httpd
+ * A POST implementation can call this function to update the TCP window.
+ * This can be used to throttle data reception (e.g. when received data is
+ * programmed to flash and data is received faster than programmed).
+ *
+ * @param connection A connection handle passed to httpd_post_begin for which
+ * httpd_post_finished has *NOT* been called yet!
+ * @param recved_len Length of data received (for window update)
+ */
+void httpd_post_data_recved(void *connection, u16_t recved_len)
+{
+ struct http_state *hs = (struct http_state *)connection;
+ if (hs != NULL) {
+ if (hs->no_auto_wnd) {
+ u16_t len = recved_len;
+ if (hs->unrecved_bytes >= recved_len) {
+ hs->unrecved_bytes -= recved_len;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("httpd_post_data_recved: recved_len too big\n"));
+ len = (u16_t)hs->unrecved_bytes;
+ hs->unrecved_bytes = 0;
+ }
+ if (hs->pcb != NULL) {
+ if (len != 0) {
+ altcp_recved(hs->pcb, len);
+ }
+ if ((hs->post_content_len_left == 0) && (hs->unrecved_bytes == 0)) {
+ /* finished handling POST */
+ http_handle_post_finished(hs);
+ http_send(hs->pcb, hs);
+ }
+ }
+ }
+ }
+}
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+/** Try to send more data if file has been blocked before
+ * This is a callback function passed to fs_read_async().
+ */
+static void
+http_continue(void *connection)
+{
+ struct http_state *hs = (struct http_state *)connection;
+ LWIP_ASSERT_CORE_LOCKED();
+ if (hs && (hs->pcb) && (hs->handle)) {
+ LWIP_ASSERT("hs->pcb != NULL", hs->pcb != NULL);
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("httpd_continue: try to send more data\n"));
+ if (http_send(hs->pcb, hs)) {
+ /* If we wrote anything to be sent, go ahead and send it now. */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
+ altcp_output(hs->pcb);
+ }
+ }
+}
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+/**
+ * When data has been received in the correct state, try to parse it
+ * as a HTTP request.
+ *
+ * @param inp the received pbuf
+ * @param hs the connection state
+ * @param pcb the altcp_pcb which received this packet
+ * @return ERR_OK if request was OK and hs has been initialized correctly
+ * ERR_INPROGRESS if request was OK so far but not fully received
+ * another err_t otherwise
+ */
+static err_t
+http_parse_request(struct pbuf *inp, struct http_state *hs, struct altcp_pcb *pcb)
+{
+ char *data;
+ char *crlf;
+ u16_t data_len;
+ struct pbuf *p = inp;
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ u16_t clen;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+#if LWIP_HTTPD_SUPPORT_POST
+ err_t err;
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+ LWIP_UNUSED_ARG(pcb); /* only used for post */
+ LWIP_ASSERT("p != NULL", p != NULL);
+ LWIP_ASSERT("hs != NULL", hs != NULL);
+
+ if ((hs->handle != NULL) || (hs->file != NULL)) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Received data while sending a file\n"));
+ /* already sending a file */
+ /* @todo: abort? */
+ return ERR_USE;
+ }
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Received %"U16_F" bytes\n", p->tot_len));
+
+ /* first check allowed characters in this pbuf? */
+
+ /* enqueue the pbuf */
+ if (hs->req == NULL) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("First pbuf\n"));
+ hs->req = p;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("pbuf enqueued\n"));
+ pbuf_cat(hs->req, p);
+ }
+ /* increase pbuf ref counter as it is freed when we return but we want to
+ keep it on the req list */
+ pbuf_ref(p);
+
+ if (hs->req->next != NULL) {
+ data_len = LWIP_MIN(hs->req->tot_len, LWIP_HTTPD_MAX_REQ_LENGTH);
+ pbuf_copy_partial(hs->req, httpd_req_buf, data_len, 0);
+ data = httpd_req_buf;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ {
+ data = (char *)p->payload;
+ data_len = p->len;
+ if (p->len != p->tot_len) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Warning: incomplete header due to chained pbufs\n"));
+ }
+ }
+
+ /* received enough data for minimal request? */
+ if (data_len >= MIN_REQ_LEN) {
+ /* wait for CRLF before parsing anything */
+ crlf = lwip_strnstr(data, CRLF, data_len);
+ if (crlf != NULL) {
+#if LWIP_HTTPD_SUPPORT_POST
+ int is_post = 0;
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ int is_09 = 0;
+ char *sp1, *sp2;
+ u16_t left_len, uri_len;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("CRLF received, parsing request\n"));
+ /* parse method */
+ if (!strncmp(data, "GET ", 4)) {
+ sp1 = data + 3;
+ /* received GET request */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Received GET request\"\n"));
+#if LWIP_HTTPD_SUPPORT_POST
+ } else if (!strncmp(data, "POST ", 5)) {
+ /* store request type */
+ is_post = 1;
+ sp1 = data + 4;
+ /* received GET request */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Received POST request\n"));
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ } else {
+ /* null-terminate the METHOD (pbuf is freed anyway wen returning) */
+ data[4] = 0;
+ /* unsupported method! */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Unsupported request method (not implemented): \"%s\"\n",
+ data));
+ return http_find_error_file(hs, 501);
+ }
+ /* if we come here, method is OK, parse URI */
+ left_len = (u16_t)(data_len - ((sp1 + 1) - data));
+ sp2 = lwip_strnstr(sp1 + 1, " ", left_len);
+#if LWIP_HTTPD_SUPPORT_V09
+ if (sp2 == NULL) {
+ /* HTTP 0.9: respond with correct protocol version */
+ sp2 = lwip_strnstr(sp1 + 1, CRLF, left_len);
+ is_09 = 1;
+#if LWIP_HTTPD_SUPPORT_POST
+ if (is_post) {
+ /* HTTP/0.9 does not support POST */
+ goto badrequest;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ }
+#endif /* LWIP_HTTPD_SUPPORT_V09 */
+ uri_len = (u16_t)(sp2 - (sp1 + 1));
+ if ((sp2 != NULL) && (sp2 > sp1)) {
+ /* wait for CRLFCRLF (indicating end of HTTP headers) before parsing anything */
+ if (lwip_strnstr(data, CRLF CRLF, data_len) != NULL) {
+ char *uri = sp1 + 1;
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ /* This is HTTP/1.0 compatible: for strict 1.1, a connection
+ would always be persistent unless "close" was specified. */
+ if (!is_09 && (lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE, data_len) ||
+ lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE2, data_len))) {
+ hs->keepalive = 1;
+ } else {
+ hs->keepalive = 0;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ /* null-terminate the METHOD (pbuf is freed anyway wen returning) */
+ *sp1 = 0;
+ uri[uri_len] = 0;
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Received \"%s\" request for URI: \"%s\"\n",
+ data, uri));
+#if LWIP_HTTPD_SUPPORT_POST
+ if (is_post) {
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ struct pbuf *q = hs->req;
+#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ struct pbuf *q = inp;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ err = http_post_request(q, hs, data, data_len, uri, sp2);
+ if (err != ERR_OK) {
+ /* restore header for next try */
+ *sp1 = ' ';
+ *sp2 = ' ';
+ uri[uri_len] = ' ';
+ }
+ if (err == ERR_ARG) {
+ goto badrequest;
+ }
+ return err;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ {
+ return http_find_file(hs, uri, is_09);
+ }
+ }
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("invalid URI\n"));
+ }
+ }
+ }
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ clen = pbuf_clen(hs->req);
+ if ((hs->req->tot_len <= LWIP_HTTPD_REQ_BUFSIZE) &&
+ (clen <= LWIP_HTTPD_REQ_QUEUELEN)) {
+ /* request not fully received (too short or CRLF is missing) */
+ return ERR_INPROGRESS;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ {
+#if LWIP_HTTPD_SUPPORT_POST
+badrequest:
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("bad request\n"));
+ /* could not parse request */
+ return http_find_error_file(hs, 400);
+ }
+}
+
+#if LWIP_HTTPD_SSI && (LWIP_HTTPD_SSI_BY_FILE_EXTENSION == 1)
+/* Check if SSI should be parsed for this file/URL
+ * (With LWIP_HTTPD_SSI_BY_FILE_EXTENSION == 2, this function can be
+ * overridden by an external implementation.)
+ *
+ * @return 1 for SSI, 0 for standard files
+ */
+static u8_t
+http_uri_is_ssi(struct fs_file *file, const char *uri)
+{
+ size_t loop;
+ u8_t tag_check = 0;
+ if (file != NULL) {
+ /* See if we have been asked for an shtml file and, if so,
+ enable tag checking. */
+ const char *ext = NULL, *sub;
+ char *param = (char *)strstr(uri, "?");
+ if (param != NULL) {
+ /* separate uri from parameters for now, set back later */
+ *param = 0;
+ }
+ sub = uri;
+ ext = uri;
+ for (sub = strstr(sub, "."); sub != NULL; sub = strstr(sub, ".")) {
+ ext = sub;
+ sub++;
+ }
+ for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) {
+ if (!lwip_stricmp(ext, g_pcSSIExtensions[loop])) {
+ tag_check = 1;
+ break;
+ }
+ }
+ if (param != NULL) {
+ *param = '?';
+ }
+ }
+ return tag_check;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/** Try to find the file specified by uri and, if found, initialize hs
+ * accordingly.
+ *
+ * @param hs the connection state
+ * @param uri the HTTP header URI
+ * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response)
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ * another err_t otherwise
+ */
+static err_t
+http_find_file(struct http_state *hs, const char *uri, int is_09)
+{
+ size_t loop;
+ struct fs_file *file = NULL;
+ char *params = NULL;
+ err_t err;
+#if LWIP_HTTPD_CGI
+ int i;
+#endif /* LWIP_HTTPD_CGI */
+#if !LWIP_HTTPD_SSI
+ const
+#endif /* !LWIP_HTTPD_SSI */
+ /* By default, assume we will not be processing server-side-includes tags */
+ u8_t tag_check = 0;
+
+ /* Have we been asked for the default file (in root or a directory) ? */
+#if LWIP_HTTPD_MAX_REQUEST_URI_LEN
+ size_t uri_len = strlen(uri);
+ if ((uri_len > 0) && (uri[uri_len - 1] == '/') &&
+ ((uri != http_uri_buf) || (uri_len == 1))) {
+ size_t copy_len = LWIP_MIN(sizeof(http_uri_buf) - 1, uri_len - 1);
+ if (copy_len > 0) {
+ MEMCPY(http_uri_buf, uri, copy_len);
+ http_uri_buf[copy_len] = 0;
+ }
+#else /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+ if ((uri[0] == '/') && (uri[1] == 0)) {
+#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+ /* Try each of the configured default filenames until we find one
+ that exists. */
+ for (loop = 0; loop < NUM_DEFAULT_FILENAMES; loop++) {
+ const char *file_name;
+#if LWIP_HTTPD_MAX_REQUEST_URI_LEN
+ if (copy_len > 0) {
+ size_t len_left = sizeof(http_uri_buf) - copy_len - 1;
+ if (len_left > 0) {
+ size_t name_len = strlen(httpd_default_filenames[loop].name);
+ size_t name_copy_len = LWIP_MIN(len_left, name_len);
+ MEMCPY(&http_uri_buf[copy_len], httpd_default_filenames[loop].name, name_copy_len);
+ http_uri_buf[copy_len + name_copy_len] = 0;
+ }
+ file_name = http_uri_buf;
+ } else
+#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+ {
+ file_name = httpd_default_filenames[loop].name;
+ }
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Looking for %s...\n", file_name));
+ err = fs_open(&hs->file_handle, file_name);
+ if (err == ERR_OK) {
+ uri = file_name;
+ file = &hs->file_handle;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opened.\n"));
+#if LWIP_HTTPD_SSI
+ tag_check = httpd_default_filenames[loop].shtml;
+#endif /* LWIP_HTTPD_SSI */
+ break;
+ }
+ }
+ }
+ if (file == NULL) {
+ /* No - we've been asked for a specific file. */
+ /* First, isolate the base URI (without any parameters) */
+ params = (char *)strchr(uri, '?');
+ if (params != NULL) {
+ /* URI contains parameters. NULL-terminate the base URI */
+ *params = '\0';
+ params++;
+ }
+
+#if LWIP_HTTPD_CGI
+ http_cgi_paramcount = -1;
+ /* Does the base URI we have isolated correspond to a CGI handler? */
+ if (httpd_num_cgis && httpd_cgis) {
+ for (i = 0; i < httpd_num_cgis; i++) {
+ if (strcmp(uri, httpd_cgis[i].pcCGIName) == 0) {
+ /*
+ * We found a CGI that handles this URI so extract the
+ * parameters and call the handler.
+ */
+ http_cgi_paramcount = extract_uri_parameters(hs, params);
+ uri = httpd_cgis[i].pfnCGIHandler(i, http_cgi_paramcount, hs->params,
+ hs->param_vals);
+ break;
+ }
+ }
+ }
+#endif /* LWIP_HTTPD_CGI */
+
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opening %s\n", uri));
+
+ err = fs_open(&hs->file_handle, uri);
+ if (err == ERR_OK) {
+ file = &hs->file_handle;
+ } else {
+ file = http_get_404_file(hs, &uri);
+ }
+#if LWIP_HTTPD_SSI
+ if (file != NULL) {
+ if (file->flags & FS_FILE_FLAGS_SSI) {
+ tag_check = 1;
+ } else {
+#if LWIP_HTTPD_SSI_BY_FILE_EXTENSION
+ tag_check = http_uri_is_ssi(file, uri);
+#endif /* LWIP_HTTPD_SSI_BY_FILE_EXTENSION */
+ }
+ }
+#endif /* LWIP_HTTPD_SSI */
+ }
+ if (file == NULL) {
+ /* None of the default filenames exist so send back a 404 page */
+ file = http_get_404_file(hs, &uri);
+ }
+ return http_init_file(hs, file, is_09, uri, tag_check, params);
+}
+
+/** Initialize a http connection with a file to send (if found).
+ * Called by http_find_file and http_find_error_file.
+ *
+ * @param hs http connection state
+ * @param file file structure to send (or NULL if not found)
+ * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response)
+ * @param uri the HTTP header URI
+ * @param tag_check enable SSI tag checking
+ * @param params != NULL if URI has parameters (separated by '?')
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ * another err_t otherwise
+ */
+static err_t
+http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri,
+ u8_t tag_check, char *params)
+{
+#if !LWIP_HTTPD_SUPPORT_V09
+ LWIP_UNUSED_ARG(is_09);
+#endif
+ if (file != NULL) {
+ /* file opened, initialise struct http_state */
+#if !LWIP_HTTPD_DYNAMIC_FILE_READ
+ /* If dynamic read is disabled, file data must be in one piece and available now */
+ LWIP_ASSERT("file->data != NULL", file->data != NULL);
+#endif
+
+#if LWIP_HTTPD_SSI
+ if (tag_check) {
+ struct http_ssi_state *ssi = http_ssi_state_alloc();
+ if (ssi != NULL) {
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_NONE;
+ ssi->parsed = file->data;
+ ssi->parse_left = file->len;
+ ssi->tag_end = file->data;
+ hs->ssi = ssi;
+ }
+ }
+#else /* LWIP_HTTPD_SSI */
+ LWIP_UNUSED_ARG(tag_check);
+#endif /* LWIP_HTTPD_SSI */
+ hs->handle = file;
+#if LWIP_HTTPD_CGI_SSI
+ if (params != NULL) {
+ /* URI contains parameters, call generic CGI handler */
+ int count;
+#if LWIP_HTTPD_CGI
+ if (http_cgi_paramcount >= 0) {
+ count = http_cgi_paramcount;
+ } else
+#endif
+ {
+ count = extract_uri_parameters(hs, params);
+ }
+ httpd_cgi_handler(file, uri, count, http_cgi_params, http_cgi_param_vals
+#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE
+ , file->state
+#endif /* LWIP_HTTPD_FILE_STATE */
+ );
+ }
+#else /* LWIP_HTTPD_CGI_SSI */
+ LWIP_UNUSED_ARG(params);
+#endif /* LWIP_HTTPD_CGI_SSI */
+ hs->file = file->data;
+ LWIP_ASSERT("File length must be positive!", (file->len >= 0));
+#if LWIP_HTTPD_CUSTOM_FILES
+ if (((file->flags & FS_FILE_FLAGS_CUSTOM) != 0) && (file->data == NULL)) {
+ /* custom file, need to read data first (via fs_read_custom) */
+ hs->left = 0;
+ } else
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+ {
+ hs->left = (u32_t)file->len;
+ }
+ hs->retries = 0;
+#if LWIP_HTTPD_TIMING
+ hs->time_started = sys_now();
+#endif /* LWIP_HTTPD_TIMING */
+#if !LWIP_HTTPD_DYNAMIC_HEADERS
+ LWIP_ASSERT("HTTP headers not included in file system",
+ (hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0);
+#endif /* !LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_SUPPORT_V09
+ if (is_09 && ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0)) {
+ /* HTTP/0.9 responses are sent without HTTP header,
+ search for the end of the header. */
+ char *file_start = lwip_strnstr(hs->file, CRLF CRLF, hs->left);
+ if (file_start != NULL) {
+ int diff = file_start + 4 - hs->file;
+ hs->file += diff;
+ hs->left -= (u32_t)diff;
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_V09*/
+ } else {
+ hs->handle = NULL;
+ hs->file = NULL;
+ hs->left = 0;
+ hs->retries = 0;
+ }
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ /* Determine the HTTP headers to send based on the file extension of
+ * the requested URI. */
+ if ((hs->handle == NULL) || ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0)) {
+ get_http_headers(hs, uri);
+ }
+#else /* LWIP_HTTPD_DYNAMIC_HEADERS */
+ LWIP_UNUSED_ARG(uri);
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (hs->keepalive) {
+#if LWIP_HTTPD_SSI
+ if (hs->ssi != NULL) {
+ hs->keepalive = 0;
+ } else
+#endif /* LWIP_HTTPD_SSI */
+ {
+ if ((hs->handle != NULL) &&
+ ((hs->handle->flags & (FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT)) == FS_FILE_FLAGS_HEADER_INCLUDED)) {
+ hs->keepalive = 0;
+ }
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ 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\n", 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 altcp_pcb *pcb, u16_t len)
+{
+ struct http_state *hs = (struct http_state *)arg;
+
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_sent %p\n", (void *)pcb));
+
+ LWIP_UNUSED_ARG(len);
+
+ if (hs == NULL) {
+ return ERR_OK;
+ }
+
+ hs->retries = 0;
+
+ http_send(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 altcp_pcb *pcb)
+{
+ struct http_state *hs = (struct http_state *)arg;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: pcb=%p hs=%p pcb_state=%s\n",
+ (void *)pcb, (void *)hs, tcp_debug_state_str(altcp_dbg_get_tcp_state(pcb))));
+
+ if (hs == NULL) {
+ err_t closed;
+ /* arg is null, close. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: arg is NULL, close\n"));
+ closed = http_close_conn(pcb, NULL);
+ LWIP_UNUSED_ARG(closed);
+#if LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR
+ if (closed == ERR_MEM) {
+ altcp_abort(pcb);
+ return ERR_ABRT;
+ }
+#endif /* LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR */
+ return ERR_OK;
+ } 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_OK;
+ }
+
+ /* If this connection has a file open, try to send some more data. If
+ * it has not yet received a GET request, don't do this since it will
+ * cause the connection to close immediately. */
+ if (hs->handle) {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: try to send more data\n"));
+ if (http_send(pcb, hs)) {
+ /* If we wrote anything to be sent, go ahead and send it now. */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
+ altcp_output(pcb);
+ }
+ }
+ }
+
+ 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 altcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+ struct http_state *hs = (struct http_state *)arg;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void *)pcb,
+ (void *)p, lwip_strerr(err)));
+
+ if ((err != ERR_OK) || (p == NULL) || (hs == NULL)) {
+ /* error or closed by other side? */
+ if (p != NULL) {
+ /* Inform TCP that we have taken the data. */
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ }
+ if (hs == NULL) {
+ /* this should not happen, only to be robust */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error, http_recv: hs is NULL, close\n"));
+ }
+ http_close_conn(pcb, hs);
+ return ERR_OK;
+ }
+
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ if (hs->no_auto_wnd) {
+ hs->unrecved_bytes += p->tot_len;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+ {
+ /* Inform TCP that we have taken the data. */
+ altcp_recved(pcb, p->tot_len);
+ }
+
+#if LWIP_HTTPD_SUPPORT_POST
+ if (hs->post_content_len_left > 0) {
+ /* reset idle counter when POST data is received */
+ hs->retries = 0;
+ /* this is data for a POST, pass the complete pbuf to the application */
+ http_post_rxpbuf(hs, p);
+ /* pbuf is passed to the application, don't free it! */
+ if (hs->post_content_len_left == 0) {
+ /* all data received, send response or close connection */
+ http_send(pcb, hs);
+ }
+ return ERR_OK;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ {
+ if (hs->handle == NULL) {
+ err_t parsed = http_parse_request(p, hs, pcb);
+ LWIP_ASSERT("http_parse_request: unexpected return value", parsed == ERR_OK
+ || parsed == ERR_INPROGRESS || parsed == ERR_ARG || parsed == ERR_USE);
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ if (parsed != ERR_INPROGRESS) {
+ /* request fully parsed or error */
+ if (hs->req != NULL) {
+ pbuf_free(hs->req);
+ hs->req = NULL;
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ pbuf_free(p);
+ if (parsed == ERR_OK) {
+#if LWIP_HTTPD_SUPPORT_POST
+ if (hs->post_content_len_left == 0)
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: data %p len %"S32_F"\n", (const void *)hs->file, hs->left));
+ http_send(pcb, hs);
+ }
+ } else if (parsed == ERR_ARG) {
+ /* @todo: close on ERR_USE? */
+ http_close_conn(pcb, hs);
+ }
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n"));
+ /* already sending but still receiving data, we might want to RST here? */
+ pbuf_free(p);
+ }
+ }
+ return ERR_OK;
+}
+
+/**
+ * A new incoming connection has been accepted.
+ */
+static err_t
+http_accept(void *arg, struct altcp_pcb *pcb, err_t err)
+{
+ struct http_state *hs;
+ LWIP_UNUSED_ARG(err);
+ LWIP_UNUSED_ARG(arg);
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept %p / %p\n", (void *)pcb, arg));
+
+ if ((err != ERR_OK) || (pcb == NULL)) {
+ return ERR_VAL;
+ }
+
+ /* Set priority */
+ altcp_setprio(pcb, HTTPD_TCP_PRIO);
+
+ /* Allocate memory for the structure that holds the state of the
+ connection - initialized by that function. */
+ hs = http_state_alloc();
+ if (hs == NULL) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept: Out of memory, RST\n"));
+ return ERR_MEM;
+ }
+ hs->pcb = pcb;
+
+ /* Tell TCP that this is the structure we wish to be passed for our
+ callbacks. */
+ altcp_arg(pcb, hs);
+
+ /* Set up the various callback functions */
+ altcp_recv(pcb, http_recv);
+ altcp_err(pcb, http_err);
+ altcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
+ altcp_sent(pcb, http_sent);
+
+ return ERR_OK;
+}
+
+static void
+httpd_init_pcb(struct altcp_pcb *pcb, u16_t port)
+{
+ err_t err;
+
+ if (pcb) {
+ altcp_setprio(pcb, HTTPD_TCP_PRIO);
+ /* set SOF_REUSEADDR here to explicitly bind httpd to multiple interfaces */
+ err = altcp_bind(pcb, IP_ANY_TYPE, port);
+ LWIP_UNUSED_ARG(err); /* in case of LWIP_NOASSERT */
+ LWIP_ASSERT("httpd_init: tcp_bind failed", err == ERR_OK);
+ pcb = altcp_listen(pcb);
+ LWIP_ASSERT("httpd_init: tcp_listen failed", pcb != NULL);
+ altcp_accept(pcb, http_accept);
+ }
+}
+
+/**
+ * @ingroup httpd
+ * Initialize the httpd: set up a listening PCB and bind it to the defined port
+ */
+void
+httpd_init(void)
+{
+ struct altcp_pcb *pcb;
+
+#if HTTPD_USE_MEM_POOL
+ LWIP_MEMPOOL_INIT(HTTPD_STATE);
+#if LWIP_HTTPD_SSI
+ LWIP_MEMPOOL_INIT(HTTPD_SSI_STATE);
+#endif
+#endif
+ LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n"));
+
+ /* LWIP_ASSERT_CORE_LOCKED(); is checked by tcp_new() */
+
+ pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY);
+ LWIP_ASSERT("httpd_init: tcp_new failed", pcb != NULL);
+ httpd_init_pcb(pcb, HTTPD_SERVER_PORT);
+}
+
+#if HTTPD_ENABLE_HTTPS
+/**
+ * @ingroup httpd
+ * Initialize the httpd: set up a listening PCB and bind it to the defined port.
+ * Also set up TLS connection handling (HTTPS).
+ */
+void
+httpd_inits(struct altcp_tls_config *conf)
+{
+#if LWIP_ALTCP_TLS
+ struct altcp_pcb *pcb_tls = altcp_tls_new(conf, IPADDR_TYPE_ANY);
+ LWIP_ASSERT("httpd_init: altcp_tls_new failed", pcb_tls != NULL);
+ httpd_init_pcb(pcb_tls, HTTPD_SERVER_PORT_HTTPS);
+#else /* LWIP_ALTCP_TLS */
+ LWIP_UNUSED_ARG(conf);
+#endif /* LWIP_ALTCP_TLS */
+}
+#endif /* HTTPD_ENABLE_HTTPS */
+
+#if LWIP_HTTPD_SSI
+/**
+ * @ingroup httpd
+ * Set the SSI handler function.
+ *
+ * @param ssi_handler the SSI handler function
+ * @param tags an array of SSI tag strings to search for in SSI-enabled files
+ * @param num_tags number of tags in the 'tags' array
+ */
+void
+http_set_ssi_handler(tSSIHandler ssi_handler, const char **tags, int num_tags)
+{
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_set_ssi_handler\n"));
+
+ LWIP_ASSERT("no ssi_handler given", ssi_handler != NULL);
+ httpd_ssi_handler = ssi_handler;
+
+#if LWIP_HTTPD_SSI_RAW
+ LWIP_UNUSED_ARG(tags);
+ LWIP_UNUSED_ARG(num_tags);
+#else /* LWIP_HTTPD_SSI_RAW */
+ LWIP_ASSERT("no tags given", tags != NULL);
+ LWIP_ASSERT("invalid number of tags", num_tags > 0);
+
+ httpd_tags = tags;
+ httpd_num_tags = num_tags;
+#endif /* !LWIP_HTTPD_SSI_RAW */
+}
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_CGI
+/**
+ * @ingroup httpd
+ * Set an array of CGI filenames/handler functions
+ *
+ * @param cgis an array of CGI filenames/handler functions
+ * @param num_handlers number of elements in the 'cgis' array
+ */
+void
+http_set_cgi_handlers(const tCGI *cgis, int num_handlers)
+{
+ LWIP_ASSERT("no cgis given", cgis != NULL);
+ LWIP_ASSERT("invalid number of handlers", num_handlers > 0);
+
+ httpd_cgis = cgis;
+ httpd_num_cgis = num_handlers;
+}
+#endif /* LWIP_HTTPD_CGI */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/http/httpd_structs.h b/src/apps/http/httpd_structs.h
new file mode 100644
index 00000000000..aa5bce2ffbb
--- /dev/null
+++ b/src/apps/http/httpd_structs.h
@@ -0,0 +1,123 @@
+#ifndef LWIP_HTTPD_STRUCTS_H
+#define LWIP_HTTPD_STRUCTS_H
+
+#include "lwip/apps/httpd.h"
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/** This struct is used for a list of HTTP header strings for various
+ * filename extensions. */
+typedef struct {
+ const char *extension;
+ const char *content_type;
+} tHTTPHeader;
+
+/** A list of strings used in HTTP headers (see RFC 1945 HTTP/1.0 and
+ * RFC 2616 HTTP/1.1 for header field definitions) */
+static const char *const g_psHTTPHeaderStrings[] = {
+ "HTTP/1.0 200 OK\r\n",
+ "HTTP/1.0 404 File not found\r\n",
+ "HTTP/1.0 400 Bad Request\r\n",
+ "HTTP/1.0 501 Not Implemented\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "HTTP/1.1 404 File not found\r\n",
+ "HTTP/1.1 400 Bad Request\r\n",
+ "HTTP/1.1 501 Not Implemented\r\n",
+ "Content-Length: ",
+ "Connection: Close\r\n",
+ "Connection: keep-alive\r\n",
+ "Connection: keep-alive\r\nContent-Length: ",
+ "Server: "HTTPD_SERVER_AGENT"\r\n",
+ "\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ , "Connection: keep-alive\r\nContent-Length: 77\r\n\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"
+#endif
+};
+
+/* Indexes into the g_psHTTPHeaderStrings array */
+#define HTTP_HDR_OK 0 /* 200 OK */
+#define HTTP_HDR_NOT_FOUND 1 /* 404 File not found */
+#define HTTP_HDR_BAD_REQUEST 2 /* 400 Bad request */
+#define HTTP_HDR_NOT_IMPL 3 /* 501 Not Implemented */
+#define HTTP_HDR_OK_11 4 /* 200 OK */
+#define HTTP_HDR_NOT_FOUND_11 5 /* 404 File not found */
+#define HTTP_HDR_BAD_REQUEST_11 6 /* 400 Bad request */
+#define HTTP_HDR_NOT_IMPL_11 7 /* 501 Not Implemented */
+#define HTTP_HDR_CONTENT_LENGTH 8 /* Content-Length: (HTTP 1.0)*/
+#define HTTP_HDR_CONN_CLOSE 9 /* Connection: Close (HTTP 1.1) */
+#define HTTP_HDR_CONN_KEEPALIVE 10 /* Connection: keep-alive (HTTP 1.1) */
+#define HTTP_HDR_KEEPALIVE_LEN 11 /* Connection: keep-alive + Content-Length: (HTTP 1.1)*/
+#define HTTP_HDR_SERVER 12 /* Server: HTTPD_SERVER_AGENT */
+#define DEFAULT_404_HTML 13 /* default 404 body */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+#define DEFAULT_404_HTML_PERSISTENT 14 /* default 404 body, but including Connection: keep-alive */
+#endif
+
+#define HTTP_CONTENT_TYPE(contenttype) "Content-Type: "contenttype"\r\n\r\n"
+#define HTTP_CONTENT_TYPE_ENCODING(contenttype, encoding) "Content-Type: "contenttype"\r\nContent-Encoding: "encoding"\r\n\r\n"
+
+#define HTTP_HDR_HTML HTTP_CONTENT_TYPE("text/html")
+#define HTTP_HDR_SSI HTTP_CONTENT_TYPE("text/html\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache")
+#define HTTP_HDR_GIF HTTP_CONTENT_TYPE("image/gif")
+#define HTTP_HDR_PNG HTTP_CONTENT_TYPE("image/png")
+#define HTTP_HDR_JPG HTTP_CONTENT_TYPE("image/jpeg")
+#define HTTP_HDR_BMP HTTP_CONTENT_TYPE("image/bmp")
+#define HTTP_HDR_ICO HTTP_CONTENT_TYPE("image/x-icon")
+#define HTTP_HDR_APP HTTP_CONTENT_TYPE("application/octet-stream")
+#define HTTP_HDR_JS HTTP_CONTENT_TYPE("application/javascript")
+#define HTTP_HDR_RA HTTP_CONTENT_TYPE("application/javascript")
+#define HTTP_HDR_CSS HTTP_CONTENT_TYPE("text/css")
+#define HTTP_HDR_SWF HTTP_CONTENT_TYPE("application/x-shockwave-flash")
+#define HTTP_HDR_XML HTTP_CONTENT_TYPE("text/xml")
+#define HTTP_HDR_PDF HTTP_CONTENT_TYPE("application/pdf")
+#define HTTP_HDR_JSON HTTP_CONTENT_TYPE("application/json")
+#define HTTP_HDR_CSV HTTP_CONTENT_TYPE("text/csv")
+#define HTTP_HDR_TSV HTTP_CONTENT_TYPE("text/tsv")
+#define HTTP_HDR_SVG HTTP_CONTENT_TYPE("image/svg+xml")
+#define HTTP_HDR_SVGZ HTTP_CONTENT_TYPE_ENCODING("image/svg+xml", "gzip")
+
+#define HTTP_HDR_DEFAULT_TYPE HTTP_CONTENT_TYPE("text/plain")
+
+/** A list of extension-to-HTTP header strings (see outdated RFC 1700 MEDIA TYPES
+ * and http://www.iana.org/assignments/media-types for registered content types
+ * and subtypes) */
+static const tHTTPHeader g_psHTTPHeaders[] = {
+ { "html", HTTP_HDR_HTML},
+ { "htm", HTTP_HDR_HTML},
+ { "shtml", HTTP_HDR_SSI},
+ { "shtm", HTTP_HDR_SSI},
+ { "ssi", HTTP_HDR_SSI},
+ { "gif", HTTP_HDR_GIF},
+ { "png", HTTP_HDR_PNG},
+ { "jpg", HTTP_HDR_JPG},
+ { "bmp", HTTP_HDR_BMP},
+ { "ico", HTTP_HDR_ICO},
+ { "class", HTTP_HDR_APP},
+ { "cls", HTTP_HDR_APP},
+ { "js", HTTP_HDR_JS},
+ { "ram", HTTP_HDR_RA},
+ { "css", HTTP_HDR_CSS},
+ { "swf", HTTP_HDR_SWF},
+ { "xml", HTTP_HDR_XML},
+ { "xsl", HTTP_HDR_XML},
+ { "pdf", HTTP_HDR_PDF},
+ { "json", HTTP_HDR_JSON}
+#ifdef HTTPD_ADDITIONAL_CONTENT_TYPES
+ /* If you need to add content types not listed here:
+ * #define HTTPD_ADDITIONAL_CONTENT_TYPES {"ct1", HTTP_CONTENT_TYPE("text/ct1")}, {"exe", HTTP_CONTENT_TYPE("application/exe")}
+ */
+ , HTTPD_ADDITIONAL_CONTENT_TYPES
+#endif
+};
+
+#define NUM_HTTP_HEADERS LWIP_ARRAYSIZE(g_psHTTPHeaders)
+
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI && LWIP_HTTPD_SSI_BY_FILE_EXTENSION
+static const char *const g_pcSSIExtensions[] = {
+ LWIP_HTTPD_SSI_EXTENSIONS
+};
+#define NUM_SHTML_EXTENSIONS LWIP_ARRAYSIZE(g_pcSSIExtensions)
+#endif /* LWIP_HTTPD_SSI && LWIP_HTTPD_SSI_BY_FILE_EXTENSION */
+
+#endif /* LWIP_HTTPD_STRUCTS_H */
diff --git a/src/apps/http/makefsdata/makefsdata b/src/apps/http/makefsdata/makefsdata
new file mode 100644
index 00000000000..667eb888513
--- /dev/null
+++ b/src/apps/http/makefsdata/makefsdata
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+open(OUTPUT, "> fsdata.c");
+
+chdir("fs");
+open(FILES, "find . -type f |");
+
+while($file = <FILES>) {
+
+ # Do not include files in CVS directories nor backup files.
+ if($file =~ /(CVS|~)/) {
+ next;
+ }
+
+ chop($file);
+
+ open(HEADER, "> /tmp/header") || die $!;
+ if($file =~ /404/) {
+ print(HEADER "HTTP/1.0 404 File not found\r\n");
+ } else {
+ print(HEADER "HTTP/1.0 200 OK\r\n");
+ }
+ print(HEADER "Server: lwIP/pre-0.6 (http://www.sics.se/~adam/lwip/)\r\n");
+ if($file =~ /\.html$/) {
+ print(HEADER "Content-type: text/html\r\n");
+ } elsif($file =~ /\.gif$/) {
+ print(HEADER "Content-type: image/gif\r\n");
+ } elsif($file =~ /\.png$/) {
+ print(HEADER "Content-type: image/png\r\n");
+ } elsif($file =~ /\.jpg$/) {
+ print(HEADER "Content-type: image/jpeg\r\n");
+ } elsif($file =~ /\.class$/) {
+ print(HEADER "Content-type: application/octet-stream\r\n");
+ } elsif($file =~ /\.ram$/) {
+ print(HEADER "Content-type: audio/x-pn-realaudio\r\n");
+ } else {
+ print(HEADER "Content-type: text/plain\r\n");
+ }
+ print(HEADER "\r\n");
+ close(HEADER);
+
+ unless($file =~ /\.plain$/ || $file =~ /cgi/) {
+ system("cat /tmp/header $file > /tmp/file");
+ } else {
+ system("cp $file /tmp/file");
+ }
+
+ open(FILE, "/tmp/file");
+ unlink("/tmp/file");
+ unlink("/tmp/header");
+
+ $file =~ s/\.//;
+ $fvar = $file;
+ $fvar =~ s-/-_-g;
+ $fvar =~ s-\.-_-g;
+ print(OUTPUT "static const unsigned char data".$fvar."[] = {\n");
+ print(OUTPUT "\t/* $file */\n\t");
+ for($j = 0; $j < length($file); $j++) {
+ printf(OUTPUT "%#02x, ", unpack("C", substr($file, $j, 1)));
+ }
+ printf(OUTPUT "0,\n");
+
+
+ $i = 0;
+ while(read(FILE, $data, 1)) {
+ if($i == 0) {
+ print(OUTPUT "\t");
+ }
+ printf(OUTPUT "%#02x, ", unpack("C", $data));
+ $i++;
+ if($i == 10) {
+ print(OUTPUT "\n");
+ $i = 0;
+ }
+ }
+ print(OUTPUT "};\n\n");
+ close(FILE);
+ push(@fvars, $fvar);
+ push(@files, $file);
+}
+
+for($i = 0; $i < @fvars; $i++) {
+ $file = $files[$i];
+ $fvar = $fvars[$i];
+
+ if($i == 0) {
+ $prevfile = "NULL";
+ } else {
+ $prevfile = "file" . $fvars[$i - 1];
+ }
+ print(OUTPUT "const struct fsdata_file file".$fvar."[] = {{$prevfile, data$fvar, ");
+ print(OUTPUT "data$fvar + ". (length($file) + 1) .", ");
+ print(OUTPUT "sizeof(data$fvar) - ". (length($file) + 1) .", FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT}};\n\n");
+}
+
+print(OUTPUT "#define FS_ROOT file$fvars[$i - 1]\n\n");
+print(OUTPUT "#define FS_NUMFILES $i\n");
diff --git a/src/apps/http/makefsdata/makefsdata.c b/src/apps/http/makefsdata/makefsdata.c
new file mode 100644
index 00000000000..240c72e4be8
--- /dev/null
+++ b/src/apps/http/makefsdata/makefsdata.c
@@ -0,0 +1,1307 @@
+/**
+ * makefsdata: Converts a directory structure for use with the lwIP httpd.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Jim Pettinato
+ * Simon Goldschmidt
+ *
+ * @todo:
+ * - take TCP_MSS, LWIP_TCP_TIMESTAMPS and
+ * PAYLOAD_ALIGN_TYPE/PAYLOAD_ALIGNMENT as arguments
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#include "tinydir.h"
+
+/** Makefsdata can generate *all* files deflate-compressed (where file size shrinks).
+ * Since nearly all browsers support this, this is a good way to reduce ROM size.
+ * To compress the files, "miniz.c" must be downloaded separately OR
+ * MAKEFS_SUPPORT_DEFLATE_ZLIB must be set and the zlib library and headers
+ * must be present on the system compiling this program.
+ */
+#ifndef MAKEFS_SUPPORT_DEFLATE
+#define MAKEFS_SUPPORT_DEFLATE 0
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+#define MAKEFS_SUPPORT_DEFLATE_ZLIB 0
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+#define COPY_BUFSIZE (1024*1024) /* 1 MByte */
+
+#if MAKEFS_SUPPORT_DEFLATE
+#if MAKEFS_SUPPORT_DEFLATE_ZLIB
+#include <zlib.h>
+#else
+#include "../miniz.c"
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint;
+
+#define my_max(a,b) (((a) > (b)) ? (a) : (b))
+#define my_min(a,b) (((a) < (b)) ? (a) : (b))
+
+/* COMP_OUT_BUF_SIZE is the size of the output buffer used during compression.
+ COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE */
+#define COMP_OUT_BUF_SIZE COPY_BUFSIZE
+
+/* OUT_BUF_SIZE is the size of the output buffer used during decompression.
+ OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses) */
+#define OUT_BUF_SIZE COPY_BUFSIZE
+static uint8 s_outbuf[OUT_BUF_SIZE];
+static uint8 s_checkbuf[OUT_BUF_SIZE];
+
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+/* tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k).
+ This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it. */
+tdefl_compressor g_deflator;
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+
+static int deflate_level; /* default compression level, can be changed via command line */
+#define USAGE_ARG_DEFLATE " [-defl<:compr_level>]"
+#else /* MAKEFS_SUPPORT_DEFLATE */
+#define USAGE_ARG_DEFLATE ""
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+#ifdef WIN32
+
+#define GETCWD(path, len) GetCurrentDirectoryA(len, path)
+#define GETCWD_SUCCEEDED(ret) (ret != 0)
+#define CHDIR(path) SetCurrentDirectoryA(path)
+#define CHDIR_SUCCEEDED(ret) (ret == TRUE)
+
+#elif __linux__
+
+#define GETCWD(path, len) getcwd(path, len)
+#define GETCWD_SUCCEEDED(ret) (ret != NULL)
+#define CHDIR(path) chdir(path)
+#define CHDIR_SUCCEEDED(ret) (ret == 0)
+
+#else
+
+#error makefsdata not supported on this platform
+
+#endif
+
+#define NEWLINE "\r\n"
+#define NEWLINE_LEN 2
+
+/* Define this here since we don't include any external C files and ports might override it */
+#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
+ x, __LINE__, __FILE__); fflush(NULL); abort();} while(0)
+
+/* define this to get the header variables we use to build HTTP headers */
+#define LWIP_HTTPD_DYNAMIC_HEADERS 1
+#define LWIP_HTTPD_SSI 1
+#include "lwip/init.h"
+#include "../httpd_structs.h"
+#include "lwip/apps/fs.h"
+
+#include "../core/inet_chksum.c"
+#include "../core/def.c"
+
+/** (Your server name here) */
+static const char *serverID = "Server: "HTTPD_SERVER_AGENT"\r\n";
+static char serverIDBuffer[1024];
+
+/* change this to suit your MEM_ALIGNMENT */
+#define PAYLOAD_ALIGNMENT 4
+/* set this to 0 to prevent aligning payload */
+#define ALIGN_PAYLOAD 1
+/* define this to a type that has the required alignment */
+#define PAYLOAD_ALIGN_TYPE "unsigned int"
+static int payload_alingment_dummy_counter = 0;
+
+#define HEX_BYTES_PER_LINE 16
+
+#define MAX_PATH_LEN 256
+
+struct file_entry {
+ struct file_entry *next;
+ const char *filename_c;
+};
+
+int process_sub(FILE *data_file, FILE *struct_file);
+int process_file(FILE *data_file, FILE *struct_file, const char *filename);
+int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len,
+ u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed);
+int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i);
+int s_put_ascii(char *buf, const char *ascii_string, int len, int *i);
+void concat_files(const char *file1, const char *file2, const char *targetfile);
+int check_path(char *path, size_t size);
+static int checkSsiByFilelist(const char* filename_listfile);
+static int ext_in_list(const char* filename, const char *ext_list);
+static int file_to_exclude(const char* filename);
+static int file_can_be_compressed(const char* filename);
+
+/* 5 bytes per char + 3 bytes per line */
+static char file_buffer_c[COPY_BUFSIZE * 5 + ((COPY_BUFSIZE / HEX_BYTES_PER_LINE) * 3)];
+
+static char curSubdir[MAX_PATH_LEN-3];
+static char lastFileVar[MAX_PATH_LEN];
+static char hdr_buf[4096];
+
+static unsigned char processSubs = 1;
+static unsigned char includeHttpHeader = 1;
+static unsigned char useHttp11 = 0;
+static unsigned char supportSsi = 1;
+static unsigned char precalcChksum = 0;
+static unsigned char includeLastModified = 0;
+#if MAKEFS_SUPPORT_DEFLATE
+static unsigned char deflateNonSsiFiles = 0;
+static size_t deflatedBytesReduced = 0;
+static size_t overallDataBytes = 0;
+#endif
+static const char *exclude_list = NULL;
+static const char *ncompress_list = NULL;
+
+static struct file_entry *first_file = NULL;
+static struct file_entry *last_file = NULL;
+
+static char *ssi_file_buffer;
+static char **ssi_file_lines;
+static size_t ssi_file_num_lines;
+
+static void print_usage(void)
+{
+ printf(" Usage: htmlgen [targetdir] [-s] [-e] [-11] [-nossi] [-ssi:<filename>] [-c] [-f:<filename>] [-m] [-svr:<name>] [-x:<ext_list>] [-xc:<ext_list>" USAGE_ARG_DEFLATE NEWLINE NEWLINE);
+ printf(" targetdir: relative or absolute path to files to convert" NEWLINE);
+ printf(" switch -s: toggle processing of subdirectories (default is on)" NEWLINE);
+ printf(" switch -e: exclude HTTP header from file (header is created at runtime, default is off)" NEWLINE);
+ printf(" switch -11: include HTTP 1.1 header (1.0 is default)" NEWLINE);
+ printf(" switch -nossi: no support for SSI (cannot calculate Content-Length for SSI)" NEWLINE);
+ printf(" switch -ssi: ssi filename (ssi support controlled by file list, not by extension)" NEWLINE);
+ printf(" switch -c: precalculate checksums for all pages (default is off)" NEWLINE);
+ printf(" switch -f: target filename (default is \"fsdata.c\")" NEWLINE);
+ printf(" switch -m: include \"Last-Modified\" header based on file time" NEWLINE);
+ printf(" switch -svr: server identifier sent in HTTP response header ('Server' field)" NEWLINE);
+ printf(" switch -x: comma separated list of extensions of files to exclude (e.g., -x:json,txt)" NEWLINE);
+ printf(" switch -xc: comma separated list of extensions of files to not compress (e.g., -xc:mp3,jpg)" NEWLINE);
+#if MAKEFS_SUPPORT_DEFLATE
+ printf(" switch -defl: deflate-compress all non-SSI files (with opt. compr.-level, default=10)" NEWLINE);
+ printf(" ATTENTION: browser has to support \"Content-Encoding: deflate\"!" NEWLINE);
+#endif
+ printf(" if targetdir not specified, htmlgen will attempt to" NEWLINE);
+ printf(" process files in subdirectory 'fs'" NEWLINE);
+}
+
+int main(int argc, char *argv[])
+{
+ char path[MAX_PATH_LEN];
+ char appPath[MAX_PATH_LEN];
+ FILE *data_file;
+ FILE *struct_file;
+ int filesProcessed;
+ int i;
+ char targetfile[MAX_PATH_LEN];
+ strcpy(targetfile, "fsdata.c");
+
+ memset(path, 0, sizeof(path));
+ memset(appPath, 0, sizeof(appPath));
+
+ printf(NEWLINE " makefsdata v" LWIP_VERSION_STRING " - HTML to C source converter" NEWLINE);
+ printf(" by Jim Pettinato - circa 2003 " NEWLINE);
+ printf(" extended by Simon Goldschmidt - 2009 " NEWLINE NEWLINE);
+
+ LWIP_ASSERT("sizeof(hdr_buf) must fit into an u16_t", sizeof(hdr_buf) <= 0xffff);
+
+ strcpy(path, "fs");
+ for (i = 1; i < argc; i++) {
+ if (argv[i] == NULL) {
+ continue;
+ }
+ if (argv[i][0] == '-') {
+ if (strstr(argv[i], "-svr:") == argv[i]) {
+ snprintf(serverIDBuffer, sizeof(serverIDBuffer), "Server: %s\r\n", &argv[i][5]);
+ serverID = serverIDBuffer;
+ printf("Using Server-ID: \"%s\"\n", serverID);
+ } else if (!strcmp(argv[i], "-s")) {
+ processSubs = 0;
+ } else if (!strcmp(argv[i], "-e")) {
+ includeHttpHeader = 0;
+ } else if (!strcmp(argv[i], "-11")) {
+ useHttp11 = 1;
+ } else if (!strcmp(argv[i], "-nossi")) {
+ supportSsi = 0;
+ } else if (strstr(argv[i], "-ssi:") == argv[i]) {
+ const char* ssi_list_filename = &argv[i][5];
+ if (checkSsiByFilelist(ssi_list_filename)) {
+ printf("Reading list of SSI files from \"%s\"\n", ssi_list_filename);
+ } else {
+ printf("Failed to load list of SSI files from \"%s\"\n", ssi_list_filename);
+ }
+ } else if (!strcmp(argv[i], "-c")) {
+ precalcChksum = 1;
+ } else if (strstr(argv[i], "-f:") == argv[i]) {
+ strncpy(targetfile, &argv[i][3], sizeof(targetfile) - 1);
+ targetfile[sizeof(targetfile) - 1] = 0;
+ printf("Writing to file \"%s\"\n", targetfile);
+ } else if (!strcmp(argv[i], "-m")) {
+ includeLastModified = 1;
+ } else if (strstr(argv[i], "-defl") == argv[i]) {
+#if MAKEFS_SUPPORT_DEFLATE
+ const char *colon = &argv[i][5];
+ if (*colon == ':') {
+ int defl_level = atoi(&colon[1]);
+ if ((colon[1] != 0) && (defl_level >= 0) && (defl_level <= 10)) {
+ deflate_level = defl_level;
+ } else {
+ printf("ERROR: deflate level must be [0..10]" NEWLINE);
+ exit(0);
+ }
+ } else {
+ /* default to highest compression */
+ deflate_level = 10;
+ }
+ deflateNonSsiFiles = 1;
+ printf("Deflating all non-SSI files with level %d (but only if size is reduced)" NEWLINE, deflate_level);
+#else
+ printf("WARNING: Deflate support is disabled\n");
+#endif
+ } else if (strstr(argv[i], "-x:") == argv[i]) {
+ exclude_list = &argv[i][3];
+ printf("Excluding files with extensions %s" NEWLINE, exclude_list);
+ } else if (strstr(argv[i], "-xc:") == argv[i]) {
+ ncompress_list = &argv[i][4];
+ printf("Skipping compression for files with extensions %s" NEWLINE, ncompress_list);
+ } else if ((strstr(argv[i], "-?")) || (strstr(argv[i], "-h"))) {
+ print_usage();
+ exit(0);
+ }
+ } else if ((argv[i][0] == '/') && (argv[i][1] == '?') && (argv[i][2] == 0)) {
+ print_usage();
+ exit(0);
+ } else {
+ strncpy(path, argv[i], sizeof(path) - 1);
+ path[sizeof(path) - 1] = 0;
+ }
+ }
+
+ if (!check_path(path, sizeof(path))) {
+ printf("Invalid path: \"%s\"." NEWLINE, path);
+ exit(-1);
+ }
+
+ if(!GETCWD_SUCCEEDED(GETCWD(appPath, MAX_PATH_LEN))) {
+ printf("Unable to get current dir." NEWLINE);
+ exit(-1);
+ }
+ /* if command line param or subdir named 'fs' not found spout usage verbiage */
+ if (!CHDIR_SUCCEEDED(CHDIR(path))) {
+ /* if no subdir named 'fs' (or the one which was given) exists, spout usage verbiage */
+ printf(" Failed to open directory \"%s\"." NEWLINE NEWLINE, path);
+ print_usage();
+ exit(-1);
+ }
+ if(!CHDIR_SUCCEEDED(CHDIR(appPath))) {
+ printf("Invalid path: \"%s\"." NEWLINE, appPath);
+ exit(-1);
+ }
+
+ printf("HTTP %sheader will %s statically included." NEWLINE,
+ (includeHttpHeader ? (useHttp11 ? "1.1 " : "1.0 ") : ""),
+ (includeHttpHeader ? "be" : "not be"));
+
+ curSubdir[0] = '\0'; /* start off in web page's root directory - relative paths */
+ printf(" Processing all files in directory %s", path);
+ if (processSubs) {
+ printf(" and subdirectories..." NEWLINE NEWLINE);
+ } else {
+ printf("..." NEWLINE NEWLINE);
+ }
+
+ data_file = fopen("fsdata.tmp", "wb");
+ if (data_file == NULL) {
+ printf("Failed to create file \"fsdata.tmp\"\n");
+ exit(-1);
+ }
+ struct_file = fopen("fshdr.tmp", "wb");
+ if (struct_file == NULL) {
+ printf("Failed to create file \"fshdr.tmp\"\n");
+ fclose(data_file);
+ exit(-1);
+ }
+
+ if(!CHDIR_SUCCEEDED(CHDIR(path))) {
+ printf("Invalid path: \"%s\"." NEWLINE, path);
+ exit(-1);
+ }
+
+ fprintf(data_file, "#include \"lwip/apps/fs.h\"" NEWLINE);
+ fprintf(data_file, "#include \"lwip/def.h\"" NEWLINE NEWLINE NEWLINE);
+
+ fprintf(data_file, "#define file_NULL (struct fsdata_file *) NULL" NEWLINE NEWLINE NEWLINE);
+ /* define FS_FILE_FLAGS_HEADER_INCLUDED to 1 if not defined (compatibility with older httpd/fs) */
+ fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_INCLUDED" NEWLINE "#define FS_FILE_FLAGS_HEADER_INCLUDED 1" NEWLINE "#endif" NEWLINE);
+ /* define FS_FILE_FLAGS_HEADER_PERSISTENT to 0 if not defined (compatibility with older httpd/fs: wasn't supported back then) */
+ fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT" NEWLINE "#define FS_FILE_FLAGS_HEADER_PERSISTENT 0" NEWLINE "#endif" NEWLINE);
+
+ /* define alignment defines */
+#if ALIGN_PAYLOAD
+ fprintf(data_file, "/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */" NEWLINE "#ifndef FSDATA_FILE_ALIGNMENT" NEWLINE "#define FSDATA_FILE_ALIGNMENT 0" NEWLINE "#endif" NEWLINE);
+#endif
+ fprintf(data_file, "#ifndef FSDATA_ALIGN_PRE" NEWLINE "#define FSDATA_ALIGN_PRE" NEWLINE "#endif" NEWLINE);
+ fprintf(data_file, "#ifndef FSDATA_ALIGN_POST" NEWLINE "#define FSDATA_ALIGN_POST" NEWLINE "#endif" NEWLINE);
+#if ALIGN_PAYLOAD
+ fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==2" NEWLINE "#include \"fsdata_alignment.h\"" NEWLINE "#endif" NEWLINE);
+#endif
+
+ sprintf(lastFileVar, "NULL");
+
+ filesProcessed = process_sub(data_file, struct_file);
+
+ /* data_file now contains all of the raw data.. now append linked list of
+ * file header structs to allow embedded app to search for a file name */
+ fprintf(data_file, NEWLINE NEWLINE);
+ fprintf(struct_file, "#define FS_ROOT file_%s" NEWLINE, lastFileVar);
+ fprintf(struct_file, "#define FS_NUMFILES %d" NEWLINE NEWLINE, filesProcessed);
+
+ fclose(data_file);
+ fclose(struct_file);
+
+ if(!CHDIR_SUCCEEDED(CHDIR(appPath))) {
+ printf("Invalid path: \"%s\"." NEWLINE, appPath);
+ exit(-1);
+ }
+
+ /* append struct_file to data_file */
+ printf(NEWLINE "Creating target file..." NEWLINE NEWLINE);
+ concat_files("fsdata.tmp", "fshdr.tmp", targetfile);
+
+ /* if succeeded, delete the temporary files */
+ if (remove("fsdata.tmp") != 0) {
+ printf("Warning: failed to delete fsdata.tmp\n");
+ }
+ if (remove("fshdr.tmp") != 0) {
+ printf("Warning: failed to delete fshdr.tmp\n");
+ }
+
+ printf(NEWLINE "Processed %d files - done." NEWLINE, filesProcessed);
+#if MAKEFS_SUPPORT_DEFLATE
+ if (deflateNonSsiFiles) {
+ printf("(Deflated total byte reduction: %d bytes -> %d bytes (%.02f%%)" NEWLINE,
+ (int)overallDataBytes, (int)deflatedBytesReduced, (float)((deflatedBytesReduced * 100.0) / overallDataBytes));
+ }
+#endif
+ printf(NEWLINE);
+
+ while (first_file != NULL) {
+ struct file_entry *fe = first_file;
+ first_file = fe->next;
+ free(fe);
+ }
+
+ if (ssi_file_buffer) {
+ free(ssi_file_buffer);
+ }
+ if (ssi_file_lines) {
+ free(ssi_file_lines);
+ }
+
+ return 0;
+}
+
+int check_path(char *path, size_t size)
+{
+ size_t slen;
+ if (path[0] == 0) {
+ /* empty */
+ return 0;
+ }
+ slen = strlen(path);
+ if (slen >= size) {
+ /* not NULL-terminated */
+ return 0;
+ }
+ while ((slen > 0) && ((path[slen] == '\\') || (path[slen] == '/'))) {
+ /* path should not end with trailing backslash */
+ path[slen] = 0;
+ slen--;
+ }
+ if (slen == 0) {
+ return 0;
+ }
+ return 1;
+}
+
+static void copy_file(const char *filename_in, FILE *fout)
+{
+ FILE *fin;
+ size_t len;
+ void *buf;
+ fin = fopen(filename_in, "rb");
+ if (fin == NULL) {
+ printf("Failed to open file \"%s\"\n", filename_in);
+ exit(-1);
+ }
+ buf = malloc(COPY_BUFSIZE);
+ while ((len = fread(buf, 1, COPY_BUFSIZE, fin)) > 0) {
+ fwrite(buf, 1, len, fout);
+ }
+ free(buf);
+ fclose(fin);
+}
+
+void concat_files(const char *file1, const char *file2, const char *targetfile)
+{
+ FILE *fout;
+ fout = fopen(targetfile, "wb");
+ if (fout == NULL) {
+ printf("Failed to open file \"%s\"\n", targetfile);
+ exit(-1);
+ }
+ copy_file(file1, fout);
+ copy_file(file2, fout);
+ fclose(fout);
+}
+
+int process_sub(FILE *data_file, FILE *struct_file)
+{
+ tinydir_dir dir;
+ int filesProcessed = 0;
+
+ if (processSubs) {
+ /* process subs recursively */
+ size_t sublen = strlen(curSubdir);
+ size_t freelen = sizeof(curSubdir) - sublen - 1;
+ int ret;
+ LWIP_ASSERT("sublen < sizeof(curSubdir)", sublen < sizeof(curSubdir));
+
+ ret = tinydir_open_sorted(&dir, TINYDIR_STRING("."));
+
+ if (ret == 0) {
+ unsigned int i;
+ for (i = 0; i < dir.n_files; i++) {
+ tinydir_file file;
+
+ ret = tinydir_readfile_n(&dir, &file, i);
+
+ if (ret == 0) {
+#if (defined _MSC_VER || defined __MINGW32__) && (defined _UNICODE)
+ size_t num_char_converted;
+ char currName[256];
+ wcstombs_s(&num_char_converted, currName, sizeof(currName), file.name, sizeof(currName));
+#else
+ const char *currName = file.name;
+#endif
+
+ if (currName[0] == '.') {
+ continue;
+ }
+ if (!file.is_dir) {
+ continue;
+ }
+ if (freelen > 0) {
+ if(!CHDIR_SUCCEEDED(CHDIR(currName))) {
+ printf("Invalid path: \"%s\"." NEWLINE, currName);
+ exit(-1);
+ }
+ strncat(curSubdir, "/", freelen);
+ strncat(curSubdir, currName, freelen - 1);
+ curSubdir[sizeof(curSubdir) - 1] = 0;
+ printf("processing subdirectory %s/..." NEWLINE, curSubdir);
+ filesProcessed += process_sub(data_file, struct_file);
+ if(!CHDIR_SUCCEEDED(CHDIR(".."))) {
+ printf("Unable to get back to parent dir of: \"%s\"." NEWLINE, currName);
+ exit(-1);
+ }
+ curSubdir[sublen] = 0;
+ } else {
+ printf("WARNING: cannot process sub due to path length restrictions: \"%s/%s\"\n", curSubdir, currName);
+ }
+ }
+ }
+ }
+
+ ret = tinydir_open_sorted(&dir, TINYDIR_STRING("."));
+ if (ret == 0) {
+ unsigned int i;
+ for (i = 0; i < dir.n_files; i++) {
+ tinydir_file file;
+
+ ret = tinydir_readfile_n(&dir, &file, i);
+
+ if (ret == 0) {
+ if (!file.is_dir) {
+#if (defined _MSC_VER || defined __MINGW32__) && (defined _UNICODE)
+ size_t num_char_converted;
+ char curName[256];
+ wcstombs_s(&num_char_converted, curName, sizeof(curName), file.name, sizeof(curName));
+#else
+ const char *curName = file.name;
+#endif
+
+ if (strcmp(curName, "fsdata.tmp") == 0) {
+ continue;
+ }
+ if (strcmp(curName, "fshdr.tmp") == 0) {
+ continue;
+ }
+ if (file_to_exclude(curName)) {
+ printf("skipping %s/%s by exclude list (-x option)..." NEWLINE, curSubdir, curName);
+ continue;
+ }
+
+ printf("processing %s/%s..." NEWLINE, curSubdir, curName);
+
+ if (process_file(data_file, struct_file, curName) < 0) {
+ printf(NEWLINE "Error... aborting" NEWLINE);
+ return -1;
+ }
+ filesProcessed++;
+ }
+ }
+ }
+ }
+ }
+
+ return filesProcessed;
+}
+
+static u8_t *get_file_data(const char *filename, int *file_size, int can_be_compressed, int *is_compressed)
+{
+ FILE *inFile;
+ size_t fsize = 0;
+ u8_t *buf;
+ size_t r;
+ int rs;
+ LWIP_UNUSED_ARG(r); /* for LWIP_NOASSERT */
+ inFile = fopen(filename, "rb");
+ if (inFile == NULL) {
+ printf("Failed to open file \"%s\"\n", filename);
+ exit(-1);
+ }
+ fseek(inFile, 0, SEEK_END);
+ rs = ftell(inFile);
+ if (rs < 0) {
+ printf("ftell failed with %d\n", errno);
+ exit(-1);
+ }
+ fsize = (size_t)rs;
+ fseek(inFile, 0, SEEK_SET);
+ buf = (u8_t *)malloc(fsize);
+ LWIP_ASSERT("buf != NULL", buf != NULL);
+ r = fread(buf, 1, fsize, inFile);
+ LWIP_ASSERT("r == fsize", r == fsize);
+ *file_size = fsize;
+ *is_compressed = 0;
+#if MAKEFS_SUPPORT_DEFLATE
+ overallDataBytes += fsize;
+ if (deflateNonSsiFiles) {
+ if (can_be_compressed) {
+ if (fsize < OUT_BUF_SIZE) {
+ u8_t *ret_buf;
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+ tdefl_status status;
+#else /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ int status;
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ size_t in_bytes = fsize;
+ size_t out_bytes = OUT_BUF_SIZE;
+ const void *next_in = buf;
+ void *next_out = s_outbuf;
+ memset(s_outbuf, 0, sizeof(s_outbuf));
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+ /* create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined). */
+ mz_uint comp_flags = s_tdefl_num_probes[MZ_MIN(10, deflate_level)] | ((deflate_level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
+ if (!deflate_level) {
+ comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
+ }
+ status = tdefl_init(&g_deflator, NULL, NULL, comp_flags);
+ if (status != TDEFL_STATUS_OKAY) {
+ printf("tdefl_init() failed!\n");
+ exit(-1);
+ }
+ status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, TDEFL_FINISH);
+ if (status != TDEFL_STATUS_DONE) {
+ printf("deflate failed: %d\n", status);
+ exit(-1);
+ }
+#else /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ status = compress2(next_out, &out_bytes, next_in, in_bytes, deflate_level);
+ if (status != Z_OK) {
+ printf("deflate failed: %d\n", status);
+ exit(-1);
+ }
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ LWIP_ASSERT("out_bytes <= COPY_BUFSIZE", out_bytes <= OUT_BUF_SIZE);
+ if (out_bytes < fsize) {
+ ret_buf = (u8_t *)malloc(out_bytes);
+ LWIP_ASSERT("ret_buf != NULL", ret_buf != NULL);
+ memcpy(ret_buf, s_outbuf, out_bytes);
+ {
+ /* sanity-check compression be inflating and comparing to the original */
+ size_t dec_in_bytes = out_bytes;
+ size_t dec_out_bytes = OUT_BUF_SIZE;
+ next_out = s_checkbuf;
+ memset(s_checkbuf, 0, sizeof(s_checkbuf));
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+ tinfl_status dec_status;
+ tinfl_decompressor inflator;
+
+ tinfl_init(&inflator);
+ dec_status = tinfl_decompress(&inflator, (const mz_uint8 *)ret_buf, &dec_in_bytes, s_checkbuf, (mz_uint8 *)next_out, &dec_out_bytes, 0);
+ LWIP_ASSERT("tinfl_decompress failed", dec_status == TINFL_STATUS_DONE);
+#else /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ int dec_status;
+ dec_status = uncompress2 (s_checkbuf, &dec_out_bytes, ret_buf, &dec_in_bytes);
+ LWIP_ASSERT("tinfl_decompress failed", dec_status == Z_OK);
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ LWIP_ASSERT("tinfl_decompress size mismatch", fsize == dec_out_bytes);
+ LWIP_ASSERT("decompressed memcmp failed", !memcmp(s_checkbuf, buf, fsize));
+ }
+ /* free original buffer, use compressed data + size */
+ free(buf);
+ buf = ret_buf;
+ *file_size = out_bytes;
+ printf(" - deflate: %d bytes -> %d bytes (%.02f%%)" NEWLINE, (int)fsize, (int)out_bytes, (float)((out_bytes * 100.0) / fsize));
+ deflatedBytesReduced += (size_t)(fsize - out_bytes);
+ *is_compressed = 1;
+ } else {
+ printf(" - uncompressed: (would be %d bytes larger using deflate)" NEWLINE, (int)(out_bytes - fsize));
+ }
+ } else {
+ printf(" - uncompressed: (file is larger than deflate buffer)" NEWLINE);
+ }
+ } else {
+ printf(" - cannot be compressed" NEWLINE);
+ }
+ }
+#else
+ LWIP_UNUSED_ARG(can_be_compressed);
+#endif
+ fclose(inFile);
+ return buf;
+}
+
+static void process_file_data(FILE *data_file, u8_t *file_data, size_t file_size)
+{
+ size_t written, i, src_off = 0;
+ size_t off = 0;
+ LWIP_UNUSED_ARG(written); /* for LWIP_NOASSERT */
+ for (i = 0; i < file_size; i++) {
+ LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - 5);
+ sprintf(&file_buffer_c[off], "0x%02x,", file_data[i]);
+ off += 5;
+ if ((++src_off % HEX_BYTES_PER_LINE) == 0) {
+ LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - NEWLINE_LEN);
+ memcpy(&file_buffer_c[off], NEWLINE, NEWLINE_LEN);
+ off += NEWLINE_LEN;
+ }
+ if (off + 20 >= sizeof(file_buffer_c)) {
+ written = fwrite(file_buffer_c, 1, off, data_file);
+ LWIP_ASSERT("written == off", written == off);
+ off = 0;
+ }
+ }
+ written = fwrite(file_buffer_c, 1, off, data_file);
+ LWIP_ASSERT("written == off", written == off);
+}
+
+static int write_checksums(FILE *struct_file, const char *varname,
+ u16_t hdr_len, u16_t hdr_chksum, const u8_t *file_data, size_t file_size)
+{
+ int chunk_size = TCP_MSS;
+ int offset, src_offset;
+ size_t len;
+ int i = 0;
+#if LWIP_TCP_TIMESTAMPS
+ /* when timestamps are used, usable space is 12 bytes less per segment */
+ chunk_size -= 12;
+#endif
+
+ fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE);
+ fprintf(struct_file, "const struct fsdata_chksum chksums_%s[] = {" NEWLINE, varname);
+
+ if (hdr_len > 0) {
+ /* add checksum for HTTP header */
+ fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, 0, hdr_chksum, hdr_len);
+ i++;
+ }
+ src_offset = 0;
+ for (offset = hdr_len; ; offset += len) {
+ unsigned short chksum;
+ const void *data = (const void *)&file_data[src_offset];
+ len = LWIP_MIN(chunk_size, (int)file_size - src_offset);
+ if (len == 0) {
+ break;
+ }
+ chksum = ~inet_chksum(data, (u16_t)len);
+ /* add checksum for data */
+ fprintf(struct_file, "{%d, 0x%04x, %"SZT_F"}," NEWLINE, offset, chksum, len);
+ i++;
+ }
+ fprintf(struct_file, "};" NEWLINE);
+ fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE);
+ return i;
+}
+
+static int is_valid_char_for_c_var(char x)
+{
+ if (((x >= 'A') && (x <= 'Z')) ||
+ ((x >= 'a') && (x <= 'z')) ||
+ ((x >= '0') && (x <= '9')) ||
+ (x == '_')) {
+ return 1;
+ }
+ return 0;
+}
+
+static void fix_filename_for_c(char *qualifiedName, size_t max_len)
+{
+ struct file_entry *f;
+ size_t len = strlen(qualifiedName);
+ char *new_name = (char *)malloc(len + 2);
+ int filename_ok;
+ int cnt = 0;
+ size_t i;
+ if (len + 3 == max_len) {
+ printf("File name too long: \"%s\"\n", qualifiedName);
+ exit(-1);
+ }
+ strcpy(new_name, qualifiedName);
+ for (i = 0; i < len; i++) {
+ if (!is_valid_char_for_c_var(new_name[i])) {
+ new_name[i] = '_';
+ }
+ }
+ do {
+ filename_ok = 1;
+ for (f = first_file; f != NULL; f = f->next) {
+ if (!strcmp(f->filename_c, new_name)) {
+ filename_ok = 0;
+ cnt++;
+ /* try next unique file name */
+ sprintf(&new_name[len], "%d", cnt);
+ break;
+ }
+ }
+ } while (!filename_ok && (cnt < 999));
+ if (!filename_ok) {
+ printf("Failed to get unique file name: \"%s\"\n", qualifiedName);
+ exit(-1);
+ }
+ strcpy(qualifiedName, new_name);
+ free(new_name);
+}
+
+static void register_filename(const char *qualifiedName)
+{
+ struct file_entry *fe = (struct file_entry *)malloc(sizeof(struct file_entry));
+ fe->filename_c = strdup(qualifiedName);
+ fe->next = NULL;
+ if (first_file == NULL) {
+ first_file = last_file = fe;
+ } else {
+ last_file->next = fe;
+ last_file = fe;
+ }
+}
+
+static int checkSsiByFilelist(const char* filename_listfile)
+{
+ FILE *f = fopen(filename_listfile, "r");
+ if (f != NULL) {
+ char *buf;
+ long rs;
+ size_t fsize, readcount;
+ size_t i, l, num_lines;
+ char **lines;
+ int state;
+
+ fseek(f, 0, SEEK_END);
+ rs = ftell(f);
+ if (rs < 0) {
+ printf("ftell failed with %d\n", errno);
+ fclose(f);
+ return 0;
+ }
+ fsize = (size_t)rs;
+ fseek(f, 0, SEEK_SET);
+ buf = (char*)malloc(fsize);
+ if (!buf) {
+ printf("failed to allocate ssi file buffer\n");
+ fclose(f);
+ return 0;
+ }
+ memset(buf, 0, fsize);
+ readcount = fread(buf, 1, fsize, f);
+ fclose(f);
+ if ((readcount > fsize) || !readcount) {
+ printf("failed to read data from ssi file\n");
+ free(buf);
+ return 0;
+ }
+
+ /* first pass: get the number of lines (and convert newlines to '0') */
+ num_lines = 1;
+ for (i = 0; i < readcount; i++) {
+ if (buf[i] == '\n') {
+ num_lines++;
+ buf[i] = 0;
+ } else if (buf[i] == '\r') {
+ buf[i] = 0;
+ }
+ }
+ /* allocate the line pointer array */
+ lines = (char**)malloc(sizeof(char*) * num_lines);
+ if (!lines) {
+ printf("failed to allocate ssi line buffer\n");
+ free(buf);
+ return 0;
+ }
+ memset(lines, 0, sizeof(char*) * num_lines);
+ l = 0;
+ state = 0;
+ for (i = 0; i < readcount; i++) {
+ if (state) {
+ /* waiting for null */
+ if (buf[i] == 0) {
+ state = 0;
+ }
+ } else {
+ /* waiting for beginning of new string */
+ if (buf[i] != 0) {
+ LWIP_ASSERT("lines array overflow", l < num_lines);
+ lines[l] = &buf[i];
+ state = 1;
+ l++;
+ }
+ }
+ }
+ LWIP_ASSERT("lines array overflow", l < num_lines);
+
+ ssi_file_buffer = buf;
+ ssi_file_lines = lines;
+ ssi_file_num_lines = l;
+ }
+ return 0;
+}
+
+static int is_ssi_file(const char *filename)
+{
+ if (supportSsi) {
+ if (ssi_file_buffer) {
+ /* compare by list */
+ size_t i;
+ int ret = 0;
+ /* build up the relative path to this file */
+ size_t sublen = strlen(curSubdir);
+ size_t freelen = sizeof(curSubdir) - sublen - 1;
+ strncat(curSubdir, "/", freelen);
+ strncat(curSubdir, filename, freelen - 1);
+ curSubdir[sizeof(curSubdir) - 1] = 0;
+ for (i = 0; i < ssi_file_num_lines; i++) {
+ const char *listed_file = ssi_file_lines[i];
+ /* compare without the leading '/' */
+ if (!strcmp(&curSubdir[1], listed_file)) {
+ ret = 1;
+ }
+ }
+ curSubdir[sublen] = 0;
+ return ret;
+#if LWIP_HTTPD_SSI_BY_FILE_EXTENSION
+ } else {
+ /* check file extension */
+ size_t loop;
+ for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) {
+ if (strstr(filename, g_pcSSIExtensions[loop])) {
+ return 1;
+ }
+ }
+#endif /* LWIP_HTTPD_SSI_BY_FILE_EXTENSION */
+ }
+ }
+ return 0;
+}
+
+static int ext_in_list(const char* filename, const char *ext_list)
+{
+ int found = 0;
+ const char *ext = ext_list;
+ if (ext_list == NULL) {
+ return 0;
+ }
+ while(*ext != '\0') {
+ const char *comma = strchr(ext, ',');
+ size_t ext_size;
+ size_t filename_size = strlen(filename);
+ if (comma == NULL) {
+ comma = strchr(ext, '\0');
+ }
+ ext_size = comma - ext;
+ if ((filename[filename_size - ext_size - 1] == '.') &&
+ !strncmp(&filename[filename_size - ext_size], ext, ext_size)) {
+ found = 1;
+ break;
+ }
+ ext = comma + 1;
+ }
+
+ return found;
+}
+
+static int file_to_exclude(const char *filename)
+{
+ return (exclude_list != NULL) && ext_in_list(filename, exclude_list);
+}
+
+static int file_can_be_compressed(const char *filename)
+{
+ return (ncompress_list == NULL) || !ext_in_list(filename, ncompress_list);
+}
+
+int process_file(FILE *data_file, FILE *struct_file, const char *filename)
+{
+ char varname[MAX_PATH_LEN];
+ int i = 0;
+ char qualifiedName[MAX_PATH_LEN];
+ int file_size;
+ u16_t http_hdr_chksum = 0;
+ u16_t http_hdr_len = 0;
+ int chksum_count = 0;
+ u8_t flags = 0;
+ u8_t has_content_len;
+ u8_t *file_data;
+ int is_ssi;
+ int can_be_compressed;
+ int is_compressed = 0;
+ int flags_printed;
+
+ /* create qualified name (@todo: prepend slash or not?) */
+ snprintf(qualifiedName, sizeof(qualifiedName), "%s/%s", curSubdir, filename);
+ /* create C variable name */
+ strncpy(varname, qualifiedName, sizeof(varname));
+ /* convert slashes & dots to underscores */
+ fix_filename_for_c(varname, MAX_PATH_LEN);
+ register_filename(varname);
+#if ALIGN_PAYLOAD
+ /* to force even alignment of array, type 1 */
+ fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==1" NEWLINE);
+ fprintf(data_file, "static const " PAYLOAD_ALIGN_TYPE " dummy_align_%s = %d;" NEWLINE, varname, payload_alingment_dummy_counter++);
+ fprintf(data_file, "#endif" NEWLINE);
+#endif /* ALIGN_PAYLOAD */
+ fprintf(data_file, "static const unsigned char FSDATA_ALIGN_PRE data_%s[] FSDATA_ALIGN_POST = {" NEWLINE, varname);
+ /* encode source file name (used by file system, not returned to browser) */
+ fprintf(data_file, "/* %s (%"SZT_F" chars) */" NEWLINE, qualifiedName, strlen(qualifiedName) + 1);
+ file_put_ascii(data_file, qualifiedName, strlen(qualifiedName) + 1, &i);
+#if ALIGN_PAYLOAD
+ /* pad to even number of bytes to assure payload is on aligned boundary */
+ while (i % PAYLOAD_ALIGNMENT != 0) {
+ fprintf(data_file, "0x%02x,", 0);
+ i++;
+ }
+#endif /* ALIGN_PAYLOAD */
+ fprintf(data_file, NEWLINE);
+
+ is_ssi = is_ssi_file(filename);
+ if (is_ssi) {
+ flags |= FS_FILE_FLAGS_SSI;
+ }
+ has_content_len = !is_ssi;
+ can_be_compressed = includeHttpHeader && !is_ssi && file_can_be_compressed(filename);
+ file_data = get_file_data(filename, &file_size, can_be_compressed, &is_compressed);
+ if (includeHttpHeader) {
+ file_write_http_header(data_file, filename, file_size, &http_hdr_len, &http_hdr_chksum, has_content_len, is_compressed);
+ flags |= FS_FILE_FLAGS_HEADER_INCLUDED;
+ if (has_content_len) {
+ flags |= FS_FILE_FLAGS_HEADER_PERSISTENT;
+ if (useHttp11) {
+ flags |= FS_FILE_FLAGS_HEADER_HTTPVER_1_1;
+ }
+ }
+ }
+ if (precalcChksum) {
+ chksum_count = write_checksums(struct_file, varname, http_hdr_len, http_hdr_chksum, file_data, file_size);
+ }
+
+ /* build declaration of struct fsdata_file in temp file */
+ fprintf(struct_file, "const struct fsdata_file file_%s[] = { {" NEWLINE, varname);
+ fprintf(struct_file, "file_%s," NEWLINE, lastFileVar);
+ fprintf(struct_file, "data_%s," NEWLINE, varname);
+ fprintf(struct_file, "data_%s + %d," NEWLINE, varname, i);
+ fprintf(struct_file, "sizeof(data_%s) - %d," NEWLINE, varname, i);
+
+ flags_printed = 0;
+ if (flags & FS_FILE_FLAGS_HEADER_INCLUDED) {
+ fputs("FS_FILE_FLAGS_HEADER_INCLUDED", struct_file);
+ flags_printed = 1;
+ }
+ if (flags & FS_FILE_FLAGS_HEADER_PERSISTENT) {
+ if (flags_printed) {
+ fputs(" | ", struct_file);
+ }
+ fputs("FS_FILE_FLAGS_HEADER_PERSISTENT", struct_file);
+ flags_printed = 1;
+ }
+ if (flags & FS_FILE_FLAGS_HEADER_HTTPVER_1_1) {
+ if (flags_printed) {
+ fputs(" | ", struct_file);
+ }
+ fputs("FS_FILE_FLAGS_HEADER_HTTPVER_1_1", struct_file);
+ flags_printed = 1;
+ }
+ if (flags & FS_FILE_FLAGS_SSI) {
+ if (flags_printed) {
+ fputs(" | ", struct_file);
+ }
+ fputs("FS_FILE_FLAGS_SSI", struct_file);
+ flags_printed = 1;
+ }
+ if (!flags_printed) {
+ fputs("0", struct_file);
+ }
+ fputs("," NEWLINE, struct_file);
+ if (precalcChksum) {
+ fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE);
+ fprintf(struct_file, "%d, chksums_%s," NEWLINE, chksum_count, varname);
+ fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE);
+ }
+ fprintf(struct_file, "}};" NEWLINE NEWLINE);
+ strcpy(lastFileVar, varname);
+
+ /* write actual file contents */
+ i = 0;
+ fprintf(data_file, NEWLINE "/* raw file data (%d bytes) */" NEWLINE, file_size);
+ process_file_data(data_file, file_data, file_size);
+ fprintf(data_file, "};" NEWLINE NEWLINE);
+ free(file_data);
+ return 0;
+}
+
+int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len,
+ u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed)
+{
+ int i = 0;
+ int response_type = HTTP_HDR_OK;
+ const char *file_type;
+ const char *cur_string;
+ size_t cur_len;
+ int written = 0;
+ size_t hdr_len = 0;
+ u16_t acc;
+ const char *file_ext;
+ size_t j;
+ u8_t provide_last_modified = includeLastModified;
+
+ memset(hdr_buf, 0, sizeof(hdr_buf));
+
+ if (useHttp11) {
+ response_type = HTTP_HDR_OK_11;
+ }
+
+ fprintf(data_file, NEWLINE "/* HTTP header */");
+ if (strstr(filename, "404.") == filename) {
+ response_type = HTTP_HDR_NOT_FOUND;
+ if (useHttp11) {
+ response_type = HTTP_HDR_NOT_FOUND_11;
+ }
+ } else if (strstr(filename, "400.") == filename) {
+ response_type = HTTP_HDR_BAD_REQUEST;
+ if (useHttp11) {
+ response_type = HTTP_HDR_BAD_REQUEST_11;
+ }
+ } else if (strstr(filename, "501.") == filename) {
+ response_type = HTTP_HDR_NOT_IMPL;
+ if (useHttp11) {
+ response_type = HTTP_HDR_NOT_IMPL_11;
+ }
+ }
+ cur_string = g_psHTTPHeaderStrings[response_type];
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ cur_string = serverID;
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ file_ext = filename;
+ if (file_ext != NULL) {
+ while (strstr(file_ext, ".") != NULL) {
+ file_ext = strstr(file_ext, ".");
+ file_ext++;
+ }
+ }
+ if ((file_ext == NULL) || (*file_ext == 0)) {
+ printf("failed to get extension for file \"%s\", using default.\n", filename);
+ file_type = HTTP_HDR_DEFAULT_TYPE;
+ } else {
+ file_type = NULL;
+ for (j = 0; j < NUM_HTTP_HEADERS; j++) {
+ if (!strcmp(file_ext, g_psHTTPHeaders[j].extension)) {
+ file_type = g_psHTTPHeaders[j].content_type;
+ break;
+ }
+ }
+ if (file_type == NULL) {
+ printf("failed to get file type for extension \"%s\", using default.\n", file_ext);
+ file_type = HTTP_HDR_DEFAULT_TYPE;
+ }
+ }
+
+ /* Content-Length is used for persistent connections in HTTP/1.1 but also for
+ download progress in older versions
+ @todo: just use a big-enough buffer and let the HTTPD send spaces? */
+ if (provide_content_len) {
+ char intbuf[MAX_PATH_LEN];
+ int content_len = file_size;
+ memset(intbuf, 0, sizeof(intbuf));
+ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH];
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s%d\r\n\" (%"SZT_F"+ bytes) */" NEWLINE, cur_string, content_len, cur_len + 2);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ lwip_itoa(intbuf, sizeof(intbuf), content_len);
+ strcat(intbuf, "\r\n");
+ cur_len = strlen(intbuf);
+ written += file_put_ascii(data_file, intbuf, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], intbuf, cur_len);
+ hdr_len += cur_len;
+ }
+ }
+ if (provide_last_modified) {
+ char modbuf[256];
+ struct stat stat_data;
+ struct tm *t;
+ memset(modbuf, 0, sizeof(modbuf));
+ memset(&stat_data, 0, sizeof(stat_data));
+ cur_string = modbuf;
+ strcpy(modbuf, "Last-Modified: ");
+ if (stat(filename, &stat_data) != 0) {
+ printf("stat(%s) failed with error %d\n", filename, errno);
+ exit(-1);
+ }
+ t = gmtime(&stat_data.st_mtime);
+ if (t == NULL) {
+ printf("gmtime() failed with error %d\n", errno);
+ exit(-1);
+ }
+ strftime(&modbuf[15], sizeof(modbuf) - 15, "%a, %d %b %Y %H:%M:%S GMT", t);
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\"\r\n\" (%"SZT_F"+ bytes) */" NEWLINE, cur_string, cur_len + 2);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ modbuf[0] = 0;
+ strcat(modbuf, "\r\n");
+ cur_len = strlen(modbuf);
+ written += file_put_ascii(data_file, modbuf, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], modbuf, cur_len);
+ hdr_len += cur_len;
+ }
+ }
+
+ /* HTTP/1.1 implements persistent connections */
+ if (useHttp11) {
+ if (provide_content_len) {
+ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_KEEPALIVE];
+ } else {
+ /* no Content-Length available, so a persistent connection is no possible
+ because the client does not know the data length */
+ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE];
+ }
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+ }
+
+#if MAKEFS_SUPPORT_DEFLATE
+ if (is_compressed) {
+ /* tell the client about the deflate encoding */
+ LWIP_ASSERT("error", deflateNonSsiFiles);
+ cur_string = "Content-Encoding: deflate\r\n";
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ }
+#else
+ LWIP_UNUSED_ARG(is_compressed);
+#endif
+
+ /* write content-type, ATTENTION: this includes the double-CRLF! */
+ cur_string = file_type;
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+
+ /* ATTENTION: headers are done now (double-CRLF has been written!) */
+
+ if (precalcChksum) {
+ LWIP_ASSERT("hdr_len + cur_len <= sizeof(hdr_buf)", hdr_len + cur_len <= sizeof(hdr_buf));
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+
+ LWIP_ASSERT("strlen(hdr_buf) == hdr_len", strlen(hdr_buf) == hdr_len);
+ acc = ~inet_chksum(hdr_buf, (u16_t)hdr_len);
+ *http_hdr_len = (u16_t)hdr_len;
+ *http_hdr_chksum = acc;
+ }
+
+ return written;
+}
+
+int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i)
+{
+ int x;
+ for (x = 0; x < len; x++) {
+ unsigned char cur = ascii_string[x];
+ fprintf(file, "0x%02x,", cur);
+ if ((++(*i) % HEX_BYTES_PER_LINE) == 0) {
+ fprintf(file, NEWLINE);
+ }
+ }
+ return len;
+}
+
+int s_put_ascii(char *buf, const char *ascii_string, int len, int *i)
+{
+ int x;
+ int idx = 0;
+ for (x = 0; x < len; x++) {
+ unsigned char cur = ascii_string[x];
+ sprintf(&buf[idx], "0x%02x,", cur);
+ idx += 5;
+ if ((++(*i) % HEX_BYTES_PER_LINE) == 0) {
+ sprintf(&buf[idx], NEWLINE);
+ idx += NEWLINE_LEN;
+ }
+ }
+ return len;
+}
diff --git a/src/apps/http/makefsdata/readme.txt b/src/apps/http/makefsdata/readme.txt
new file mode 100644
index 00000000000..929179a1110
--- /dev/null
+++ b/src/apps/http/makefsdata/readme.txt
@@ -0,0 +1,23 @@
+This directory contains a script ('makefsdata') to create C code suitable for
+httpd for given html pages (or other files) in a directory.
+
+There is also a plain C console application doing the same and extended a bit.
+
+Usage: htmlgen [targetdir] [-s] [-i]s
+ targetdir: relative or absolute path to files to convert
+ switch -s: toggle processing of subdirectories (default is on)
+ switch -e: exclude HTTP header from file (header is created at runtime, default is on)
+ switch -11: include HTTP 1.1 header (1.0 is default)
+
+ if targetdir not specified, makefsdata will attempt to
+ process files in subdirectory 'fs'.
+
+The C version of this program can optionally store the none-SSI files in
+a compressed form in which they are also sent to the web client (which
+must support the Deflate content encoding). Files that grow during compression
+(due to being not compressible well), will stored umcompressed automatically.
+In order to do so, compile the program with MAKEFS_SUPPORT_DEFLATE set to 1. You must
+manually download minizip.c for this to work. As an alternative, you can additionally
+define MAKEFS_SUPPORT_DEFLATE_ZLIB to use your system's zlib instead.
+Compression of .html, .js, .css and .svg files usually yields very good compression
+rates and is a great way of reducing your program's size.
diff --git a/src/apps/http/makefsdata/tinydir.h b/src/apps/http/makefsdata/tinydir.h
new file mode 100644
index 00000000000..e08eb84ec97
--- /dev/null
+++ b/src/apps/http/makefsdata/tinydir.h
@@ -0,0 +1,831 @@
+/*
+Copyright (c) 2013-2019, tinydir authors:
+- Cong Xu
+- Lautis Sun
+- Baudouin Feildel
+- Andargor <[email protected]>
+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.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
+*/
+#ifndef TINYDIR_H
+#define TINYDIR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if ((defined _UNICODE) && !(defined UNICODE))
+#define UNICODE
+#endif
+
+#if ((defined UNICODE) && !(defined _UNICODE))
+#define _UNICODE
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include <windows.h>
+# include <tchar.h>
+# pragma warning(push)
+# pragma warning (disable : 4996)
+#else
+# include <dirent.h>
+# include <libgen.h>
+# include <sys/stat.h>
+# include <stddef.h>
+#endif
+#ifdef __MINGW32__
+# include <tchar.h>
+#endif
+
+
+/* types */
+
+/* Windows UNICODE wide character support */
+#if defined _MSC_VER || defined __MINGW32__
+# define _tinydir_char_t TCHAR
+# define TINYDIR_STRING(s) _TEXT(s)
+# define _tinydir_strlen _tcslen
+# define _tinydir_strcpy _tcscpy
+# define _tinydir_strcat _tcscat
+# define _tinydir_strcmp _tcscmp
+# define _tinydir_strrchr _tcsrchr
+# define _tinydir_strncmp _tcsncmp
+#else
+# define _tinydir_char_t char
+# define TINYDIR_STRING(s) s
+# define _tinydir_strlen strlen
+# define _tinydir_strcpy strcpy
+# define _tinydir_strcat strcat
+# define _tinydir_strcmp strcmp
+# define _tinydir_strrchr strrchr
+# define _tinydir_strncmp strncmp
+#endif
+
+#if (defined _MSC_VER || defined __MINGW32__)
+# include <windows.h>
+# define _TINYDIR_PATH_MAX MAX_PATH
+#elif defined __linux__
+# include <limits.h>
+# ifdef PATH_MAX
+# define _TINYDIR_PATH_MAX PATH_MAX
+# endif
+#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+# include <sys/param.h>
+# if defined(BSD)
+# include <limits.h>
+# ifdef PATH_MAX
+# define _TINYDIR_PATH_MAX PATH_MAX
+# endif
+# endif
+#endif
+
+#ifndef _TINYDIR_PATH_MAX
+#define _TINYDIR_PATH_MAX 4096
+#endif
+
+#ifdef _MSC_VER
+/* extra chars for the "\\*" mask */
+# define _TINYDIR_PATH_EXTRA 2
+#else
+# define _TINYDIR_PATH_EXTRA 0
+#endif
+
+#define _TINYDIR_FILENAME_MAX 256
+
+#if (defined _MSC_VER || defined __MINGW32__)
+#define _TINYDIR_DRIVE_MAX 3
+#endif
+
+#ifdef _MSC_VER
+# define _TINYDIR_FUNC static __inline
+#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# define _TINYDIR_FUNC static __inline__
+#else
+# define _TINYDIR_FUNC static inline
+#endif
+
+/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
+#ifdef TINYDIR_USE_READDIR_R
+
+/* readdir_r is a POSIX-only function, and may not be available under various
+ * environments/settings, e.g. MinGW. Use readdir fallback */
+#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
+ _POSIX_SOURCE
+# define _TINYDIR_HAS_READDIR_R
+#endif
+#if _POSIX_C_SOURCE >= 200112L
+# define _TINYDIR_HAS_FPATHCONF
+# include <unistd.h>
+#endif
+#if _BSD_SOURCE || _SVID_SOURCE || \
+ (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
+# define _TINYDIR_HAS_DIRFD
+# include <sys/types.h>
+#endif
+#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
+ defined _PC_NAME_MAX
+# define _TINYDIR_USE_FPATHCONF
+#endif
+#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
+ !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* Use readdir by default */
+#else
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* MINGW32 has two versions of dirent, ASCII and UNICODE*/
+#ifndef _MSC_VER
+#if (defined __MINGW32__) && (defined _UNICODE)
+#define _TINYDIR_DIR _WDIR
+#define _tinydir_dirent _wdirent
+#define _tinydir_opendir _wopendir
+#define _tinydir_readdir _wreaddir
+#define _tinydir_closedir _wclosedir
+#else
+#define _TINYDIR_DIR DIR
+#define _tinydir_dirent dirent
+#define _tinydir_opendir opendir
+#define _tinydir_readdir readdir
+#define _tinydir_closedir closedir
+#endif
+#endif
+
+/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
+#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE)
+#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
+#else
+#error "Either define both alloc and free or none of them!"
+#endif
+
+#if !defined(_TINYDIR_MALLOC)
+ #define _TINYDIR_MALLOC(_size) malloc(_size)
+ #define _TINYDIR_FREE(_ptr) free(_ptr)
+#endif /* !defined(_TINYDIR_MALLOC) */
+
+typedef struct tinydir_file
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ _tinydir_char_t name[_TINYDIR_FILENAME_MAX];
+ _tinydir_char_t *extension;
+ int is_dir;
+ int is_reg;
+
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+ struct _stat _s;
+#else
+ struct stat _s;
+#endif
+#endif
+} tinydir_file;
+
+typedef struct tinydir_dir
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ int has_next;
+ size_t n_files;
+
+ tinydir_file *_files;
+#ifdef _MSC_VER
+ HANDLE _h;
+ WIN32_FIND_DATA _f;
+#else
+ _TINYDIR_DIR *_d;
+ struct _tinydir_dirent *_e;
+#ifndef _TINYDIR_USE_READDIR
+ struct _tinydir_dirent *_ep;
+#endif
+#endif
+} tinydir_dir;
+
+
+/* declarations */
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir);
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir);
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
+
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file);
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b);
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
+#endif
+#endif
+
+
+/* definitions*/
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+ int error;
+ int size; /* using int size */
+#endif
+#else
+ _tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
+#endif
+ _tinydir_char_t *pathp;
+
+ if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* initialise dir */
+ dir->_files = NULL;
+#ifdef _MSC_VER
+ dir->_h = INVALID_HANDLE_VALUE;
+#else
+ dir->_d = NULL;
+#ifndef _TINYDIR_USE_READDIR
+ dir->_ep = NULL;
+#endif
+#endif
+ tinydir_close(dir);
+
+ _tinydir_strcpy(dir->path, path);
+ /* Remove trailing slashes */
+ pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
+ while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
+ {
+ *pathp = TINYDIR_STRING('\0');
+ pathp++;
+ }
+#ifdef _MSC_VER
+ _tinydir_strcpy(path_buf, dir->path);
+ _tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
+#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
+ dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0);
+#else
+ dir->_h = FindFirstFile(path_buf, &dir->_f);
+#endif
+ if (dir->_h == INVALID_HANDLE_VALUE)
+ {
+ errno = ENOENT;
+#else
+ dir->_d = _tinydir_opendir(path);
+ if (dir->_d == NULL)
+ {
+#endif
+ goto bail;
+ }
+
+ /* read first file */
+ dir->has_next = 1;
+#ifndef _MSC_VER
+#ifdef _TINYDIR_USE_READDIR
+ dir->_e = _tinydir_readdir(dir->_d);
+#else
+ /* allocate dirent buffer for readdir_r */
+ size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
+ if (size == -1) return -1;
+ dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
+ if (dir->_ep == NULL) return -1;
+
+ error = readdir_r(dir->_d, dir->_ep, &dir->_e);
+ if (error != 0) return -1;
+#endif
+ if (dir->_e == NULL)
+ {
+ dir->has_next = 0;
+ }
+#endif
+
+ return 0;
+
+bail:
+ tinydir_close(dir);
+ return -1;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+ /* Count the number of files first, to pre-allocate the files array */
+ size_t n_files = 0;
+ if (tinydir_open(dir, path) == -1)
+ {
+ return -1;
+ }
+ while (dir->has_next)
+ {
+ n_files++;
+ if (tinydir_next(dir) == -1)
+ {
+ goto bail;
+ }
+ }
+ tinydir_close(dir);
+
+ if (n_files == 0 || tinydir_open(dir, path) == -1)
+ {
+ return -1;
+ }
+
+ dir->n_files = 0;
+ dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
+ if (dir->_files == NULL)
+ {
+ goto bail;
+ }
+ while (dir->has_next)
+ {
+ tinydir_file *p_file;
+ dir->n_files++;
+
+ p_file = &dir->_files[dir->n_files - 1];
+ if (tinydir_readfile(dir, p_file) == -1)
+ {
+ goto bail;
+ }
+
+ if (tinydir_next(dir) == -1)
+ {
+ goto bail;
+ }
+
+ /* Just in case the number of files has changed between the first and
+ second reads, terminate without writing into unallocated memory */
+ if (dir->n_files == n_files)
+ {
+ break;
+ }
+ }
+
+ qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
+
+ return 0;
+
+bail:
+ tinydir_close(dir);
+ return -1;
+}
+
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir)
+{
+ if (dir == NULL)
+ {
+ return;
+ }
+
+ memset(dir->path, 0, sizeof(dir->path));
+ dir->has_next = 0;
+ dir->n_files = 0;
+ _TINYDIR_FREE(dir->_files);
+ dir->_files = NULL;
+#ifdef _MSC_VER
+ if (dir->_h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(dir->_h);
+ }
+ dir->_h = INVALID_HANDLE_VALUE;
+#else
+ if (dir->_d)
+ {
+ _tinydir_closedir(dir->_d);
+ }
+ dir->_d = NULL;
+ dir->_e = NULL;
+#ifndef _TINYDIR_USE_READDIR
+ _TINYDIR_FREE(dir->_ep);
+ dir->_ep = NULL;
+#endif
+#endif
+}
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir)
+{
+ if (dir == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!dir->has_next)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+#ifdef _MSC_VER
+ if (FindNextFile(dir->_h, &dir->_f) == 0)
+#else
+#ifdef _TINYDIR_USE_READDIR
+ dir->_e = _tinydir_readdir(dir->_d);
+#else
+ if (dir->_ep == NULL)
+ {
+ return -1;
+ }
+ if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
+ {
+ return -1;
+ }
+#endif
+ if (dir->_e == NULL)
+#endif
+ {
+ dir->has_next = 0;
+#ifdef _MSC_VER
+ if (GetLastError() != ERROR_SUCCESS &&
+ GetLastError() != ERROR_NO_MORE_FILES)
+ {
+ tinydir_close(dir);
+ errno = EIO;
+ return -1;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
+{
+ const _tinydir_char_t *filename;
+ if (dir == NULL || file == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+#ifdef _MSC_VER
+ if (dir->_h == INVALID_HANDLE_VALUE)
+#else
+ if (dir->_e == NULL)
+#endif
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ filename =
+#ifdef _MSC_VER
+ dir->_f.cFileName;
+#else
+ dir->_e->d_name;
+#endif
+ if (_tinydir_strlen(dir->path) +
+ _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >=
+ _TINYDIR_PATH_MAX)
+ {
+ /* the path for the file will be too long */
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ _tinydir_strcpy(file->path, dir->path);
+ if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0)
+ _tinydir_strcat(file->path, TINYDIR_STRING("/"));
+ _tinydir_strcpy(file->name, filename);
+ _tinydir_strcat(file->path, filename);
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+ if (_tstat(
+#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \
+ || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \
+ || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L))
+ if (lstat(
+#else
+ if (stat(
+#endif
+ file->path, &file->_s) == -1)
+ {
+ return -1;
+ }
+#endif
+ _tinydir_get_ext(file);
+
+ file->is_dir =
+#ifdef _MSC_VER
+ !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+#else
+ S_ISDIR(file->_s.st_mode);
+#endif
+ file->is_reg =
+#ifdef _MSC_VER
+ !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
+ (
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
+#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
+#endif
+#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
+#endif
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
+#else
+ S_ISREG(file->_s.st_mode);
+#endif
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
+{
+ if (dir == NULL || file == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (i >= dir->n_files)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ memcpy(file, &dir->_files[i], sizeof(tinydir_file));
+ _tinydir_get_ext(file);
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ if (dir == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (i >= dir->n_files || !dir->_files[i].is_dir)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ _tinydir_strcpy(path, dir->_files[i].path);
+ tinydir_close(dir);
+ if (tinydir_open_sorted(dir, path) == -1)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Open a single file given its path */
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
+{
+ tinydir_dir dir;
+ int result = 0;
+ int found = 0;
+ _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
+ _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX];
+ _tinydir_char_t *dir_name;
+ _tinydir_char_t *base_name;
+#if (defined _MSC_VER || defined __MINGW32__)
+ _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX];
+ _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
+#endif
+
+ if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* Get the parent path */
+#if (defined _MSC_VER || defined __MINGW32__)
+#if ((defined _MSC_VER) && (_MSC_VER >= 1400))
+ errno = _tsplitpath_s(
+ path,
+ drive_buf, _TINYDIR_DRIVE_MAX,
+ dir_name_buf, _TINYDIR_FILENAME_MAX,
+ file_name_buf, _TINYDIR_FILENAME_MAX,
+ ext_buf, _TINYDIR_FILENAME_MAX);
+#else
+ _tsplitpath(
+ path,
+ drive_buf,
+ dir_name_buf,
+ file_name_buf,
+ ext_buf);
+#endif
+
+ if (errno)
+ {
+ return -1;
+ }
+
+/* _splitpath_s not work fine with only filename and widechar support */
+#ifdef _UNICODE
+ if (drive_buf[0] == L'\xFEFE')
+ drive_buf[0] = '\0';
+ if (dir_name_buf[0] == L'\xFEFE')
+ dir_name_buf[0] = '\0';
+#endif
+
+ /* Emulate the behavior of dirname by returning "." for dir name if it's
+ empty */
+ if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
+ {
+ _tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
+ }
+ /* Concatenate the drive letter and dir name to form full dir name */
+ _tinydir_strcat(drive_buf, dir_name_buf);
+ dir_name = drive_buf;
+ /* Concatenate the file name and extension to form base name */
+ _tinydir_strcat(file_name_buf, ext_buf);
+ base_name = file_name_buf;
+#else
+ _tinydir_strcpy(dir_name_buf, path);
+ dir_name = dirname(dir_name_buf);
+ _tinydir_strcpy(file_name_buf, path);
+ base_name = basename(file_name_buf);
+#endif
+
+ /* Special case: if the path is a root dir, open the parent dir as the file */
+#if (defined _MSC_VER || defined __MINGW32__)
+ if (_tinydir_strlen(base_name) == 0)
+#else
+ if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0)
+#endif
+ {
+ memset(file, 0, sizeof * file);
+ file->is_dir = 1;
+ file->is_reg = 0;
+ _tinydir_strcpy(file->path, dir_name);
+ file->extension = file->path + _tinydir_strlen(file->path);
+ return 0;
+ }
+
+ /* Open the parent directory */
+ if (tinydir_open(&dir, dir_name) == -1)
+ {
+ return -1;
+ }
+
+ /* Read through the parent directory and look for the file */
+ while (dir.has_next)
+ {
+ if (tinydir_readfile(&dir, file) == -1)
+ {
+ result = -1;
+ goto bail;
+ }
+ if (_tinydir_strcmp(file->name, base_name) == 0)
+ {
+ /* File found */
+ found = 1;
+ break;
+ }
+ tinydir_next(&dir);
+ }
+ if (!found)
+ {
+ result = -1;
+ errno = ENOENT;
+ }
+
+bail:
+ tinydir_close(&dir);
+ return result;
+}
+
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file)
+{
+ _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
+ if (period == NULL)
+ {
+ file->extension = &(file->name[_tinydir_strlen(file->name)]);
+ }
+ else
+ {
+ file->extension = period + 1;
+ }
+}
+
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b)
+{
+ const tinydir_file *fa = (const tinydir_file *)a;
+ const tinydir_file *fb = (const tinydir_file *)b;
+ if (fa->is_dir != fb->is_dir)
+ {
+ return -(fa->is_dir - fb->is_dir);
+ }
+ return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
+}
+
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+/*
+The following authored by Ben Hutchings <[email protected]>
+from https://womble.decadent.org.uk/readdir_r-advisory.html
+*/
+/* Calculate the required buffer size (in bytes) for directory *
+* entries read from the given directory handle. Return -1 if this *
+* this cannot be done. *
+* *
+* This code does not trust values of NAME_MAX that are less than *
+* 255, since some systems (including at least HP-UX) incorrectly *
+* define it to be a smaller value. */
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
+{
+ long name_max;
+ size_t name_end;
+ /* parameter may be unused */
+ (void)dirp;
+
+#if defined _TINYDIR_USE_FPATHCONF
+ name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
+ if (name_max == -1)
+#if defined(NAME_MAX)
+ name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+ return (size_t)(-1);
+#endif
+#elif defined(NAME_MAX)
+ name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+#error "buffer size for readdir_r cannot be determined"
+#endif
+ name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
+ return (name_end > sizeof(struct _tinydir_dirent) ?
+ name_end : sizeof(struct _tinydir_dirent));
+}
+#endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+# if defined (_MSC_VER)
+# pragma warning(pop)
+# endif
+
+#endif