Commit 96150bf5 authored by Deomid Ryabkov's avatar Deomid Ryabkov Committed by rojer

Commonize file upload and make it part of Mongoose

PUBLISHED_FROM=23819ed308aeb8c1d6bdb08f5edd257df458ab38
parent f49df515
......@@ -8,6 +8,7 @@ signature: |
const char *var_name;
struct mg_str data;
int status; /* <0 on error */
void *user_data;
};
---
......
......@@ -4254,6 +4254,7 @@ struct mg_http_multipart_stream {
int boundary_len;
const char *var_name;
const char *file_name;
void *user_data;
int prev_io_len;
enum mg_http_multipart_stream_state state;
int processing_part;
......@@ -5342,9 +5343,11 @@ static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
mp.var_name = pd->mp_stream.var_name;
mp.file_name = pd->mp_stream.file_name;
mp.user_data = pd->mp_stream.user_data;
mp.data.p = data;
mp.data.len = data_len;
mg_call(c, pd->endpoint_handler, ev, &mp);
pd->mp_stream.user_data = mp.user_data;
}
static int mg_http_multipart_got_chunk(struct mg_connection *c) {
......@@ -5526,6 +5529,128 @@ static void mg_http_multipart_continue(struct mg_connection *c) {
}
}
struct file_upload_state {
char *lfn;
size_t num_recd;
FILE *fp;
};
void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
mg_fu_fname_fn local_name_fn) {
switch (ev) {
case MG_EV_HTTP_PART_BEGIN: {
struct mg_http_multipart_part *mp =
(struct mg_http_multipart_part *) ev_data;
struct file_upload_state *fus =
(struct file_upload_state *) calloc(1, sizeof(*fus));
mp->user_data = NULL;
struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name));
if (lfn.p == NULL || lfn.len == 0) {
LOG(LL_ERROR, ("%p Not allowed to upload %s", nc, mp->file_name));
mg_printf(nc,
"HTTP/1.1 403 Not Allowed\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n"
"Not allowed to upload %s\r\n",
mp->file_name);
nc->flags |= MG_F_SEND_AND_CLOSE;
return;
}
fus->lfn = (char *) malloc(lfn.len + 1);
memcpy(fus->lfn, lfn.p, lfn.len);
fus->lfn[lfn.len] = '\0';
if (lfn.p != mp->file_name) free((char *) lfn.p);
LOG(LL_DEBUG,
("%p Receiving file %s -> %s", nc, mp->file_name, fus->lfn));
fus->fp = fopen(fus->lfn, "w");
if (fus->fp == NULL) {
mg_printf(nc,
"HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n");
LOG(LL_ERROR, ("Failed to open %s: %d\n", fus->lfn, errno));
mg_printf(nc, "Failed to open %s: %d\n", fus->lfn, errno);
/* Do not close the connection just yet, discard remainder of the data.
* This is because at the time of writing some browsers (Chrome) fail to
* render response before all the data is sent. */
}
mp->user_data = (void *) fus;
break;
}
case MG_EV_HTTP_PART_DATA: {
struct mg_http_multipart_part *mp =
(struct mg_http_multipart_part *) ev_data;
struct file_upload_state *fus =
(struct file_upload_state *) mp->user_data;
if (fus == NULL || fus->fp == NULL) break;
if (fwrite(mp->data.p, 1, mp->data.len, fus->fp) != mp->data.len) {
LOG(LL_ERROR, ("Failed to write to %s: %d, wrote %d", fus->lfn, errno,
(int) fus->num_recd));
if (errno == ENOSPC
#ifdef SPIFFS_ERR_FULL
|| errno == SPIFFS_ERR_FULL
#endif
) {
mg_printf(nc,
"HTTP/1.1 413 Payload Too Large\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n");
mg_printf(nc, "Failed to write to %s: no space left; wrote %d\r\n",
fus->lfn, (int) fus->num_recd);
} else {
mg_printf(nc,
"HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n");
mg_printf(nc, "Failed to write to %s: %d, wrote %d", mp->file_name,
errno, (int) fus->num_recd);
}
fclose(fus->fp);
remove(fus->lfn);
fus->fp = NULL;
/* Do not close the connection just yet, discard remainder of the data.
* This is because at the time of writing some browsers (Chrome) fail to
* render response before all the data is sent. */
return;
}
fus->num_recd += mp->data.len;
LOG(LL_DEBUG, ("%p rec'd %d bytes, %d total", nc, (int) mp->data.len,
(int) fus->num_recd));
break;
}
case MG_EV_HTTP_PART_END: {
struct mg_http_multipart_part *mp =
(struct mg_http_multipart_part *) ev_data;
struct file_upload_state *fus =
(struct file_upload_state *) mp->user_data;
if (fus == NULL) break;
if (mp->status >= 0 && fus->fp != NULL) {
LOG(LL_DEBUG, ("%p Uploaded %s (%s), %d bytes", nc, mp->file_name,
fus->lfn, (int) fus->num_recd));
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n"
"Ok, %s - %d bytes.\r\n",
mp->file_name, (int) fus->num_recd);
} else {
LOG(LL_ERROR, ("Failed to store %s (%s)", mp->file_name, fus->lfn));
/*
* mp->status < 0 means connection was terminated, so no reason to send
* HTTP reply
*/
}
if (fus->fp != NULL) fclose(fus->fp);
free(fus->lfn);
free(fus);
mp->user_data = NULL;
nc->flags |= MG_F_SEND_AND_CLOSE;
break;
}
}
}
#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
void mg_set_protocol_http_websocket(struct mg_connection *nc) {
......
......@@ -1994,6 +1994,7 @@ struct mg_http_multipart_part {
const char *var_name;
struct mg_str data;
int status; /* <0 on error */
void *user_data;
};
/* HTTP and websocket events. void *ev_data is described in a comment. */
......@@ -2526,6 +2527,46 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
mg_event_handler_t handler);
#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
/*
* File upload handler.
* This handler can be used to implement file uploads with minimum code.
* This handler will process MG_EV_HTTP_PART_* events and store file data into
* a local file.
* `local_name_fn` will be invoked with whatever name was provided by the client
* and will expect the name of the local file to open. Return value of NULL will
* abort file upload (client will get a "403 Forbidden" response). If non-null,
* the returned string must be heap-allocated and will be freed by the caller.
* Exception: it is ok to return the same string verbatim.
*
* Example:
*
* ```c
* struct mg_str upload_fname(struct mg_connection *nc, struct mg_str fname) {
* // Just return the same filename. Do not actually do this except in test!
* // fname is user-controlled and needs to be sanitized.
* return fname;
* }
* void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
* switch (ev) {
* ...
* case MG_EV_HTTP_PART_BEGIN:
* case MG_EV_HTTP_PART_DATA:
* case MG_EV_HTTP_PART_END:
* mg_file_upload_handler(nc, ev, ev_data, upload_fname);
* break;
* }
* }
* ```
*/
typedef struct mg_str (*mg_fu_fname_fn)(struct mg_connection *nc,
struct mg_str fname);
void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
mg_fu_fname_fn local_name_fn);
#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
#ifdef __cplusplus
}
#endif /* __cplusplus */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment