[En-Nut-Discussion] Send File via HTTP POST request

Ole Reinhardt ole.reinhardt at embedded-it.de
Tue Jan 19 23:15:06 CET 2010


Hi!

> i need to send a file to NutOS via HTTP Post request. Therefore i need to
> specify the "multipart/form-data" enctype in my html form. Unfortunately
> NutOS won't understand that enctype and NutHttpGetParameterCount returns 0
> :(
> How can i send a file to NutOS via HTTP POST request? Any ideas?

As Andras just said, you have to parse the multipart form data yourself.
Assuming your website is working correct (correct upload form), here is
a CGI code which decodes this multipart section and extracts you the
file.

Be aware this is _NO_ NutOS Code!!! Indeed it's quite similar in its
behaviour as the program was ported from NutOS to Linux, but it is using
the FastCGI Interface with an Apache Webserver. In addition to the
libfastcgi I've created some more helper functions that are used here
too. There are also some glib functions and data types used. But at
least the datatypes and functions beginning with a 'g' are almost the
same like there standard libc counterparts.

So this isn't a fully working source code, but shall give you an idea
how to decode the multipart section and how to answer to the post
request.

You should ignote the INFO(), LOG() and status_insert_text() functions.
This ist just debug code.

How it works:

First we query the content length from the fcgi request header. You can
query it from the HTTP header send by the browser. It gives you the
complete length of the whole data section including the multipart
boundary strings!

Next the content type is checked and we just proceed if it's
"multipart/form-data"

The boundary string is append right at the end of the content type.
We'll need this string to seperate the following section(s).

Next we read the incomming data using fcgi_getstr(). This is your "post"
data.

Then it's checked if the read data has the same size as the
CONTENT_LENGTH header value. Just a sanity check.

Now the first occurence of the boundary string is searched using
"find_pattern_in_buffer()"

Each section has it's own header again! So this header will be skipped.
Next follows the data and will be closed by the boundary string again.

That's it...

How I could help you even if it's not ready to use NutOS code.

Bye,

Ole




#define CONTENT_TYPE_MULTIPART_FORM_DATA_BOUNDARY "multipart/form-data;
boundary="

#define LENGTH_NAME 64
#define LENGTH_FILENAME 128

gint find_pattern_in_buffer(guchar *buffer, gint length, guchar
*pattern, gint pattern_length) 
{    
    gint idx;
    gint pattern_idx = 0;

    for (idx = 0; idx < length - pattern_length; idx ++) {
        if (buffer[idx] != pattern[0]) {
            continue;
        }

        for (pattern_idx = 0; pattern_idx < pattern_length; pattern_idx
++) {
            if (buffer[idx + pattern_idx] != pattern[pattern_idx]) {
                break;
            }
        }
        if (pattern_idx == pattern_length) {
            return idx;
        }
    }

    return -1;
}

gint CGI_FirmwareUpload(FCGX_Request *cgiRequestInfo, THTTPRequestMethod
request_method, gpointer userdata)
{              
    time_t  now;
    struct  tm *lot;
    gchar  *end_ptr;
    gchar  *content_length_str;
    size_t  content_length;
    guchar  *data;
    
    now = time(0);
    lot = localtime(&now);

    /* This CGI works a little bit different, the form data is not send
url-encoded but as multipart/form-data */
    /* So we first have to parse the multipart encoded data and assume
that the first field is our file */
    /* Everything else is discarded */
    
    if (request_method == HTTP_REQUEST_METHOD_POST) {
        gchar  *version = NULL;
        gboolean update_ok = FALSE;
        
        /* Assume we use Apache, which has a parameter "CONTENT_LENGTH"
*/
        content_length_str = fcgi_get_param(cgiRequestInfo,
"CONTENT_LENGTH");
        if (content_length_str == NULL) {
            /* This one is the lighthttpd variant */
            content_length_str = fcgi_get_param(cgiRequestInfo,
"HTTP_CONTENT_LENGTH");
        }
        if (content_length_str != NULL) {
            content_length = g_strtod(content_length_str, &end_ptr);
            if (end_ptr == content_length_str) {
                content_length = 0;
            }
        } else {
            content_length = 0;
        }
        
        /* Ok, we got content. Now decode the first multipart section */
        if (content_length != 0) {
            gchar  *content_type;
            gchar  *boundary = NULL;
            gsize   real_length;
            gint    offset;
            gint    start = 0;
            gint    end   = 0;
            guchar *buffer = NULL;
            gint    buffer_size;

            /* Check if the content type is realy is multipart/form-data
*/
            content_type = fcgi_get_param(cgiRequestInfo,
"CONTENT_TYPE");
            if (content_type != NULL) {
                if (strncmp(content_type,
CONTENT_TYPE_MULTIPART_FORM_DATA_BOUNDARY,
strlen(CONTENT_TYPE_MULTIPART_FORM_DATA_BOUNDARY)) == 0)

                if (g_str_has_prefix(content_type,
CONTENT_TYPE_MULTIPART_FORM_DATA_BOUNDARY)) {
                    boundary =
g_strdup(&content_type[strlen(CONTENT_TYPE_MULTIPART_FORM_DATA_BOUNDARY)]);
                }
            }

            /* Alloc content_length + 1 bytes, to add a trailing 0 at
the end */
            data = g_malloc(content_length + 1);
            real_length = fcgi_getstr((gchar*)data, content_length,
cgiRequestInfo);
            /* Check if we realy got enought content */
            if (real_length == content_length) {
                /* Append 0 character to the end */
                data[content_length] = 0;

                /* Find the first boundary */
                offset = find_pattern_in_buffer(data, content_length,
(guchar*)boundary, strlen(boundary));
                
                if (offset >= 0) {
                    start = offset + strlen(boundary);
                }

                /* Find the next boundary, which marks the end */
                if (start >= 0) {
                    offset = find_pattern_in_buffer(&data[start],
content_length - start, (guchar*)boundary, strlen(boundary));
                    if (offset >= 0) {
                        end = offset + start;
                    }
                }

                /* Sanity check before continue */
                if ((start > 0) && (start < end)) {
                    /* Skip the header part of the first multipart
section */
                    offset = find_pattern_in_buffer(&data[start],
content_length - start, (guchar*)"\r\n\r\n", 4);
                    if (offset < 0) {
                        offset = find_pattern_in_buffer(&data[start],
content_length - start, (guchar*)"\n\n", 2);
                        offset += 2;
                        /* skip "\n--" at the end */
                        end -= 3;
                    } else {
                        offset += 4;
                        /* skip "\r\n--" at the end */
                        end -=4;
                    }

                    /* Calculate the new start position */
                    start += offset;

                    /* And now the correct buffer size */
                    buffer_size = end - start;

                    if (start + buffer_size < content_length) {

                        buffer = g_memdup(&data[start], buffer_size);
                        now = time(0);
                        lot = localtime(&now);

                        if (check_and_install_update_file ((TUpdateFile
*) buffer, buffer_size, &version) != 0) {
                            sprintf(out, "%04u.%02u.%02u %02u:%02u:%02u
Update: FAILED\n", 
                                    lot->tm_year + 1900, lot->tm_mon +
1, lot->tm_mday, lot->tm_hour, lot->tm_min, lot->tm_sec);
                            status_insert_text(out);
                            INFO(LOG_ERROR, out);
                        } else {
                            sprintf(out, "%04u.%02u.%02u %02u:%02u:%02u
Update to %s %s successfully\n", 
                                    lot->tm_year + 1900, lot->tm_mon +
1, lot->tm_mday, lot->tm_hour, lot->tm_min, lot->tm_sec, PRODUCT,
version);
                            status_insert_text(out);
                            INFO(LOG_INFO, out);
                            update_ok = TRUE;
                        }
                    }
                }
            }
            
            g_free(buffer);
            g_free(boundary);
            g_free(data);
        }

        fcgi_send_http_header(cgiRequestInfo, 200,
HTTP_CONTENT_TYPE_TEXT_HTML, NULL); 

        if (update_ok) {
            gchar *message =
g_strdup_printf(text_message_page_notice_installed, PRODUCT, version);
            ShowMessagePage(cgiRequestInfo, text_message_page_installed,
message, 90, "/index.shtml"); 
            g_free(message);
            fcgi_flush(cgiRequestInfo);
            system_reboot();
        } else {
            ShowMessagePage(cgiRequestInfo, text_message_page_error,
text_message_page_error_notice, 0, "");   
            fcgi_flush(cgiRequestInfo);
        }
        g_free(version);
        return 0; 
    } else {
        /* Nothing done. Tell the browser to load the original
maintenance site again. */
        /* Use http 1.1 code 307 to do the trick! */
        fcgi_send_http_header(cgiRequestInfo, 307,
HTTP_CONTENT_TYPE_TEXT_HTML, "Location: /admin/maintenance.shtml");
        fcgi_flush(cgiRequestInfo);        
        return 0;
    }
}




-- 

Thermotemp GmbH, Embedded-IT

Embedded Hard-/ Software and Open Source Development, 
Integration and Consulting

Geschäftsstelle Siegen - Steinstraße 67 - D-57072 Siegen - 
tel +49 (0)271 5513597, +49 (0)271-73681 - fax +49 (0)271 736 97

Hauptsitz - Hademarscher Weg 7 - 13503 Berlin
Tel +49 (0)30 4315205 - Fax +49 (0)30 43665002
Geschäftsführer: Jörg Friedrichs, Ole Reinhardt
Handelsregister Berlin Charlottenburg HRB 45978 UstID DE 156329280 




More information about the En-Nut-Discussion mailing list