HTTP POST for file upload

Discussion in 'Linux Networking' started by Josef Moellers, Jun 5, 2013.

  1. Hi,

    I'm trying to extend an application to upload result files to a web
    server using HTMP POST. By and large it works but the last couple of
    bytes (the last line, to be precise) does not show up in the received
    data. Maybe someone more knowing than I has an idea what I'm doing wrong.

    Thanks in advance,

    Josef


    Here's my code:

    # include <sys/types.h>
    # include <sys/socket.h>
    # include <stdio.h>
    # include <string.h>
    # include <unistd.h>
    # include <netdb.h>
    # include <stdlib.h>
    # include <arpa/inet.h>

    int upload(char *archive_name, char *local_name, char *remote_name, char
    *servname);
    static ssize_t writen(int sd, void *ptr, size_t nbytes);
    static ssize_t readline(int sd, void *ptr, size_t maxlen);

    char servname[] = "de2server";
    char hostname[128];
    /*
    * upload <archive> <file> <remote-name>
    */
    int
    main(int argc, char *argv[])
    {
    if (argc != 4)
    {
    fprintf(stderr, "usage: %s <archive> <file> <remote-name>\n",
    argv[0]);
    exit(1);
    }
    gethostname(hostname, sizeof(hostname));
    upload(argv[1], argv[2], argv[3], servname);
    }

    void
    pr_inet(char **listptr, int length)
    {
    struct in_addr *ptr;
    while ((ptr = (struct in_addr *) *listptr++) != NULL)
    printf(" %s\n", inet_ntoa(*ptr));
    }
    int
    upload(char *archive_name, char *local_name, char *remote_name, char
    *servname)
    {
    int sd;
    struct sockaddr_in servaddr;
    struct hostent *he;
    FILE *src, *tmpfp;
    char buffer[BUFSIZ];
    char boundary[128];
    char line[256];
    size_t nread, content_length;

    if ((he = gethostbyname(servname)) == NULL)
    {
    fprintf(stderr, "Cannot find %s\n", servname);
    return 0;
    }
    pr_inet(he->h_addr_list, he->h_length);

    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
    perror("socket");
    return 0;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(80); /* Web port */
    memcpy(&servaddr.sin_addr, he->h_addr, he->h_length);

    if (connect(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
    perror("connect");
    close(sd);
    return 0;
    }

    /* Let's assemble the body in a file so we can determine the
    Content-Length */
    if ((src = fopen(local_name, "r")) == NULL)
    {
    perror(local_name);
    close(sd);
    return 0;
    }
    if ((tmpfp = tmpfile()) == NULL)
    {
    perror("tmpfile");
    close(sd);
    fclose(src);
    return 0;
    }

    snprintf(boundary, sizeof(boundary),
    "WebKitFormBoundaryePkpFF7tjBAqx29L");
    content_length = 0;
    content_length += fprintf(tmpfp, "\r\n"); /* This belongs to the
    content */
    content_length += fprintf(tmpfp, "------%s\r\n", boundary);
    content_length += fprintf(tmpfp, "Content-Disposition: form-data;
    name=\"archive_name\"\r\n");
    content_length += fprintf(tmpfp, "\r\n");
    content_length += fprintf(tmpfp, "%s\r\n", archive_name);
    content_length += fprintf(tmpfp, "------%s\r\n", boundary);
    content_length += fprintf(tmpfp, "Content-Disposition: form-data;
    name=\"destination_name\"\r\n");
    content_length += fprintf(tmpfp, "\r\n");
    content_length += fprintf(tmpfp, "%s\r\n", remote_name);
    content_length += fprintf(tmpfp, "------%s\r\n", boundary);
    content_length += fprintf(tmpfp, "Content-Disposition: form-data;
    name=\"file_contents\"; filename=\"%s\"\r\n", local_name);
    content_length += fprintf(tmpfp, "Content-Type:
    application/x-object\r\n");
    content_length += fprintf(tmpfp, "\r\n");
    while ((nread = fread(buffer, 1, sizeof(buffer), src)) > 0)
    {
    fwrite(buffer, 1, nread, tmpfp);
    content_length += nread;
    }
    content_length += fprintf(tmpfp, "------%s--\r\n", boundary);
    fprintf(stderr, "content_length=%u, file length=%u\n",
    content_length, ftell(tmpfp));
    fclose(src); /* We don't need that any more */

    /* Now send request */
    snprintf(line, sizeof(line), "POST
    /cgi-bin/zip.pl?upload_progress_id=12344 HTTP/1.1\r\n");
    writen(sd, line, strlen(line));
    snprintf(line, sizeof(line), "Host: %s\r\n", hostname);
    writen(sd, line, strlen(line));
    snprintf(line, sizeof(line), "Content-Length: %u\r\n", content_length);
    writen(sd, line, strlen(line));
    snprintf(line, sizeof(line), "Origin: http://%s\r\n", hostname);
    writen(sd, line, strlen(line));
    snprintf(line, sizeof(line), "Content-Type: multipart/form-data;
    boundary=----%s\r\n", boundary);
    writen(sd, line, strlen(line));
    /* Copy file to sd */
    rewind(tmpfp);
    while ((nread = fread(buffer, 1, sizeof(buffer), tmpfp)) > 0)
    writen(sd, buffer, nread);
    /*
    * Now await response
    */
    while (readline(sd, line, sizeof(line)))
    fprintf(stderr, ">>%s", line);

    close(sd);
    fclose(tmpfp);

    return 1;
    }

    static ssize_t
    writen(int sd, void *ptr, size_t nbytes)
    {
    ssize_t nleft, nwritten;
    fd_set writefds;
    struct timeval tensec;

    for (nleft = nbytes; nleft > 0; )
    {
    /* Wait up to 10s for permission to write, then give up */

    FD_ZERO(&writefds);
    FD_SET(sd, &writefds);
    tensec.tv_sec = 10;
    tensec.tv_usec = 0;
    if (select(sd+1, NULL, &writefds, NULL, &tensec) != 1)
    nwritten = -1;
    else
    nwritten = write(sd, ptr, nleft);

    if (nwritten == -1)
    return -1;
    nleft -= nwritten;
    ptr += nwritten;
    }

    return nbytes - nleft;
    }

    static ssize_t
    readline(int sd, void *ptr, size_t maxlen)
    {
    char *sp = ptr;
    ssize_t rc;
    int n;
    char c;
    fd_set readfds;
    struct timeval tensec;

    for (n = 1; n < maxlen; n++)
    {
    /* Wait up to 10s for data, then give up */

    FD_ZERO(&readfds);
    FD_SET(sd, &readfds);
    tensec.tv_sec = 10;
    tensec.tv_usec = 0;
    if (select(sd+1, &readfds, NULL, NULL, &tensec) != 1)
    rc = 0;
    else
    rc = read(sd, &c, 1);

    if (rc == 1)
    {
    *sp++ = c;
    if (c == '\n')
    break;
    }
    else if (rc == 0) /* EOF */
    {
    if (n == 1)
    return 0;
    else
    break;
    }
    else
    return -1;
    }

    *sp = '\0';

    return n;
    }
    -eoc-
     
    Josef Moellers, Jun 5, 2013
    #1
    1. Advertisements

  2. Josef Moellers

    Xavier Roche Guest

    Le 05/06/2013 19:40, Josef Moellers a écrit :
    No, this belongs to the HTTP headers.

    <http://www.ietf.org/rfc/rfc2616.txt>
    (...)
    4 HTTP Message

    4.1 Message Types
    (...)
    Request (section 5) and Response (section 6) messages use the generic
    message format of RFC 822 [9] for transferring entities (the payload
    of the message). Both types of message consist of a start-line, zero
    or more header fields (also known as "headers"), an empty line (i.e.,
    a line with nothing preceding the CRLF) indicating the end of the
    header fields, and possibly a message-body.
     
    Xavier Roche, Jun 5, 2013
    #2
    1. Advertisements

  3. Have you considered using a library such as cURL?
    No it doesn’t...
     
    Richard Kettlewell, Jun 5, 2013
    #3
  4. I found the solution in RFC 1341
    (<http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html>:

    7.2.1 Multipart: The common syntax
    :
    Note that the encapsulation boundary must occur at the beginning of a
    line, i.e., following a CRLF, and that that initial CRLF is considered
    to be part of the encapsulation boundary rather than part of the
    preceding part.
    :

    So I changed my code to
    Apparently the Perl CGI code was tolerant and took the "}\n" as the CRLF
    preceding the last boundary and dropped that!

    Another problem, not related to this one, was that I had to explicitly
    close the write side of the socket in order to reliably receive the
    response.

    Thanks for both who helped (and all who thought about the issue).

    Josef
     
    Josef Moellers, Jun 6, 2013
    #4
  5. Josef Moellers

    Jorgen Grahn Guest

    I thought briefly about it, but ...

    It's easier (for others) to look at this kind of problem in terms of
    what is actually transmitted and received on the TCP stream level in
    an example run: any mistake in your code will end up as a protocol
    violation there -- and we don't have to decipher your C code.

    You can use tcpdump, tcpflow and similar tools to capture that data.

    /Jorgen
     
    Jorgen Grahn, Jun 6, 2013
    #5
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.