Implemented basic upload functionality
This commit is contained in:
parent
d07fc77f7d
commit
54dd5bae12
37
include/main.h
Normal file
37
include/main.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef MAIN_H
|
||||||
|
#define MAIN_H
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
enum ResultCode {
|
||||||
|
RC_OK = 0, // No problem (so far)
|
||||||
|
RC_EXISTS = 1, // On upload, the file to be uploaded already existed
|
||||||
|
RC_OPEN_FAILED = 2, // On upload, opening the file for writing failed
|
||||||
|
RC_WRONG_TARGET = 3 // On upload, the given URL was not a directory
|
||||||
|
};
|
||||||
|
|
||||||
|
enum RequestType {
|
||||||
|
GET,
|
||||||
|
POST
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionState {
|
||||||
|
char cleanedURL[PATH_MAX];
|
||||||
|
char localFileName[PATH_MAX];
|
||||||
|
struct stat targetStat;
|
||||||
|
|
||||||
|
FILE *upload_fd;
|
||||||
|
|
||||||
|
enum ResultCode result;
|
||||||
|
enum RequestType requestType;
|
||||||
|
|
||||||
|
struct MHD_PostProcessor *postProcessor;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RequestRange {
|
||||||
|
off_t start;
|
||||||
|
off_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // MAIN_H
|
|
@ -11,6 +11,10 @@
|
||||||
#ifndef UTIL_H
|
#ifndef UTIL_H
|
||||||
#define UTIL_H
|
#define UTIL_H
|
||||||
|
|
||||||
|
#ifndef MAX
|
||||||
|
#define MAX(a, b) ((a > b) ? a : b)
|
||||||
|
#endif
|
||||||
|
|
||||||
void remove_trailing_slash(char *str);
|
void remove_trailing_slash(char *str);
|
||||||
void urlencode(const char *str, char *result);
|
void urlencode(const char *str, char *result);
|
||||||
|
|
||||||
|
|
240
src/main.c
240
src/main.c
|
@ -39,24 +39,16 @@
|
||||||
#include "favicon.h"
|
#include "favicon.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
#define DEFAULT_PORT 8888
|
#define DEFAULT_PORT 8888
|
||||||
|
|
||||||
struct ConnectionState {
|
|
||||||
char cleanedURL[PATH_MAX];
|
|
||||||
char localFileName[PATH_MAX];
|
|
||||||
struct stat targetStat;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RequestRange {
|
|
||||||
off_t start;
|
|
||||||
off_t length;
|
|
||||||
};
|
|
||||||
|
|
||||||
char *shareRoot;
|
char *shareRoot;
|
||||||
struct MHD_Response *error403Response;
|
struct MHD_Response *error403Response;
|
||||||
struct MHD_Response *error404Response;
|
struct MHD_Response *error404Response;
|
||||||
struct MHD_Response *error500Response;
|
struct MHD_Response *error500Response;
|
||||||
struct MHD_Response *faviconResponse;
|
struct MHD_Response *faviconResponse;
|
||||||
|
struct MHD_Response *uploadFormResponse;
|
||||||
|
|
||||||
#ifdef HAVE_MAGIC
|
#ifdef HAVE_MAGIC
|
||||||
magic_t magicCookie;
|
magic_t magicCookie;
|
||||||
|
@ -73,6 +65,15 @@ void request_completed(void *cls,
|
||||||
// TODO: show IP+Port in log
|
// TODO: show IP+Port in log
|
||||||
LOG(LVL_DEBUG, "Freeing state for request of %s", connstate->localFileName);
|
LOG(LVL_DEBUG, "Freeing state for request of %s", connstate->localFileName);
|
||||||
|
|
||||||
|
// close the upload file if open
|
||||||
|
if(connstate->upload_fd) {
|
||||||
|
fclose(connstate->upload_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(connstate->postProcessor) {
|
||||||
|
MHD_destroy_post_processor(connstate->postProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
free(connstate);
|
free(connstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,6 +224,115 @@ int serv_directory(struct MHD_Connection *connection, struct ConnectionState *co
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int serv_result_page(struct MHD_Connection *connection, struct ConnectionState *connstate) {
|
||||||
|
struct MHD_Response *response;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
char *message, *title;
|
||||||
|
unsigned int resultCode;
|
||||||
|
|
||||||
|
switch(connstate->result) {
|
||||||
|
case RC_OK:
|
||||||
|
message = "Request was processed successfully.";
|
||||||
|
title = "Success";
|
||||||
|
resultCode = MHD_HTTP_OK;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RC_EXISTS:
|
||||||
|
message = "The file you tried to upload already exists on the server.";
|
||||||
|
title = "File already exists";
|
||||||
|
resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RC_OPEN_FAILED:
|
||||||
|
message = "Could not open the file on the server for writing.";
|
||||||
|
title = "Open failed";
|
||||||
|
resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RC_WRONG_TARGET:
|
||||||
|
message = "You tried to put a new file into something which is not a directory.";
|
||||||
|
title = "Target error";
|
||||||
|
resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buffer = malloc(strlen(HEADER1 HEADER2 FOOTER) + 1024);
|
||||||
|
|
||||||
|
sprintf(buffer, HEADER1 "<title>%s</title>" HEADER2 "<h1>%s</h1><p>%s</p>" FOOTER,
|
||||||
|
title, title, message);
|
||||||
|
|
||||||
|
response = MHD_create_response_from_buffer(
|
||||||
|
strlen(buffer), buffer, MHD_RESPMEM_MUST_FREE);
|
||||||
|
|
||||||
|
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
|
||||||
|
ret = MHD_queue_response(connection, resultCode, response);
|
||||||
|
MHD_destroy_response(response);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int post_processor(void *cls,
|
||||||
|
enum MHD_ValueKind kind,
|
||||||
|
const char *key,
|
||||||
|
const char *filename,
|
||||||
|
const char *content_type,
|
||||||
|
const char *transfer_encoding,
|
||||||
|
const char *data,
|
||||||
|
uint64_t off,
|
||||||
|
size_t size) {
|
||||||
|
LOG(LVL_DUMP, "Entering POST handler (key=%s, filename=%s, contentType=%s, transferEncoding=%s, size=%u)",
|
||||||
|
key, filename, content_type, transfer_encoding, size);
|
||||||
|
|
||||||
|
struct ConnectionState *ci = (struct ConnectionState*)cls;
|
||||||
|
|
||||||
|
if(kind == MHD_POSTDATA_KIND) {
|
||||||
|
if(strcmp("data", key) == 0) {
|
||||||
|
if(ci->upload_fd == NULL) {
|
||||||
|
// file pointer not allocated yet
|
||||||
|
// determine the path where to store the uploaded file
|
||||||
|
char uploadFilename[PATH_MAX];
|
||||||
|
|
||||||
|
if(!S_ISDIR(ci->targetStat.st_mode)) {
|
||||||
|
LOG(LVL_WARN, "Cannot upload %s: Target path is not an existing directory.", filename);
|
||||||
|
ci->result = RC_WRONG_TARGET;
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(uploadFilename, ci->localFileName, PATH_MAX);
|
||||||
|
strncat(uploadFilename, "/", PATH_MAX);
|
||||||
|
strncat(uploadFilename, filename, PATH_MAX);
|
||||||
|
|
||||||
|
// check if the file already exists
|
||||||
|
FILE *tmp = fopen(uploadFilename, "rb");
|
||||||
|
if(tmp) {
|
||||||
|
LOG(LVL_WARN, "Cannot upload to %s: file exists.", uploadFilename);
|
||||||
|
ci->result = RC_EXISTS;
|
||||||
|
fclose(tmp);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// file does not exist and can therefore be opened for writing
|
||||||
|
ci->upload_fd = fopen(uploadFilename, "wb");
|
||||||
|
if(!ci->upload_fd) {
|
||||||
|
LOG(LVL_WARN, "Cannot upload to %s: file cannot be created.", uploadFilename);
|
||||||
|
ci->result = RC_OPEN_FAILED;
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(data, size, 1, ci->upload_fd);
|
||||||
|
} else {
|
||||||
|
// file descriptor is already open
|
||||||
|
fwrite(data, size, 1, ci->upload_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MHD_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
static int connection_handler(void * cls,
|
static int connection_handler(void * cls,
|
||||||
struct MHD_Connection *connection,
|
struct MHD_Connection *connection,
|
||||||
const char *url,
|
const char *url,
|
||||||
|
@ -233,17 +343,15 @@ static int connection_handler(void * cls,
|
||||||
void **ptr) {
|
void **ptr) {
|
||||||
struct ConnectionState *connstate;
|
struct ConnectionState *connstate;
|
||||||
|
|
||||||
if (0 != strcmp(method, "GET") && 0 != strcmp(method, "HEAD")) {
|
|
||||||
LOG(LVL_WARN, "Unexpected method: %s.", method);
|
|
||||||
return MHD_NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*ptr == NULL) {
|
if (*ptr == NULL) {
|
||||||
// This is the first time this connection is seen.
|
// This is the first time this connection is seen.
|
||||||
// Initialize the connection state
|
// Initialize the connection state
|
||||||
connstate = malloc(sizeof(struct ConnectionState));
|
connstate = malloc(sizeof(struct ConnectionState));
|
||||||
*ptr = connstate;
|
*ptr = connstate;
|
||||||
|
|
||||||
|
connstate->result = RC_OK;
|
||||||
|
connstate->upload_fd = NULL;
|
||||||
|
|
||||||
// set the local file name
|
// set the local file name
|
||||||
strncpy(connstate->localFileName, shareRoot, PATH_MAX);
|
strncpy(connstate->localFileName, shareRoot, PATH_MAX);
|
||||||
strncat(connstate->localFileName, url,
|
strncat(connstate->localFileName, url,
|
||||||
|
@ -257,49 +365,71 @@ static int connection_handler(void * cls,
|
||||||
LOG(LVL_INFO, "%s %s (local: %s)",
|
LOG(LVL_INFO, "%s %s (local: %s)",
|
||||||
method, url, connstate->localFileName);
|
method, url, connstate->localFileName);
|
||||||
|
|
||||||
|
if (0 == strcmp(method, "GET") || 0 == strcmp(method, "HEAD")) {
|
||||||
|
connstate->postProcessor = NULL;
|
||||||
|
connstate->requestType = GET;
|
||||||
|
} else if(0 == strcmp(method, "POST")) {
|
||||||
|
connstate->postProcessor = MHD_create_post_processor(connection, 1024*1024, post_processor, connstate);
|
||||||
|
connstate->requestType = POST;
|
||||||
|
} else {
|
||||||
|
LOG(LVL_WARN, "Unexpected method: %s.", method);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if url contains a "/.." sequence. If so, block the request as it is
|
||||||
|
// trying to access the parent directory (and may be an attempt to leave the
|
||||||
|
// shared space.
|
||||||
|
if(NULL != strstr(url, "/..")) {
|
||||||
|
LOG(LVL_WARN, "User is trying to access %s, which may be an attempt to leave the shared space.",
|
||||||
|
url);
|
||||||
|
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check properties of the target file/dir
|
||||||
|
if(stat(connstate->localFileName, &(connstate->targetStat)) == -1) {
|
||||||
|
LOG(LVL_ERR, "Cannot stat %s: %s",
|
||||||
|
connstate->localFileName, strerror(errno));
|
||||||
|
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, error404Response);
|
||||||
|
}
|
||||||
|
|
||||||
return MHD_YES;
|
return MHD_YES;
|
||||||
} else {
|
} else {
|
||||||
connstate = *ptr;
|
connstate = *ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 != *upload_data_size) {
|
if(connstate->requestType == GET) {
|
||||||
return MHD_NO; // upload data in a GET!?
|
if (0 != *upload_data_size) {
|
||||||
}
|
return MHD_NO; // upload data in a GET!?
|
||||||
|
}
|
||||||
|
|
||||||
// serv the favicon, if requested
|
// serv the favicon, if requested
|
||||||
if(strcmp(url, "/favicon.png") == 0) {
|
if(strcmp(url, "/favicon.png") == 0) {
|
||||||
LOG(LVL_INFO, "Serving FavIcon request.");
|
LOG(LVL_INFO, "Serving FavIcon request.");
|
||||||
return MHD_queue_response(connection, MHD_HTTP_OK, faviconResponse);
|
return MHD_queue_response(connection, MHD_HTTP_OK, faviconResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if url contains a "/.." sequence. If so, block the request as it is
|
if(S_ISREG(connstate->targetStat.st_mode)) {
|
||||||
// trying to access the parent directory (and may be an attempt to leave the
|
return serv_regular_file(connection, connstate);
|
||||||
// shared space.
|
} else if(S_ISDIR(connstate->targetStat.st_mode)) {
|
||||||
if(NULL != strstr(url, "/..")) {
|
return serv_directory(connection, connstate);
|
||||||
LOG(LVL_WARN, "User is trying to access %s, which may be an attempt to leave the shared space.",
|
} else {
|
||||||
url);
|
LOG(LVL_WARN,
|
||||||
|
"%s is neither a directory nor a regular file. Don't allow the access.",
|
||||||
|
connstate->localFileName);
|
||||||
|
|
||||||
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
||||||
}
|
}
|
||||||
|
} else if(connstate->requestType == POST) {
|
||||||
|
if (*upload_data_size != 0) {
|
||||||
|
MHD_post_process (connstate->postProcessor, upload_data, *upload_data_size);
|
||||||
|
*upload_data_size = 0;
|
||||||
|
|
||||||
// check properties of the target file/dir
|
return MHD_YES;
|
||||||
if(stat(connstate->localFileName, &(connstate->targetStat)) == -1) {
|
} else {
|
||||||
LOG(LVL_ERR, "Cannot stat %s: %s",
|
return serv_result_page(connection, connstate);
|
||||||
connstate->localFileName, strerror(errno));
|
}
|
||||||
|
|
||||||
return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, error404Response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(S_ISREG(connstate->targetStat.st_mode)) {
|
|
||||||
return serv_regular_file(connection, connstate);
|
|
||||||
} else if(S_ISDIR(connstate->targetStat.st_mode)) {
|
|
||||||
return serv_directory(connection, connstate);
|
|
||||||
} else {
|
|
||||||
LOG(LVL_WARN,
|
|
||||||
"%s is neither a directory nor a regular file. Don't allow the access.",
|
|
||||||
connstate->localFileName);
|
|
||||||
|
|
||||||
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a 500 if request was not handled properly
|
// generate a 500 if request was not handled properly
|
||||||
|
@ -477,6 +607,15 @@ int main(int argc, char ** argv) {
|
||||||
MHD_add_response_header(error404Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
MHD_add_response_header(error404Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
MHD_add_response_header(error500Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
MHD_add_response_header(error500Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
|
||||||
|
// static response for upload form
|
||||||
|
uploadFormResponse = MHD_create_response_from_data(
|
||||||
|
strlen(UPLOAD_FORM),
|
||||||
|
(void*) UPLOAD_FORM,
|
||||||
|
MHD_NO,
|
||||||
|
MHD_NO);
|
||||||
|
|
||||||
|
MHD_add_response_header(uploadFormResponse, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
|
||||||
// static response for favicon
|
// static response for favicon
|
||||||
faviconResponse = create_favicon_response();
|
faviconResponse = create_favicon_response();
|
||||||
|
|
||||||
|
@ -527,6 +666,7 @@ int main(int argc, char ** argv) {
|
||||||
MHD_destroy_response(error404Response);
|
MHD_destroy_response(error404Response);
|
||||||
MHD_destroy_response(error500Response);
|
MHD_destroy_response(error500Response);
|
||||||
MHD_destroy_response(faviconResponse);
|
MHD_destroy_response(faviconResponse);
|
||||||
|
MHD_destroy_response(uploadFormResponse);
|
||||||
|
|
||||||
#ifdef HAVE_MAGIC
|
#ifdef HAVE_MAGIC
|
||||||
magic_close(magicCookie);
|
magic_close(magicCookie);
|
||||||
|
|
Loading…
Reference in a new issue