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 */ 30struct 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 37static int interrupted; 38 39static const char * const param_names[] = { 40 "text1", 41 "send", 42}; 43 44enum enum_param_names { 45 EPN_TEXT1, 46 EPN_SEND, 47}; 48 49static int 50file_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 100static int 101callback_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 190static 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 197static 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 217void sigint_handler(int sig) 218{ 219 interrupted = 1; 220} 221 222int 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