From 0d442058e5ea232d3c496ae0ee5f37eb7152db4b Mon Sep 17 00:00:00 2001 From: Sergey Lyubka Date: Fri, 7 Dec 2012 01:50:47 +0000 Subject: [PATCH] Added mg_upload() --- examples/upload.c | 99 ++++------------------------------------- mongoose.c | 109 +++++++++++++++++++++++++++++++++++++++++++++- mongoose.h | 11 +++++ 3 files changed, 127 insertions(+), 92 deletions(-) diff --git a/examples/upload.c b/examples/upload.c index 93c353d5..87e05aa0 100644 --- a/examples/upload.c +++ b/examples/upload.c @@ -26,97 +26,11 @@ static const char *html_form = "" ""; -static const char *HTTP_500 = "HTTP/1.0 500 Server Error\r\n\r\n"; - -static void handle_file_upload(struct mg_connection *conn) { - const char *cl_header; - char post_data[16 * 1024], path[999], file_name[1024], mime_type[100], - buf[BUFSIZ], *eop, *s, *p; - FILE *fp; - int64_t cl, written; - int fd, n, post_data_len; - - // Figure out total content length. Return if it is not present or invalid. - cl_header = mg_get_header(conn, "Content-Length"); - if (cl_header == NULL || (cl = strtoll(cl_header, NULL, 10)) <= 0) { - mg_printf(conn, "%s%s", HTTP_500, "Invalid Conent-Length"); - return; - } - - // Read the initial chunk into memory. This should be multipart POST data. - // Parse headers, where we should find file name and content-type. - post_data_len = mg_read(conn, post_data, sizeof(post_data)); - file_name[0] = mime_type[0] = '\0'; - for (s = p = post_data; p < &post_data[post_data_len]; p++) { - if (p[0] == '\r' && p[1] == '\n') { - if (s == p) { - p += 2; - break; // End of headers - } - p[0] = p[1] = '\0'; - sscanf(s, "Content-Type: %99s", mime_type); - // TODO(lsm): don't expect filename to be the 3rd field, - // parse the header properly instead. - sscanf(s, "Content-Disposition: %*s %*s filename=\"%1023[^\"]", - file_name); - s = p + 2; - } - } - - // Finished parsing headers. Now "p" points to the first byte of data. - // Calculate file size - cl -= p - post_data; // Subtract headers size - cl -= strlen(post_data); // Subtract the boundary marker at the end - cl -= 6; // Subtract "\r\n" before and after boundary - - // Construct destination file name. Write to /tmp, do not allow - // paths that contain slashes. - if ((s = strrchr(file_name, '/')) == NULL) { - s = file_name; - } - snprintf(path, sizeof(path), "/tmp/%s", s); - - if (file_name[0] == '\0') { - mg_printf(conn, "%s%s", HTTP_500, "Can't get file name"); - } else if (cl <= 0) { - mg_printf(conn, "%s%s", HTTP_500, "Empty file"); - } else if ((fd = open(path, O_CREAT | O_TRUNC | -#ifdef _WIN32 - O_BINARY | -#else - O_EXLOCK | O_CLOEXEC | -#endif - O_WRONLY)) < 0) { - // We're opening the file with exclusive lock held. This guarantee us that - // there is no other thread can save into the same file simultaneously. - mg_printf(conn, "%s%s", HTTP_500, "Cannot open file"); - } else if ((fp = fdopen(fd, "w")) == NULL) { - mg_printf(conn, "%s%s", HTTP_500, "Cannot reopen file stream"); - close(fd); - } else { - // Success. Write data into the file. - eop = post_data + post_data_len; - n = p + cl > eop ? (int) (eop - p) : (int) cl; - (void) fwrite(p, 1, n, fp); - written = n; - while (written < cl && - (n = mg_read(conn, buf, cl - written > (int64_t) sizeof(buf) ? - sizeof(buf) : cl - written)) > 0) { - (void) fwrite(buf, 1, n, fp); - written += n; - } - (void) fclose(fp); - mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n" - "Saved to [%s], written %llu bytes", path, cl); - } -} - static void *callback(enum mg_event event, struct mg_connection *conn) { - const struct mg_request_info *ri = mg_get_request_info(conn); - if (event == MG_NEW_REQUEST) { - if (!strcmp(ri->uri, "/handle_post_request")) { - handle_file_upload(conn); + if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) { + mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n"); + mg_upload(conn, "/tmp"); } else { // Show HTML form. mg_printf(conn, "HTTP/1.0 200 OK\r\n" @@ -126,9 +40,11 @@ static void *callback(enum mg_event event, struct mg_connection *conn) { } // Mark as processed return ""; - } else { - return NULL; + } else if (event == MG_UPLOAD) { + mg_printf(conn, "Saved [%s]", mg_get_request_info(conn)->ev_data); } + + return NULL; } int main(void) { @@ -137,6 +53,7 @@ int main(void) { ctx = mg_start(&callback, NULL, options); getchar(); // Wait until user hits "enter" + pause(); mg_stop(ctx); return 0; diff --git a/mongoose.c b/mongoose.c index 21a9aba6..d7b1240d 100644 --- a/mongoose.c +++ b/mongoose.c @@ -747,7 +747,7 @@ static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, } // Skip the characters until one of the delimiters characters found. -// 0-terminate resulting word. Skip the delimiter and following whitespaces if any. +// 0-terminate resulting word. Skip the delimiter and following whitespaces. // Advance pointer to buffer to the next word. Return found 0-terminated word. // Delimiters can be quoted with quotechar. static char *skip_quoted(char **buf, const char *delimiters, @@ -4035,6 +4035,113 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path, } #endif // USE_LUA +int mg_upload(struct mg_connection *conn, const char *destination_dir) { + const char *content_type_header, *boundary_start; + char buf[8192], path[PATH_MAX], fname[1024], boundary[100], *s; + FILE *fp; + int bl, n, i, j, headers_len, boundary_len, len = 0, num_uploaded_files = 0; + + // Request looks like this: + // + // POST /upload HTTP/1.1 + // Host: 127.0.0.1:8080 + // Content-Length: 244894 + // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr + // + // ------WebKitFormBoundaryRVr + // Content-Disposition: form-data; name="file"; filename="accum.png" + // Content-Type: image/png + // + // <89>PNG + // + // ------WebKitFormBoundaryRVr + + // Extract boundary string from the Content-Type header + if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL || + (boundary_start = strstr(content_type_header, "boundary=")) == NULL || + (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 && + sscanf(boundary_start, "boundary=%99s", boundary) == 0) || + boundary[0] == '\0') { + return num_uploaded_files; + } + + boundary_len = strlen(boundary); + bl = boundary_len + 4; // \r\n-- + for (;;) { + // Pull in headers + assert(len >= 0 && len <= (int) sizeof(buf)); + while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0) { + len += n; + } + if ((headers_len = get_request_len(buf, len)) <= 0) { + break; + } + + // Fetch file name. + fname[0] = '\0'; + for (i = j = 0; i < headers_len; i++) { + if (buf[i] == '\r' && buf[i + 1] == '\n') { + buf[i] = buf[i + 1] = '\0'; + // TODO(lsm): don't expect filename to be the 3rd field, + // parse the header properly instead. + sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]", + fname); + j = i + 2; + } + } + + // Give up if the headers are not what we expect + if (fname[0] == '\0') { + break; + } + + // Move data to the beginning of the buffer + assert(len >= headers_len); + memmove(buf, &buf[headers_len], len - headers_len); + len -= headers_len; + + // We open the file with exclusive lock held. This guarantee us + // there is no other thread can save into the same file simultaneously. + fp = NULL; + // Construct destination file name. Do not allow paths to have slashes. + if ((s = strrchr(fname, '/')) == NULL) { + s = fname; + } + // Open file in binary mode with exclusive lock set + snprintf(path, sizeof(path), "%s/%s", destination_dir, s); + if ((fp = fopen(path, "wbx")) == NULL) { + break; + } + + // Read POST data, write into file until boundary is found. + n = 0; + do { + len += n; + for (i = 0; i < len - bl; i++) { + if (!memcmp(&buf[i], "\r\n--", 4) && + !memcmp(&buf[i + 4], boundary, boundary_len)) { + // Found boundary, that's the end of file data. + (void) fwrite(buf, 1, i, fp); + num_uploaded_files++; + conn->request_info.ev_data = (void *) path; + call_user(conn, MG_UPLOAD); + memmove(buf, &buf[i + bl], len - (i + bl)); + len -= i + bl; + break; + } + } + if (len > bl) { + fwrite(buf, 1, len - bl, fp); + memmove(buf, &buf[len - bl], len - bl); + len = bl; + } + } while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0); + fclose(fp); + } + + return num_uploaded_files; +} + // This is the heart of the Mongoose's logic. // This function is called when the request is read, parsed and validated, // and Mongoose must decide what action to take: serve a file, or diff --git a/mongoose.h b/mongoose.h index 81039109..c51baad4 100644 --- a/mongoose.h +++ b/mongoose.h @@ -127,6 +127,11 @@ enum mg_event { // Callback's return value is ignored. // ev_data contains lua_State pointer. MG_INIT_LUA, + + // Mongoose has uploaded file to a temporary directory. + // Callback's return value is ignored. + // ev_data contains NUL-terminated file name. + MG_UPLOAD, }; @@ -336,6 +341,12 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path, char *buf, size_t buf_len, struct mg_request_info *request_info); +// File upload functionality. Each uploaded file gets saved into a temporary +// file and MG_UPLOAD event is sent. +// Return number of uploaded files. +int mg_upload(struct mg_connection *conn, const char *destination_dir); + + // Convenience function -- create detached thread. // Return: 0 on success, non-0 on error. typedef void * (*mg_thread_func_t)(void *);