/* * File: main.c * Author: Jordan * * Created on January 20, 2014, 2:54 PM */ #include #include #include #include #include #include #define MHD_RETURN(ret) (ret ? MHD_YES : MHD_NO); int debug = 1; int done = 0; /* * islegal_uri_char( int ) * * Returns true if the parameter is a legal character. * */ static int islegal_uri_char(int b) { return ( islower( b ) || isupper( b ) || isdigit( b ) || ( '_' == b ) || ( '.' == b ) ); } #define MAX_PATH 256 /* * make_path( path, resource, rname ) * * Build a path from a resource and rname. If rname == NULL, resource is * filename and path is local directory. This is used for default and local * file operations for consistency. * */ int make_path( char *path, char *resource, char *rname ) { int ret = 0; if ( resource != NULL ) { char *directory; char *basename; if ( rname == NULL ) { directory = "."; basename = resource; } else { directory = resource; basename = rname; } /* * Space for directory + space for / + space for basename + * space for null terminator. */ if ( strlen( directory ) + 1 + strlen( basename ) + 1 < MAX_PATH ) { sprintf( path, "%s/%s", directory, basename ); ret = 1; } } return ret; } /* * validate_resource * * Check that URL has a valid resource, defeat any potential attacks based * on buffer overruns, backing up the file system with .. or other tricks, * validate characters in URL. * * URLs are of the form: * * /resource/rname[/?params] * * resource is the application being activated * rname is a name passed to the application (often a filename) * params are additional parameters, typical in POST requests * * There are no plans to use params at this time. * * Potentially modifies url by terminating at resource and rname boundary * (If there are extra parameters) with a \0. * * resource points to the start of the resource at end * params points to any extra parameter passed (typically from a POST) * callers should probably make sure (*params == '\0') if not a POST */ static int valid_resource(char *url, char **resource, char **rname, char **params) { int ret = 0; *resource = NULL; *rname = NULL; *params = NULL; if ( url[ 0 ] == '/' ) { int iptr = 1; char ichar = url[ iptr ]; *resource = &url[ iptr ]; while ( islegal_uri_char( (int)ichar ) ) { ichar = url[ ++iptr ]; } /* resource ends in '/', get rname if present */ if ( ichar == '/' ) { url[ iptr++ ] = '\0'; if ( url[ iptr ] != '\0' ) { *rname = &url[ iptr ]; while ( islegal_uri_char( (int)ichar ) ) { ichar = url[ ++iptr ]; } } ichar = '\0'; } /* * resource ends in '/' or (resource or rname) end in '?', * get params if present * */ if ( ( ichar == '/' ) || ( ichar == '?' ) ) { url[ iptr++ ] = '\0'; if ( url[ iptr ] != '\0' ) { *params = &url[ iptr ]; } } if ( *rname == NULL ) { *rname = *resource; } ret = 1; } return ret; } /* * char * error_page( int status, char *error_msg ) * */ static char * error_page( int status, char *error_msg, int free_msg ) { char *prefix = "AVTARS ERROR"; char *affix = ""; /* Include space for prefix, the error message, affix and \0 terminator. */ char *return_page = malloc( strlen( prefix ) + strlen( error_msg ) + strlen( affix ) + 1 ); sprintf( return_page, "%s%s%s", prefix, error_msg, affix ); if ( free_msg ) { free( error_msg ); } return return_page; } /* * char * file_data(filename, status, fio_error) * * Return data from file in pointer. Returns NULL on failure, status * gives a status code and fio_error gives the IO error if one occurs. * */ /* Increase FILE_BUFFER_SIZE for larger data */ #define FILE_BUFFER_SIZE 10240 #define FILE_DATA_NO_ERROR 0 #define FILE_DATA_MALLOC_ERROR -1 #define FILE_DATA_OPEN_ERROR -2 #define FILE_DATA_READ_ERROR -3 #define FILE_DATA_WRITE_ERROR -4 static char * file_error(const char *filename, int error, int *status, char **fio_error) { char *tmperror = strerror( errno ); *status = error; /* space for filename:error_message */ *fio_error = malloc( strlen( filename ) + 1 + strlen( tmperror ) + 1 ); if ( *fio_error == NULL ) { *status = FILE_DATA_MALLOC_ERROR; } else { sprintf( *fio_error, "%s:%s", filename, tmperror ); } } static char * file_dataread(const char *filename, int *status, char **fio_error) { FILE *file_ptr = fopen( filename, "rb" ); char *data = NULL; *status = FILE_DATA_NO_ERROR; if ( file_ptr == NULL ) { file_error( filename, FILE_DATA_OPEN_ERROR, status, fio_error ); } else { char file_buffer[ FILE_BUFFER_SIZE ]; size_t file_read; long data_eptr = 0; do { if ( ( file_read = fread( file_buffer, sizeof(char), FILE_BUFFER_SIZE, file_ptr ) ) > 0 ) { data = realloc( data, data_eptr + file_read ); if ( data != NULL ) { memcpy( data + data_eptr, file_buffer, file_read ); data_eptr += file_read; } } } while (( file_read > 0 ) && ( data != NULL )); /* At least some data was read. */ if ( data != NULL ) { /* * If we didn't read to eof or can't close, * then an I/O error occurred. */ if ( !feof( file_ptr ) || ( fclose( file_ptr ) != 0 ) ) { free( data ); file_error( filename, FILE_DATA_READ_ERROR, status, fio_error ); data = NULL; } } } return data; } /* static int file_datawrite(const char *filename, int *status, const char *data, size_t ndata, char **fio_error) { FILE *file_ptr = fopen( filename, "wb" ); int ret = 0; *status = FILE_DATA_NO_ERROR; if ( NULL == file_ptr ) { file_error( filename, FILE_DATA_OPEN_ERROR, status, fio_error ); } else if ( fwrite( data, sizeof( char ), ndata, file_ptr ) != ndata ) { file_error( filename, FILE_DATA_WRITE_ERROR, status, fio_error ); } else { ret = 1; } return ret; } */ static int send_page( struct MHD_Connection *connection, int http_status, const char *page, enum MHD_ResponseMemoryMode mode ) { int ret = MHD_NO; struct MHD_Response *response = MHD_create_response_from_buffer ( strlen( page ), (void *)page, mode ); if ( response != NULL ) { ret = MHD_queue_response( connection, http_status, response ); MHD_destroy_response( response ); } fflush(stdout); return ret; } /* * Data kept per request * */ struct Request { struct MHD_PostProcessor *pp; const char *path; FILE *file_ptr; char *error; }; static int post_iterator( 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 ) { printf( "POST process\n" ); printf( "key = %s\n", key ); printf( "filename = %s\n", filename ); printf( "content_type = %s\n", content_type ); printf( "transfer encoding = %s\n", transfer_encoding ); printf( "data = %s\n", data ); printf( "offset = %d, size = %d\n", off, size ); fflush( stdout ); struct Request *request = (struct Request *)cls; FILE *file_ptr = request->file_ptr; if ( file_ptr != NULL ) { if ( fwrite( data, sizeof( char ), size, file_ptr ) != size ) { int status; file_error( request->path, FILE_DATA_WRITE_ERROR, &status, &(request->error) ); return MHD_NO; } else { request->error = NULL; } } else { return MHD_NO; } return MHD_YES; } #define POST_BUFFER 1024 static int process_method(void *cls, struct MHD_Connection *connection, const char *curl, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **context_ptr) { static int first_pass_get_complete; int ret = MHD_NO; char *url = strdup( curl ); char *resource; char *rname; char *params; if ( ( 0 == strcmp( method, MHD_HTTP_METHOD_GET ) ) || ( 0 == strcmp( method, MHD_HTTP_METHOD_DELETE ) ) ) { if ( &first_pass_get_complete != *context_ptr ) { *context_ptr = &first_pass_get_complete; ret = MHD_YES; } else if ( 0 == *upload_data_size ) { /* Empty body for GET */ *context_ptr = NULL; /* clear context pointer */ if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } char path[ MAX_PATH ]; if ( valid_resource( url, &resource, &rname, ¶ms ) ) { if ( !make_path( path, resource, rname ) ) { char toolong_msg[ 1024 ]; strcpy( toolong_msg, "Resource/name too long" ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, toolong_msg, MHD_RESPMEM_MUST_COPY ); } else if ( 0 == strcmp( method, MHD_HTTP_METHOD_DELETE ) ) { int dstatus = unlink( path ); if ( dstatus == 0 ) { char *delete_msg = "200 Delete Success"; ret = send_page( connection, MHD_HTTP_OK, delete_msg, MHD_RESPMEM_MUST_COPY ); } else { char delete_buff[1024]; sprintf( delete_buff, "Delete fails (%d)", dstatus ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, delete_buff, MHD_RESPMEM_MUST_COPY ); } } else { struct MHD_Response *response; int file_data_status; char *fio_error; char *fdata = file_dataread( path, &file_data_status, &fio_error ); if ( fdata != NULL ) { ret = send_page( connection, MHD_HTTP_OK, fdata, MHD_RESPMEM_MUST_FREE ); } else { int http_status; char *perror_page; if ( file_data_status == FILE_DATA_OPEN_ERROR ) { http_status = MHD_HTTP_NOT_FOUND; perror_page = error_page( http_status, fio_error, 1 ); } else { http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; perror_page = error_page( http_status, "Internal allocation error.", 0 ); } ret = send_page( connection, http_status, perror_page, MHD_RESPMEM_MUST_FREE ); } } } } } else if ( 0 == strcmp( method, MHD_HTTP_METHOD_POST ) ) { if ( *context_ptr == NULL ) { char *path = malloc( MAX_PATH ); *context_ptr = malloc( sizeof( struct Request )); if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } if ( valid_resource( url, &resource, &rname, ¶ms ) ) { if ( !make_path( path, resource, rname ) ) { char toolong_msg[ 1024 ]; strcpy( toolong_msg, "Resource/name too long" ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, toolong_msg, MHD_RESPMEM_MUST_COPY ); } else { struct Request *request = *context_ptr; request->path = path; request->file_ptr = fopen( path, "wb" ); if ( request->file_ptr == NULL ) { char error_page[ 1024 ]; sprintf( error_page, "Cannot open %s", path ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, error_page, MHD_RESPMEM_MUST_COPY ); } else { request->error = NULL; request->pp = MHD_create_post_processor( connection, POST_BUFFER, &post_iterator, request ); if ( NULL != request->pp ) { ret = MHD_YES; } } } } } else if ( 0 != *upload_data_size ) { /* Body required for PUT/POST */ struct Request *request = *context_ptr; int file_data_status; if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } MHD_post_process( request->pp, upload_data, *upload_data_size ); *upload_data_size = 0; ret = MHD_YES; } else { struct Request *request = *context_ptr; if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } if ( NULL != request->file_ptr ) { fclose( request->file_ptr ); } if ( NULL == request->error ) { char success_page[ 1024 ]; strcpy( success_page, "200 OK" ); ret = send_page( connection, MHD_HTTP_OK, success_page, MHD_RESPMEM_MUST_COPY ); } else { char error_page[ 1024 ]; sprintf( error_page, "%s", request->error ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, error_page, MHD_RESPMEM_MUST_COPY ); } } } /* PUT doesn't seem to be working */ else if ( 0 == strcmp( method, MHD_HTTP_METHOD_PUT ) ) { if ( *context_ptr == NULL ) { char *path = malloc( MAX_PATH ); *context_ptr = malloc( sizeof( struct Request )); if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } if ( valid_resource( url, &resource, &rname, ¶ms ) ) { if ( !make_path( path, resource, rname ) ) { char toolong_msg[ 1024 ]; strcpy( toolong_msg, "Resource/name too long" ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, toolong_msg, MHD_RESPMEM_MUST_COPY ); } else { struct Request *request = *context_ptr; request->path = path; request->file_ptr = fopen( path, "wb" ); if ( request->file_ptr == NULL ) { char error_page[ 1024 ]; sprintf( error_page, "Cannot open %s", path ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, error_page, MHD_RESPMEM_MUST_COPY ); } else { request->error = NULL; request->pp = MHD_create_post_processor( connection, POST_BUFFER, &post_iterator, request ); if ( NULL != request->pp ) { ret = MHD_YES; } } } } } else if ( 0 != *upload_data_size ) { /* Body required for PUT/POST */ struct Request *request = *context_ptr; int file_data_status; if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } MHD_post_process( request->pp, upload_data, *upload_data_size ); *upload_data_size = 0; ret = MHD_YES; } else { struct Request *request = *context_ptr; if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } if ( NULL != request->file_ptr ) { fclose( request->file_ptr ); } if ( NULL == request->error ) { char success_page[ 1024 ]; strcpy( success_page, "200 OK" ); ret = send_page( connection, MHD_HTTP_OK, success_page, MHD_RESPMEM_MUST_COPY ); } else { char error_page[ 1024 ]; sprintf( error_page, "%s", request->error ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, error_page, MHD_RESPMEM_MUST_COPY ); } } } /* else if ( 0 == strcmp( method, MHD_HTTP_METHOD_PUT ) ) { if ( *context_ptr == NULL ) { char *path = malloc( MAX_PATH ); *context_ptr = malloc( sizeof( struct Request )); if ( debug ) { printf( "New %s request for (%s) using version %s\n", method, url, version ); } if ( valid_resource( url, &resource, &rname, ¶ms ) ) { if ( !make_path( path, resource, rname ) ) { char toolong_msg[ 1024 ]; strcpy( toolong_msg, "Resource/name too long" ); ret = send_page( connection, MHD_HTTP_NOT_FOUND, toolong_msg, MHD_RESPMEM_MUST_COPY ); } else { struct Request *request = *context_ptr; request->path = path; request->file_ptr = fopen( path, "wb" ); ret = MHD_YES; } } } else if ( 0 == *upload_data_size ) { struct Request *request = *context_ptr; if ( fwrite( upload_data, sizeof( char ), *upload_data_size, request->file_ptr ) == *upload_data_size ) { (void)fclose( request->file_ptr ); char success_page[ 1024 ]; strcpy( success_page, "200 OK" ); ret = send_page( connection, MHD_HTTP_OK, success_page, MHD_RESPMEM_MUST_COPY ); } ret = MHD_YES; } else { printf( "PUT third pass\n" ); ret = MHD_YES; } } */ return ret; } static void request_completed (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { struct Request *request = *con_cls; if ( NULL != request) { MHD_destroy_post_processor (request->pp); free( request ); *con_cls = NULL; } } int main(int argc, char **argv) { #define PORT 8080 struct MHD_Daemon *daemon; daemon = MHD_start_daemon( MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL, &process_method, NULL, MHD_OPTION_NOTIFY_COMPLETED, request_completed, NULL, MHD_OPTION_END); if (daemon != NULL) { while (!done) { sleep(1); } MHD_stop_daemon(daemon); } return (daemon == NULL); }