1 /*
2 * lws-minimal-http-server-form-post-file
3 *
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * This demonstrates a minimal http server that performs POST with a couple
10 * of parameters and a file upload, all in multipart (mime) form mode.
11 * It saves the uploaded file in the current directory, dumps the parameters to
12 * the console log and redirects to another page.
13 */
14
15 #include <libwebsockets.h>
16 #include <string.h>
17 #include <signal.h>
18 #if !defined(WIN32)
19 #include <unistd.h>
20 #endif
21 #include <fcntl.h>
22 #include <stdlib.h>
23 #include <errno.h>
24
25 /*
26 * Unlike ws, http is a stateless protocol. This pss only exists for the
27 * duration of a single http transaction. With http/1.1 keep-alive and http/2,
28 * that is unrelated to (shorter than) the lifetime of the network connection.
29 */
30 struct pss {
31 struct lws_spa *spa; /* lws helper decodes multipart form */
32 char filename[128]; /* the filename of the uploaded file */
33 unsigned long long file_length; /* the amount of bytes uploaded */
34 int fd; /* fd on file being saved */
35 };
36
37 static int interrupted;
38
39 static const char * const param_names[] = {
40 "text1",
41 "send",
42 };
43
44 enum enum_param_names {
45 EPN_TEXT1,
46 EPN_SEND,
47 };
48
49 static int
file_upload_cb(void * data,const char * name,const char * filename,char * buf,int len,enum lws_spa_fileupload_states state)50 file_upload_cb(void *data, const char *name, const char *filename,
51 char *buf, int len, enum lws_spa_fileupload_states state)
52 {
53 struct pss *pss = (struct pss *)data;
54
55 switch (state) {
56 case LWS_UFS_OPEN:
57 /* take a copy of the provided filename */
58 lws_strncpy(pss->filename, filename, sizeof(pss->filename) - 1);
59 /* remove any scary things like .. */
60 lws_filename_purify_inplace(pss->filename);
61 /* open a file of that name for write in the cwd */
62 pss->fd = lws_open(pss->filename, O_CREAT | O_TRUNC | O_RDWR, 0600);
63 if (pss->fd == -1) {
64 lwsl_notice("Failed to open output file %s\n",
65 pss->filename);
66 return 1;
67 }
68 break;
69 case LWS_UFS_FINAL_CONTENT:
70 case LWS_UFS_CONTENT:
71 if (len) {
72 int n;
73
74 pss->file_length += (unsigned int)len;
75
76 n = (int)write(pss->fd, buf, (unsigned int)len);
77 if (n < len) {
78 lwsl_notice("Problem writing file %d\n", errno);
79 }
80 }
81 if (state == LWS_UFS_CONTENT)
82 /* wasn't the last part of the file */
83 break;
84
85 /* the file upload is completed */
86
87 lwsl_user("%s: upload done, written %lld to %s\n", __func__,
88 pss->file_length, pss->filename);
89
90 close(pss->fd);
91 pss->fd = -1;
92 break;
93 case LWS_UFS_CLOSE:
94 break;
95 }
96
97 return 0;
98 }
99
100 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)101 callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
102 void *in, size_t len)
103 {
104 uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
105 *p = start, *end = &buf[sizeof(buf) - 1];
106 struct pss *pss = (struct pss *)user;
107 int n;
108
109 switch (reason) {
110 case LWS_CALLBACK_HTTP:
111
112 /*
113 * Manually report that our form target URL exists
114 *
115 * you can also do this by adding a mount for the form URL
116 * to the protocol with type LWSMPRO_CALLBACK, then no need
117 * to trap LWS_CALLBACK_HTTP.
118 */
119
120 if (!strcmp((const char *)in, "/form1"))
121 /* assertively allow it to exist in the URL space */
122 return 0;
123
124 /* default to 404-ing the URL if not mounted */
125 break;
126
127 case LWS_CALLBACK_HTTP_BODY:
128
129 /* create the POST argument parser if not already existing */
130
131 if (!pss->spa) {
132 pss->spa = lws_spa_create(wsi, param_names,
133 LWS_ARRAY_SIZE(param_names), 1024,
134 file_upload_cb, pss);
135 if (!pss->spa)
136 return -1;
137 }
138
139 /* let it parse the POST data */
140
141 if (lws_spa_process(pss->spa, in, (int)len))
142 return -1;
143 break;
144
145 case LWS_CALLBACK_HTTP_BODY_COMPLETION:
146
147 /* inform the spa no more payload data coming */
148
149 lws_spa_finalize(pss->spa);
150
151 /* we just dump the decoded things to the log */
152
153 for (n = 0; n < (int)LWS_ARRAY_SIZE(param_names); n++) {
154 if (!lws_spa_get_string(pss->spa, n))
155 lwsl_user("%s: undefined\n", param_names[n]);
156 else
157 lwsl_user("%s: (len %d) '%s'\n",
158 param_names[n],
159 lws_spa_get_length(pss->spa, n),
160 lws_spa_get_string(pss->spa, n));
161 }
162
163 /*
164 * Our response is to redirect to a static page. We could
165 * have generated a dynamic html page here instead.
166 */
167
168 if (lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
169 (unsigned char *)"after-form1.html",
170 16, &p, end) < 0)
171 return -1;
172
173 break;
174
175 case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
176 /* called when our wsi user_space is going to be destroyed */
177 if (pss->spa) {
178 lws_spa_destroy(pss->spa);
179 pss->spa = NULL;
180 }
181 break;
182
183 default:
184 break;
185 }
186
187 return lws_callback_http_dummy(wsi, reason, user, in, len);
188 }
189
190 static struct lws_protocols protocols[] = {
191 { "http", callback_http, sizeof(struct pss), 0, 0, NULL, 0 },
192 LWS_PROTOCOL_LIST_TERM
193 };
194
195 /* default mount serves the URL space from ./mount-origin */
196
197 static const struct lws_http_mount mount = {
198 /* .mount_next */ NULL, /* linked-list "next" */
199 /* .mountpoint */ "/", /* mountpoint URL */
200 /* .origin */ "./mount-origin", /* serve from dir */
201 /* .def */ "index.html", /* default filename */
202 /* .protocol */ NULL,
203 /* .cgienv */ NULL,
204 /* .extra_mimetypes */ NULL,
205 /* .interpret */ NULL,
206 /* .cgi_timeout */ 0,
207 /* .cache_max_age */ 0,
208 /* .auth_mask */ 0,
209 /* .cache_reusable */ 0,
210 /* .cache_revalidate */ 0,
211 /* .cache_intermediaries */ 0,
212 /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
213 /* .mountpoint_len */ 1, /* char count */
214 /* .basic_auth_login_file */ NULL,
215 };
216
sigint_handler(int sig)217 void sigint_handler(int sig)
218 {
219 interrupted = 1;
220 }
221
main(int argc,const char ** argv)222 int main(int argc, const char **argv)
223 {
224 struct lws_context_creation_info info;
225 struct lws_context *context;
226 const char *p;
227 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
228 /* for LLL_ verbosity above NOTICE to be built into lws,
229 * lws must have been configured and built with
230 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
231 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
232 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
233 /* | LLL_DEBUG */;
234
235 signal(SIGINT, sigint_handler);
236
237 if ((p = lws_cmdline_option(argc, argv, "-d")))
238 logs = atoi(p);
239
240 lws_set_log_level(logs, NULL);
241 lwsl_user("LWS minimal http server POST file | visit http://localhost:7681\n");
242
243 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
244 info.port = 7681;
245 info.protocols = protocols;
246 info.mounts = &mount;
247 info.options =
248 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
249
250 context = lws_create_context(&info);
251 if (!context) {
252 lwsl_err("lws init failed\n");
253 return 1;
254 }
255
256 while (n >= 0 && !interrupted)
257 n = lws_service(context, 0);
258
259 lws_context_destroy(context);
260
261 return 0;
262 }
263