Hi,
I'm playing with a REST framework. I've not been able to get PUT requests
to work as I would expect them to. I think I have had some luck with PUT
in earlier tests I performed, but I didn't save that code.
I would expect to be able to use PUT and POST interchangeably, as the
documentation and this message suggests:
http://lists.gnu.org/archive/html/libmicrohttpd/2012-08/msg00022.html
The attached code is tested with curl commands:
curl --upload-file uploadfile http://localhost:8080/FILE/foo.dat # PUT test
curl --form upload=@uploadfile http://localhost:8080/FILE/foo.dat # POST
test
I'm seeing the print statements in the post_interator() routine executed
for the POST request but not for the PUT request.
--
----Jordan Henderson
"I can picture in my mind a world without war, a world without hate. And I
can picture us attacking that world, because they'd never expect it."
- Jack Handey
/*
* File: main.c
* Author: Jordan
*
* Created on January 20, 2014, 2:54 PM
*/
#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#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 = "<html><head><title>AVTARS ERROR</title></head><body>";
char *affix = "</body></html>";
/* 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,
"<html><body>Resource/name too long</body></html>" );
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 =
"<html><body>200 Delete Success</body></html>";
ret = send_page( connection,
MHD_HTTP_OK,
delete_msg,
MHD_RESPMEM_MUST_COPY );
}
else {
char delete_buff[1024];
sprintf( delete_buff,
"<html><body>Delete fails (%d)</body></html>",
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,
"<html><body>Resource/name too long</body></html>" );
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,
"<html><body>Cannot open %s</body></html>",
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, "<html><body>200 OK</body></html>" );
ret = send_page( connection,
MHD_HTTP_OK,
success_page,
MHD_RESPMEM_MUST_COPY );
}
else {
char error_page[ 1024 ];
sprintf( error_page,
"<html><body>%s</body></html>",
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,
"<html><body>Resource/name too long</body></html>" );
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,
"<html><body>Cannot open %s</body></html>",
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, "<html><body>200 OK</body></html>" );
ret = send_page( connection,
MHD_HTTP_OK,
success_page,
MHD_RESPMEM_MUST_COPY );
}
else {
char error_page[ 1024 ];
sprintf( error_page,
"<html><body>%s</body></html>",
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,
"<html><body>Resource/name too long</body></html>" );
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,
"<html><body>200 OK</body></html>" );
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);
}