mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-09 16:21:45 +00:00
Added mg_upload()
This commit is contained in:
1 parent
1ef31461bc
commit
0d442058e5
3 files changed
+127
-92
No files matched your search
+8
-91
@@ -26,97 +26,11 @@ static const char *html_form =
|
|||||||
"<input type=\"submit\" value=\"Upload\" />"
|
"<input type=\"submit\" value=\"Upload\" />"
|
||||||
"</form></body></html>";
|
"</form></body></html>";
|
||||||
|
|
||||||
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) {
|
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 (event == MG_NEW_REQUEST) {
|
||||||
if (!strcmp(ri->uri, "/handle_post_request")) {
|
if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
|
||||||
handle_file_upload(conn);
|
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
|
||||||
|
mg_upload(conn, "/tmp");
|
||||||
} else {
|
} else {
|
||||||
// Show HTML form.
|
// Show HTML form.
|
||||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
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
|
// Mark as processed
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else if (event == MG_UPLOAD) {
|
||||||
return NULL;
|
mg_printf(conn, "Saved [%s]", mg_get_request_info(conn)->ev_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
@@ -137,6 +53,7 @@ int main(void) {
|
|||||||
|
|
||||||
ctx = mg_start(&callback, NULL, options);
|
ctx = mg_start(&callback, NULL, options);
|
||||||
getchar(); // Wait until user hits "enter"
|
getchar(); // Wait until user hits "enter"
|
||||||
|
pause();
|
||||||
mg_stop(ctx);
|
mg_stop(ctx);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+108
-1
@@ -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.
|
// 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.
|
// Advance pointer to buffer to the next word. Return found 0-terminated word.
|
||||||
// Delimiters can be quoted with quotechar.
|
// Delimiters can be quoted with quotechar.
|
||||||
static char *skip_quoted(char **buf, const char *delimiters,
|
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
|
#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
|
||||||
|
// <PNG DATA>
|
||||||
|
// ------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--<boundary>
|
||||||
|
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 is the heart of the Mongoose's logic.
|
||||||
// This function is called when the request is read, parsed and validated,
|
// This function is called when the request is read, parsed and validated,
|
||||||
// and Mongoose must decide what action to take: serve a file, or
|
// and Mongoose must decide what action to take: serve a file, or
|
||||||
|
|||||||
+11
@@ -127,6 +127,11 @@ enum mg_event {
|
|||||||
// Callback's return value is ignored.
|
// Callback's return value is ignored.
|
||||||
// ev_data contains lua_State pointer.
|
// ev_data contains lua_State pointer.
|
||||||
MG_INIT_LUA,
|
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);
|
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.
|
// Convenience function -- create detached thread.
|
||||||
// Return: 0 on success, non-0 on error.
|
// Return: 0 on success, non-0 on error.
|
||||||
typedef void * (*mg_thread_func_t)(void *);
|
typedef void * (*mg_thread_func_t)(void *);
|
||||||
|
|||||||
Reference in new issue
Block a user