Sending multipart POST requests with C++

cpp

Finally I had the time to revisit it again. Many moons ago I tried to understand how Multipart file upload works and kinda didn’t succeed, but then again I didn’t put that much effort into it. I did it now because I have a project on my hands which requires this, whole theory didn’t seem very hard and I was able to produce working code for client and server sides in a few hours.

Sending multipart requests is great for file uploading and other large quantities of data. I won’t go about and write a tutorial on how to send these requests, instead I will point you to an awesome RFC and this tutorial.

While browsing I had found a working example done in Python. Cannot remember where I found it or to who it belongs, but anyway, that example code can be found in my projects folder here.

And so based on the docs and that example code I was able to produce a CPP project for my own needs. I tried to make it very simple to adapt for other projects. The project can be found here, and along with it I made a simple PHP script that receives the commands and acts upon them, it’s also a part of other bigger project…

For those who only want a quick review, here’s my C++ code. C++ and PHP codes assume your data is already Base64 encoded:

/*
    Author: Kulverstukas
    Date: 2014.02.23
    Website: http://9v.lt; Evilzone.org
    Description:
        Simple prototype of Multipart POST sending in C++
*/
 
#include <iostream>
#include <winsock2.h>
#include <string>
#include <fstream>
#include "windows.h"
#include "stdio.h"
 
using namespace std;
 
#define PORT       80
#define IP         "127.0.0.1"
#define HOST       "locahost"
#define RECEIVER   "/receive_data.php"
#define COMPNAME   "compname"
#define PROGRAM    "program"
#define FILENAME   "file"
#define BOUNDARY   "----------Evil_B0uNd4Ry_$"
#define DUMMY_DATA "c2FzYXNhc2FzZGRmZGZkY2Q="
#define DUMMY_FILE "dummy.txt"
 
//------------------------------------
string constructBody(string args[2], string file[2]);
string readFile(string fileName);
//------------------------------------
 
int main() {
    // initiate the socket!
    SOCKET dataSock;
    WSADATA wsaData;
    int error = WSAStartup(0x0202, &wsaData);
    if (error != 0) {
        WSACleanup();
        exit(1);  // oh shit, this shouldn't happen!
    }
    // all internets, engage!
    SOCKADDR_IN target;
    target.sin_family = AF_INET;
    target.sin_port = htons(PORT);
    target.sin_addr.s_addr = inet_addr(IP);
    dataSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (dataSock == INVALID_SOCKET) {
        exit(1); // Houston, we have a problem!
    }
    connect(dataSock, (SOCKADDR*)&target, sizeof(target));
 
    string programNames[5][2] = {{"KULVERTOP", "Chrome"}, {"KULVERTOP", "Firefox"}, {"KULVERTOP", "InternetExplorer"}, {"KULVERTOP", "Opera"}, {"KULVERTOP", "Skype"}};
    string file[2] = {FILENAME, "Default.txt"};
 
    int a = sizeof(programNames)/sizeof(programNames[0]);
    for (int i = 0; i < a; i++) {
        printf("Sending data for %s\n", (programNames[i][1]).c_str());
        string body = constructBody(programNames[i], file);
        char header[1024];
        sprintf(header, "POST %s HTTP 1.1\r\n"
                        "Host: %s\r\n"
                        "Content-Length: %d\r\n"
                        "Connection: Keep-Alive\r\n"
                        "Content-Type: multipart/form-data; boundary=%s\r\n"
                        "Accept-Charset: utf-8\r\n\r\n", RECEIVER, IP, strlen(body.c_str()), BOUNDARY);
//        printf("%s\n\n", header);
        int p = send(dataSock, header, strlen(header), 0);
//        printf("p == %d\n", p);
        int k = send(dataSock, body.c_str(), strlen(body.c_str()), 0);
//        printf("k == %d\n", k);
 
//        char buff[1024];
//        recv(dataSock, buff, 1024, 0);
//        printf("%s\n\n", buff);
    }
 
    closesocket(dataSock);
    WSACleanup();
}
 
string readFile(string fileName) {
    string fileContents;
    ifstream tmp(fileName.c_str());
    getline(tmp, fileContents);
    tmp.close();
 
    return fileContents;
}
 
 
string constructBody(string args[2], string file[2]) {
    string body;
    string CRLF = "\r\n";
 
    // first we add the args
    body.append("--"+string(BOUNDARY)+CRLF);
    body.append("Content-Disposition: form-data; name=\""+string(COMPNAME)+"\""+CRLF);
    body.append(CRLF);
    body.append(args[0]+CRLF);
    body.append("--"+string(BOUNDARY)+CRLF);
    body.append("Content-Disposition: form-data; name=\""+string(PROGRAM)+"\""+CRLF);
    body.append(CRLF);
    body.append(args[1]+CRLF);
 
    // now we add the file
    body.append("--"+string(BOUNDARY)+CRLF);
    body.append("Content-Disposition: form-data; name=\""+string(FILENAME)+"\"; filename=\""+string(DUMMY_FILE)+"\""+CRLF);
    body.append("Content-Type: text/plain"+CRLF);
    body.append(CRLF);
    body.append(DUMMY_DATA+CRLF);
    body.append("--"+string(BOUNDARY)+"--"+CRLF);
    body.append(CRLF);
 
//    printf(body.c_str()); exit(0);
 
    return body;
}

Python code:

import httplib, mimetypes

def post_multipart(host, selector, fields, files):
    """
    Post fields and files to an http host as multipart/form-data.
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return the server's response page.
    """
    content_type, body = encode_multipart_formdata(fields, files)
    h = httplib.HTTP(host)
    h.putrequest('POST', selector)
    h.putheader('content-type', content_type)
    h.putheader('content-length', str(len(body)))
    h.endheaders()
    h.send(body)
    errcode, errmsg, headers = h.getreply()
    return h.file.read()

def encode_multipart_formdata(fields, files):
    """
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (content_type, body) ready for httplib.HTTP instance
    """
    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"' % key)
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
        L.append('Content-Type: %s' % get_content_type(filename))
        L.append('')
        L.append(value)
    L.append('--' + BOUNDARY + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body

def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

And the PHP code that receives all your garbage:

'; // echo 'oh no :('; } //===================================== ?>

10 comments

  1. Manuel says:

    THANK YOU!, After spending some hours searching, I think that this is BY FAR one of the best examples of HTTP POST in C++. Cheers!, nice code, and best of all short and very clear

  2. amir says:

    Hello
    Great tutorial. I wonder if you could answer my question.
    what if we want to send and image file instead of text file?

    best regards.

  3. Kulverstukas says:

    Then you change the MIME type, read bytes from file and output them into the stream, probably should encode the bytes with Base64.

  4. amir says:

    Dear Kulverstukas
    Thank you for your respond. I did so but i get vector overflow.
    this is the post i tried and many other posts.
    http://stackoverflow.com/questions/29678661/send-image-in-http-post-via-json-object-using-cppresti

    I really don’t know what to try. I’m pulling my hair since last week but no result.
    Please help me. I created my web service with Node JS and its working like a charm. i have an android program witch is working with web service so my server side is really working fine.
    but C++ makes me pulling hair. :((((((((((((( I wonder if you could show me true path.

    Best regards

  5. amir says:

    this is the last code i try:

    string readFile(string fileName) {
    	std::ifstream InFile( fileName, std::ifstream::binary );
    	std::vector data( ( std::istreambuf_iterator( InFile ) ), std::istreambuf_iterator());
     
    	std::string Code = base64_encode((unsigned char*)&amp;data[0], (unsigned int)data.size());
     
        return Code;
    }
     
    string constructBody(string pid,string fname,string lname, string image) {
        string body;
        string CRLF = "\r\n";
     
        // first we add the args
    	body.append("--"+string(BOUNDARY)+CRLF);
        body.append("Content-Disposition: form-data; name=\""+string(PID)+"\""+CRLF);
        body.append(CRLF);
        body.append(pid+CRLF);
        body.append("--"+string(BOUNDARY)+CRLF);
        body.append("Content-Disposition: form-data; name=\""+string(FNAME)+"\""+CRLF);
        body.append(CRLF);
        body.append(fname+CRLF);
        body.append("--"+string(BOUNDARY)+CRLF);
        body.append("Content-Disposition: form-data; name=\""+string(LNAME)+"\""+CRLF);
        body.append(CRLF);
        body.append(lname+CRLF);
     
        // now we add the file
        body.append("--"+string(BOUNDARY)+CRLF);
        body.append("Content-Disposition: form-data; name=\""+string(IMAGE)+"\"; image=\""+string(image)+"\""+CRLF);
        body.append("Content-Type: text/plain"+CRLF);
        body.append(CRLF);
        body.append(readFile(DUMMY_FILE)+CRLF);
        body.append("--"+string(BOUNDARY)+"--"+CRLF);
        body.append(CRLF);
     
        return body;
    }

    and i get this error at runtime:
    http://i57.tinypic.com/suwl8p.png

  6. Kulverstukas says:

    That is a weird issue. Maybe the file is too big, try to read in chunks and output directly into the stream, instead of putting everything into a vector.

  7. amir says:

    The file is only 90KB.
    how would i do that when i want to add it as a body part? Its very nice of you if you show me what to do, I don’t understand what to do.

    Thank you.

  8. Kulverstukas says:

    Well you might have more luck asking such questions on Stackoverflow, you’d get much more help.

  9. amir says:

    Well Thank you Kulverstukas.
    I did figure it out. I used char array instead of vector and i did exactly as you mentioned above.

    Best regards.

  10. David says:

    Thanks very much. Save my day.

Leave a Reply

Your email address will not be published. Required fields are marked *