/** * @file * HTTPD custom file system example * * This file demonstrates how to add support for an external file system to httpd. * It provides access to the specified root directory and uses stdio.h file functions * to read files. * * ATTENTION: This implementation is *not* secure: no checks are added to ensure * files are only read below the specified root directory! */ /* * Copyright (c) 2017 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 * */ #include "lwip/opt.h" #include "fs_example.h" #include "lwip/apps/fs.h" #include "lwip/def.h" #include "lwip/mem.h" #include #include /** define LWIP_HTTPD_EXAMPLE_CUSTOMFILES to 1 to enable this file system */ #ifndef LWIP_HTTPD_EXAMPLE_CUSTOMFILES #define LWIP_HTTPD_EXAMPLE_CUSTOMFILES 0 #endif /** define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED to 1 to delay open and read * as if e.g. reading from external SPI flash */ #ifndef LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED #define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED 1 #endif /** define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ to the number of bytes * to read to emulate limited transfer buffers and don't read whole files in * one chunk. * WARNING: lowering this slows down the connection! */ #ifndef LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ #define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ 0 #endif #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES #if !LWIP_HTTPD_CUSTOM_FILES #error This needs LWIP_HTTPD_CUSTOM_FILES #endif #if !LWIP_HTTPD_DYNAMIC_HEADERS #error This needs LWIP_HTTPD_DYNAMIC_HEADERS #endif #if !LWIP_HTTPD_DYNAMIC_FILE_READ #error This wants to demonstrate read-after-open, so LWIP_HTTPD_DYNAMIC_FILE_READ is required! #endif #if !LWIP_HTTPD_FS_ASYNC_READ #error This needs LWIP_HTTPD_FS_ASYNC_READ #endif #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED #include "lwip/tcpip.h" #endif struct fs_custom_data { FILE *f; #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED int delay_read; fs_wait_cb callback_fn; void *callback_arg; #endif }; const char* fs_ex_root_dir; void fs_ex_init(const char *httpd_root_dir) { fs_ex_root_dir = strdup(httpd_root_dir); } #if LWIP_HTTPD_CUSTOM_FILES int fs_open_custom(struct fs_file *file, const char *name) { char full_filename[256]; FILE *f; snprintf(full_filename, 255, "%s%s", fs_ex_root_dir, name); full_filename[255] = 0; f = fopen(full_filename, "rb"); if (f != NULL) { if (!fseek(f, 0, SEEK_END)) { int len = (int)ftell(f); if(!fseek(f, 0, SEEK_SET)) { struct fs_custom_data *data = (struct fs_custom_data *)mem_malloc(sizeof(struct fs_custom_data)); LWIP_ASSERT("out of memory?", data != NULL); memset(file, 0, sizeof(struct fs_file)); #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED file->len = 0; /* read size delayed */ data->delay_read = 3; LWIP_UNUSED_ARG(len); #else file->len = len; #endif file->flags = FS_FILE_FLAGS_HEADER_PERSISTENT; data->f = f; file->pextension = data; return 1; } } fclose(f); } return 0; } void fs_close_custom(struct fs_file *file) { if (file && file->pextension) { struct fs_custom_data *data = (struct fs_custom_data *)file->pextension; if (data->f != NULL) { fclose(data->f); data->f = NULL; } mem_free(data); } } #if LWIP_HTTPD_FS_ASYNC_READ u8_t fs_canread_custom(struct fs_file *file) { /* This function is only necessary for asynchronous I/O: If reading would block, return 0 and implement fs_wait_read_custom() to call the supplied callback if reading works. */ #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED struct fs_custom_data *data; LWIP_ASSERT("file != NULL", file != NULL); data = (struct fs_custom_data *)file->pextension; if (data == NULL) { /* file transfer has been completed already */ LWIP_ASSERT("transfer complete", file->index == file->len); return 1; } LWIP_ASSERT("data != NULL", data != NULL); /* This just simulates a simple delay. This delay would normally come e.g. from SPI transfer */ if (data->delay_read == 3) { /* delayed file size mode */ data->delay_read = 1; LWIP_ASSERT("", file->len == 0); if (!fseek(data->f, 0, SEEK_END)) { int len = (int)ftell(data->f); if(!fseek(data->f, 0, SEEK_SET)) { file->len = len; /* read size delayed */ data->delay_read = 1; return 0; } } /* if we come here, something is wrong with the file */ LWIP_ASSERT("file error", 0); } if (data->delay_read == 1) { /* tell read function to delay further */ } #endif LWIP_UNUSED_ARG(file); return 1; } #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED static void fs_example_read_cb(void *arg) { struct fs_custom_data *data = (struct fs_custom_data *)arg; fs_wait_cb callback_fn = data->callback_fn; void *callback_arg = data->callback_arg; data->callback_fn = NULL; data->callback_arg = NULL; LWIP_ASSERT("no callback_fn", callback_fn != NULL); callback_fn(callback_arg); } #endif u8_t fs_wait_read_custom(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg) { #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED err_t err; struct fs_custom_data *data = (struct fs_custom_data *)file->pextension; LWIP_ASSERT("data not set", data != NULL); data->callback_fn = callback_fn; data->callback_arg = callback_arg; err = tcpip_try_callback(fs_example_read_cb, data); LWIP_ASSERT("out of queue elements?", err == ERR_OK); LWIP_UNUSED_ARG(err); #else LWIP_ASSERT("not implemented in this example configuration", 0); #endif LWIP_UNUSED_ARG(file); LWIP_UNUSED_ARG(callback_fn); LWIP_UNUSED_ARG(callback_arg); /* Return - 1 if ready to read (at least one byte) - 0 if reading should be delayed (call 'tcpip_callback(callback_fn, callback_arg)' when ready) */ return 1; } int fs_read_async_custom(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg) { struct fs_custom_data *data = (struct fs_custom_data *)file->pextension; FILE *f; int len; int read_count = count; LWIP_ASSERT("data not set", data != NULL); #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED /* This just simulates a delay. This delay would normally come e.g. from SPI transfer */ LWIP_ASSERT("invalid state", data->delay_read >= 0 && data->delay_read <= 2); if (data->delay_read == 2) { /* no delay next time */ data->delay_read = 0; return FS_READ_DELAYED; } else if (data->delay_read == 1) { err_t err; /* execute requested delay */ data->delay_read = 2; LWIP_ASSERT("duplicate callback request", data->callback_fn == NULL); data->callback_fn = callback_fn; data->callback_arg = callback_arg; err = tcpip_try_callback(fs_example_read_cb, data); LWIP_ASSERT("out of queue elements?", err == ERR_OK); LWIP_UNUSED_ARG(err); return FS_READ_DELAYED; } /* execute this read but delay the next one */ data->delay_read = 1; #endif #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ read_count = LWIP_MIN(read_count, LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ); #endif f = data->f; len = (int)fread(buffer, 1, read_count, f); LWIP_UNUSED_ARG(callback_fn); LWIP_UNUSED_ARG(callback_arg); file->index += len; /* Return - FS_READ_EOF if all bytes have been read - FS_READ_DELAYED if reading is delayed (call 'tcpip_callback(callback_fn, callback_arg)' when done) */ if (len == 0) { /* all bytes read already */ return FS_READ_EOF; } return len; } #else /* LWIP_HTTPD_FS_ASYNC_READ */ int fs_read_custom(struct fs_file *file, char *buffer, int count) { struct fs_custom_data *data = (struct fs_custom_data *)file->pextension; FILE *f; int len; int read_count = count; LWIP_ASSERT("data not set", data != NULL); #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ read_count = LWIP_MIN(read_count, LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ); #endif f = data->f; len = (int)fread(buffer, 1, read_count, f); file->index += len; /* Return FS_READ_EOF if all bytes have been read */ return len; } #endif /* LWIP_HTTPD_FS_ASYNC_READ */ #endif /* LWIP_HTTPD_CUSTOM_FILES */ #endif /* LWIP_HTTPD_EXAMPLE_CUSTOMFILES */