Sean C
Sean C

Reputation: 45

Upload HTML form data file to server using CGI in C?

I have a HTML form put in a CGI file in C with fprintf statements like this:

<form action="cgi-bin/upload.cgi" method="post" enctype="multipart/form-data"> 
    <p>Photo to Upload: <input type="file" name="photo" /></p> 
    <p>Your Email Address: <input type="text" name="email_address" /></p> 
    <p><input type="submit" name="Submit" value="Submit Form" /></p> 
</form>

I was wondering if it's possible to grab the file input (the values requested from the form with a multipart/form-data body), convert it to C code (process the values by obtaining the url data), and upload the file from there? BTW, I'm not allowed to use PHP as the machine I'm currently working with has very limited space.

Upvotes: 3

Views: 6763

Answers (2)

user9354964
user9354964

Reputation:

This example does not use any non-standard library:

#include <stdio.h>
#include <stdlib.h>

#define MAXLEN 80
#define EXTRA 5
/* 4 for field name "data", 1 for "=" */
#define MAXINPUT MAXLEN + EXTRA + 2
/* 1 for added line break, 1 for trailing NUL */
#define DATAFILE "../data/data.txt"

void unencode(char * src, char * last, char * dest) {
    for (; src != last; src++, dest++)
        if ( * src == '+')
            * dest = ' ';
        else if ( * src == '%') {
        int code;
        if (sscanf(src + 1, "%2x", & code) != 1) code = '?'; * dest = code;
        src += 2;
    } else
        *dest = * src; * dest = '\n'; * ++dest = '\0';
}

int main(void) {
    char * lenstr;
    char input[MAXINPUT], data[MAXINPUT];
    long len;
    printf("%s%c%c\n",
        "Content-Type:text/html;charset=iso-8859-1", 13, 10);
    printf("<TITLE>Response</TITLE>\n");
    lenstr = getenv("CONTENT_LENGTH");
    if (lenstr == NULL || sscanf(lenstr, "%ld", & len) != 1 || len > MAXLEN)
        printf("<P>Error in invocation - wrong FORM probably.");
    else {
        FILE * f;
        fgets(input, len + 1, stdin);
        unencode(input + EXTRA, input + len, data);
        f = fopen(DATAFILE, "a");
        if (f == NULL)
            printf("<P>Sorry, cannot store your data.");
        else
            fputs(data, f);
        fclose(f);
        printf("<P>Thank you! The following contribution of yours has \
been stored:<BR>%s", data);
    }
    return 0;
}

Upvotes: 3

user2371524
user2371524

Reputation:

Here's a simple example how you could handle file uploads through CGI. This has many shortcomings, for example it doesn't do any checks on the file size, name and type -- e.g. just concatenating the filename provided by the client is dangerous, it could contain ... Don't use the code shown here in production!

This example uses a Multipart form data parser I found on github. You would need some more code to actually parse the other form fields. You might want to start by looking at this answer including an example how a request body with multipart-form-data looks like.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "multipart_parser.h"

struct parsedata
{
    int inContentDisposition;  // flag for the right header to look for fields
    char *partname;            // field name
    char *filename;            // file name for an upload field
    FILE *saveto;              // file to save contents to
};

void showForm(void)
{
    puts("<form action=\"upload.cgi\" method=\"post\" "
            "enctype=\"multipart/form-data\">"
        "<p>Photo to Upload: <input type=\"file\" name=\"photo\" /></p>"
        "<p>Your Email Address: <input type=\"text\" "
            "name=\"email_address\" /></p>"
        "<p><input type=\"submit\" name=\"Submit\" value=\"Submit Form\""
        "/></p></form>");
}

int handle_headername(multipart_parser *parser, const char *at, size_t length)
{
    struct parsedata *data = multipart_parser_get_data(parser);
    data->inContentDisposition = !strncmp(at, "Content-Disposition", length);
    return 0;
}

int handle_headervalue(multipart_parser *parser, const char *at, size_t length)
{
    char localfilename[1024];

    struct parsedata *data = multipart_parser_get_data(parser);
    if (data->inContentDisposition)
    {
        char *hdrval = calloc(1, length + 1);
        strncpy(hdrval, at, length);
        if (strtok(hdrval, "; "))
        {
            char *tok;
            while ((tok = strtok(0, "; ")))
            {
                char *rquot;
                if (!strncmp(tok, "name=\"", 6) && 
                        ((rquot = strrchr(tok, '"')) > tok + 6))
                {
                    *rquot = 0;
                    free(data->partname);
                    data->partname = malloc(strlen(tok + 6) + 1);
                    strcpy(data->partname, tok + 6);
                }
                else if (!strncmp(tok, "filename=\"", 10) &&
                        ((rquot = strrchr(tok, '"')) > tok + 10))
                {
                    *rquot = 0;
                    free(data->filename);
                    data->filename = malloc(strlen(tok + 10) + 1);
                    strcpy(data->filename, tok + 10);
                    if (data->saveto) fclose(data->saveto);

                    // determine local location, adapt to your needs:
                    // for production code, ADD SANITY CHECKS, the following code
                    // allows an attacker to write any location of your server
                    // with a filename containing relative paths!
                    snprintf(localfilename, 1024, "uploads/%s", data->filename);
                    data->saveto = fopen(localfilename, "w");
                }
            }
        }
        free(hdrval);
    }
    return 0;
}

int handle_contentdata(multipart_parser *parser, const char *at, size_t length)
{
    struct parsedata *data = multipart_parser_get_data(parser);

    // only handle file upload of field "photo"
    // you have to extend this to get the values of other form fields
    if (data->partname && data->filename && !strcmp(data->partname, "photo"))
    {
        fwrite(at, length, 1, data->saveto);
    }

    return 0;
}

char *upload(void)
{
    // can only upload with POST
    const char *method = getenv("REQUEST_METHOD");
    if (!method || strcmp(method, "POST")) return 0;

    // check for multipart/form-data and extract boundary if present
    char boundary[128] = "--"; // boundary starts with double dash
    const char *conttype = getenv("CONTENT_TYPE");
    if (!conttype || sscanf(conttype,
                "multipart/form-data; boundary=%125s", boundary+2)
            < 1) return 0;

    // see https://github.com/iafonov/multipart-parser-c
    multipart_parser_settings callbacks = {0};
    callbacks.on_header_field = handle_headername;
    callbacks.on_header_value = handle_headervalue;
    callbacks.on_part_data = handle_contentdata;

    struct parsedata data = {0};

    multipart_parser *parser = multipart_parser_init(boundary, &callbacks);
    multipart_parser_set_data(parser, &data);

    // read body from stdin:
    char reqdata[64 * 1024];
    size_t length;
    while ((length = fread(reqdata, 1, 64 * 1024, stdin)) > 0)
    {
        // and feed it to the parser:
        multipart_parser_execute(parser, reqdata, length);
    }

    multipart_parser_free(parser);

    free(data.partname);
    if (data.filename && data.saveto)
    {
        fclose(data.saveto);
        return data.filename;
    }
    free(data.filename);

    return 0;
}

int main(void)
{
    char *uploaded = upload();

    puts("Content-Type: text/html\n");
    puts("<html><head><title>Test</title></head><body>");

    if (uploaded)
    {
        printf("<b>%s</b> uploaded.", uploaded);
        free(uploaded);
    }
    else
    {
        showForm();
    }

    puts("</body></html>");
}

Upvotes: 3

Related Questions