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, &params ) ) {
                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, &params ) ) {
                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, &params ) ) {
                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, &params ) ) {
                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);
}

Reply via email to