mirror of
https://github.com/cesanta/mongoose.git
synced 2025-06-07 09:27:05 +08:00
Commonize file upload and make it part of Mongoose
PUBLISHED_FROM=23819ed308aeb8c1d6bdb08f5edd257df458ab38
This commit is contained in:
parent
f49df51544
commit
96150bf568
@ -8,6 +8,7 @@ signature: |
|
||||
const char *var_name;
|
||||
struct mg_str data;
|
||||
int status; /* <0 on error */
|
||||
void *user_data;
|
||||
};
|
||||
---
|
||||
|
||||
|
125
mongoose.c
125
mongoose.c
@ -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) {
|
||||
|
41
mongoose.h
41
mongoose.h
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user