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