1 /**
2 * @file
3 * HTTPD custom file system example
4 *
5 * This file demonstrates how to add support for an external file system to httpd.
6 * It provides access to the specified root directory and uses stdio.h file functions
7 * to read files.
8 *
9 * ATTENTION: This implementation is *not* secure: no checks are added to ensure
10 * files are only read below the specified root directory!
11 */
12
13 /*
14 * Copyright (c) 2017 Simon Goldschmidt
15 * All rights reserved.
16 *
17 * Redistribution and use in source and binary forms, with or without modification,
18 * are permitted provided that the following conditions are met:
19 *
20 * 1. Redistributions of source code must retain the above copyright notice,
21 * this list of conditions and the following disclaimer.
22 * 2. Redistributions in binary form must reproduce the above copyright notice,
23 * this list of conditions and the following disclaimer in the documentation
24 * and/or other materials provided with the distribution.
25 * 3. The name of the author may not be used to endorse or promote products
26 * derived from this software without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
29 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
30 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
31 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
33 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
36 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
37 * OF SUCH DAMAGE.
38 *
39 * This file is part of the lwIP TCP/IP stack.
40 *
41 * Author: Simon Goldschmidt <goldsimon@gmx.de>
42 *
43 */
44
45 #include "lwip/opt.h"
46 #include "fs_example.h"
47
48 #include "lwip/apps/fs.h"
49 #include "lwip/def.h"
50 #include "lwip/mem.h"
51
52 #include <stdio.h>
53 #include <string.h>
54
55 /** define LWIP_HTTPD_EXAMPLE_CUSTOMFILES to 1 to enable this file system */
56 #ifndef LWIP_HTTPD_EXAMPLE_CUSTOMFILES
57 #define LWIP_HTTPD_EXAMPLE_CUSTOMFILES 0
58 #endif
59
60 /** define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED to 1 to delay open and read
61 * as if e.g. reading from external SPI flash */
62 #ifndef LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
63 #define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED 1
64 #endif
65
66 /** define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ to the number of bytes
67 * to read to emulate limited transfer buffers and don't read whole files in
68 * one chunk.
69 * WARNING: lowering this slows down the connection!
70 */
71 #ifndef LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ
72 #define LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ 0
73 #endif
74
75 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES
76
77 #if !LWIP_HTTPD_CUSTOM_FILES
78 #error This needs LWIP_HTTPD_CUSTOM_FILES
79 #endif
80 #if !LWIP_HTTPD_DYNAMIC_HEADERS
81 #error This needs LWIP_HTTPD_DYNAMIC_HEADERS
82 #endif
83 #if !LWIP_HTTPD_DYNAMIC_FILE_READ
84 #error This wants to demonstrate read-after-open, so LWIP_HTTPD_DYNAMIC_FILE_READ is required!
85 #endif
86 #if !LWIP_HTTPD_FS_ASYNC_READ
87 #error This needs LWIP_HTTPD_FS_ASYNC_READ
88 #endif
89 #if !LWIP_HTTPD_FILE_EXTENSION
90 #error This needs LWIP_HTTPD_FILE_EXTENSION
91 #endif
92
93 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
94 #include "lwip/tcpip.h"
95 #endif
96
97 struct fs_custom_data {
98 FILE *f;
99 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
100 int delay_read;
101 fs_wait_cb callback_fn;
102 void *callback_arg;
103 #endif
104 };
105
106 const char* fs_ex_root_dir;
107
108 void
fs_ex_init(const char * httpd_root_dir)109 fs_ex_init(const char *httpd_root_dir)
110 {
111 fs_ex_root_dir = strdup(httpd_root_dir);
112 }
113
114 #if LWIP_HTTPD_CUSTOM_FILES
115 int
fs_open_custom(struct fs_file * file,const char * name)116 fs_open_custom(struct fs_file *file, const char *name)
117 {
118 char full_filename[256];
119 FILE *f;
120
121 snprintf(full_filename, 255, "%s%s", fs_ex_root_dir, name);
122 full_filename[255] = 0;
123
124 f = fopen(full_filename, "rb");
125 if (f != NULL) {
126 if (!fseek(f, 0, SEEK_END)) {
127 int len = (int)ftell(f);
128 if(!fseek(f, 0, SEEK_SET)) {
129 struct fs_custom_data *data = (struct fs_custom_data *)mem_malloc(sizeof(struct fs_custom_data));
130 LWIP_ASSERT("out of memory?", data != NULL);
131 memset(file, 0, sizeof(struct fs_file));
132 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
133 file->len = 0; /* read size delayed */
134 data->delay_read = 3;
135 LWIP_UNUSED_ARG(len);
136 #else
137 file->len = len;
138 #endif
139 file->flags = FS_FILE_FLAGS_HEADER_PERSISTENT;
140 data->f = f;
141 file->pextension = data;
142 return 1;
143 }
144 }
145 fclose(f);
146 }
147 return 0;
148 }
149
150 void
fs_close_custom(struct fs_file * file)151 fs_close_custom(struct fs_file *file)
152 {
153 if (file && file->pextension) {
154 struct fs_custom_data *data = (struct fs_custom_data *)file->pextension;
155 if (data->f != NULL) {
156 fclose(data->f);
157 data->f = NULL;
158 }
159 mem_free(data);
160 }
161 }
162
163 #if LWIP_HTTPD_FS_ASYNC_READ
164 u8_t
fs_canread_custom(struct fs_file * file)165 fs_canread_custom(struct fs_file *file)
166 {
167 /* This function is only necessary for asynchronous I/O:
168 If reading would block, return 0 and implement fs_wait_read_custom() to call the
169 supplied callback if reading works. */
170 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
171 struct fs_custom_data *data;
172 LWIP_ASSERT("file != NULL", file != NULL);
173 data = (struct fs_custom_data *)file->pextension;
174 if (data == NULL) {
175 /* file transfer has been completed already */
176 LWIP_ASSERT("transfer complete", file->index == file->len);
177 return 1;
178 }
179 LWIP_ASSERT("data != NULL", data != NULL);
180 /* This just simulates a simple delay. This delay would normally come e.g. from SPI transfer */
181 if (data->delay_read == 3) {
182 /* delayed file size mode */
183 data->delay_read = 1;
184 LWIP_ASSERT("", file->len == 0);
185 if (!fseek(data->f, 0, SEEK_END)) {
186 int len = (int)ftell(data->f);
187 if(!fseek(data->f, 0, SEEK_SET)) {
188 file->len = len; /* read size delayed */
189 data->delay_read = 1;
190 return 0;
191 }
192 }
193 /* if we come here, something is wrong with the file */
194 LWIP_ASSERT("file error", 0);
195 }
196 if (data->delay_read == 1) {
197 /* tell read function to delay further */
198 }
199 #endif
200 LWIP_UNUSED_ARG(file);
201 return 1;
202 }
203
204 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
205 static void
fs_example_read_cb(void * arg)206 fs_example_read_cb(void *arg)
207 {
208 struct fs_custom_data *data = (struct fs_custom_data *)arg;
209 fs_wait_cb callback_fn = data->callback_fn;
210 void *callback_arg = data->callback_arg;
211 data->callback_fn = NULL;
212 data->callback_arg = NULL;
213
214 LWIP_ASSERT("no callback_fn", callback_fn != NULL);
215
216 callback_fn(callback_arg);
217 }
218 #endif
219
220 u8_t
fs_wait_read_custom(struct fs_file * file,fs_wait_cb callback_fn,void * callback_arg)221 fs_wait_read_custom(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg)
222 {
223 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
224 err_t err;
225 struct fs_custom_data *data = (struct fs_custom_data *)file->pextension;
226 LWIP_ASSERT("data not set", data != NULL);
227 data->callback_fn = callback_fn;
228 data->callback_arg = callback_arg;
229 err = tcpip_try_callback(fs_example_read_cb, data);
230 LWIP_ASSERT("out of queue elements?", err == ERR_OK);
231 LWIP_UNUSED_ARG(err);
232 #else
233 LWIP_ASSERT("not implemented in this example configuration", 0);
234 #endif
235 LWIP_UNUSED_ARG(file);
236 LWIP_UNUSED_ARG(callback_fn);
237 LWIP_UNUSED_ARG(callback_arg);
238 /* Return
239 - 0 if ready to read (at least one byte)
240 - 1 if reading should be delayed (call 'tcpip_callback(callback_fn, callback_arg)' when ready) */
241 return 1;
242 }
243
244 int
fs_read_async_custom(struct fs_file * file,char * buffer,int count,fs_wait_cb callback_fn,void * callback_arg)245 fs_read_async_custom(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg)
246 {
247 struct fs_custom_data *data = (struct fs_custom_data *)file->pextension;
248 FILE *f;
249 int len;
250 int read_count = count;
251 LWIP_ASSERT("data not set", data != NULL);
252
253 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_DELAYED
254 /* This just simulates a delay. This delay would normally come e.g. from SPI transfer */
255 LWIP_ASSERT("invalid state", data->delay_read >= 0 && data->delay_read <= 2);
256 if (data->delay_read == 2) {
257 /* no delay next time */
258 data->delay_read = 0;
259 return FS_READ_DELAYED;
260 } else if (data->delay_read == 1) {
261 err_t err;
262 /* execute requested delay */
263 data->delay_read = 2;
264 LWIP_ASSERT("duplicate callback request", data->callback_fn == NULL);
265 data->callback_fn = callback_fn;
266 data->callback_arg = callback_arg;
267 err = tcpip_try_callback(fs_example_read_cb, data);
268 LWIP_ASSERT("out of queue elements?", err == ERR_OK);
269 LWIP_UNUSED_ARG(err);
270 return FS_READ_DELAYED;
271 }
272 /* execute this read but delay the next one */
273 data->delay_read = 1;
274 #endif
275
276 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ
277 read_count = LWIP_MIN(read_count, LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ);
278 #endif
279
280 f = data->f;
281 len = (int)fread(buffer, 1, read_count, f);
282
283 LWIP_UNUSED_ARG(callback_fn);
284 LWIP_UNUSED_ARG(callback_arg);
285
286 file->index += len;
287
288 /* Return
289 - FS_READ_EOF if all bytes have been read
290 - FS_READ_DELAYED if reading is delayed (call 'tcpip_callback(callback_fn, callback_arg)' when done) */
291 if (len == 0) {
292 /* all bytes read already */
293 return FS_READ_EOF;
294 }
295 return len;
296 }
297
298 #else /* LWIP_HTTPD_FS_ASYNC_READ */
299 int
fs_read_custom(struct fs_file * file,char * buffer,int count)300 fs_read_custom(struct fs_file *file, char *buffer, int count)
301 {
302 struct fs_custom_data *data = (struct fs_custom_data *)file->pextension;
303 FILE *f;
304 int len;
305 int read_count = count;
306 LWIP_ASSERT("data not set", data != NULL);
307
308 #if LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ
309 read_count = LWIP_MIN(read_count, LWIP_HTTPD_EXAMPLE_CUSTOMFILES_LIMIT_READ);
310 #endif
311
312 f = data->f;
313 len = (int)fread(buffer, 1, read_count, f);
314
315 file->index += len;
316
317 /* Return FS_READ_EOF if all bytes have been read */
318 return len;
319 }
320
321 #endif /* LWIP_HTTPD_FS_ASYNC_READ */
322 #endif /* LWIP_HTTPD_CUSTOM_FILES */
323
324 #endif /* LWIP_HTTPD_EXAMPLE_CUSTOMFILES */
325