After spending days hitting my head to the wall I thought I'd ask here.
The problem with the below code is that I'm basically iterating a directory, uploading the files there. All the files are small, ~1KB in size so this is not a size issue. The first upload goes through like a charm, all subsequent calls are broken with winhttp sending only the headers.
Here is the code:
BOOL NetworkManager::UploadFileToServer(wchar_t *pszURL, wchar_t *pszFilePath, wchar_t *_pszProxyAddress, wchar_t *pszServerAddress)
{
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
BOOL bResults;
DWORD dwSize = 0;
DWORD dwContentLength = 0;
LPCWSTR pszProxyAddress = 0;
wchar_t wszContentLength[256] = { 0 };
pszProxyAddress = _pszProxyAddress;
printf("Trying to send %S\r\n", pszFilePath);
if(pszProxyAddress != NULL && wcslen(pszProxyAddress) < 4)
{
pszProxyAddress = NULL;
}
HANDLE hFile = CreateFile(pszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
printf("UM: Unable to open the file for sending, aborting...\r\n");
return FALSE;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
// Use WinHttpOpen to obtain a session handle.
if(pszProxyAddress == NULL)
{
hSession = WinHttpOpen( L"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
NULL,
WINHTTP_NO_PROXY_BYPASS, 0);
}
else
{
hSession = WinHttpOpen( L"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
WINHTTP_ACCESS_TYPE_NAMED_PROXY,
pszProxyAddress,
WINHTTP_NO_PROXY_BYPASS, 0);
}
// Specify an HTTP server.
if (hSession)
{
hConnect = WinHttpConnect( hSession, _pszServerAddress,
INTERNET_DEFAULT_HTTPS_PORT, 0);
}
else
{
printf("hSession failed, errorcode 0x%08x\r\n", GetLastError());
return FALSE;
}
// Create an HTTP request handle.
if (hConnect)
{
hRequest = WinHttpOpenRequest( hConnect, L"POST", L"upload.php",
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
}
else
{
printf("hConnect failed, errorcode 0x%08x\r\n", GetLastError());
WinHttpCloseHandle(hSession);
return FALSE;
}
PHEAP_BUFFER pBuf = NULL;
DWORD dwBytesWritten = 0;
// Send a request.
if (hRequest)
{
DWORD options = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA ;
bResults = WinHttpSetOption( hRequest, WINHTTP_OPTION_SECURITY_FLAGS , (LPVOID)&options, sizeof (DWORD) );
WinHttpAddRequestHeaders(hRequest, L"Content-Type: multipart/form-data; boundary=----BoundaryXu02", (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD);
dwContentLength = strlen(pszFormHeader) + dwFileSize + strlen(pszFinalBoundary);
DWORD dwTotalSent = 0;
pBuf = MemoryManager::AllocateHeapMemory(dwContentLength, 1);
DWORD dwBytesRead = 0;
strcat_s((PCHAR)pBuf->pBuffer, pBuf->dwBufferSize, pszFormHeader);
ReadFile(hFile, &pBuf->pBuffer[strlen(pszFormHeader)], dwFileSize, &dwBytesRead, NULL);
memcpy(&pBuf->pBuffer[strlen(pszFormHeader) + dwFileSize], pszFinalBoundary, strlen(pszFinalBoundary));
wsprintf(wszContentLength, L"Content-Length: %d", dwContentLength);
bResults = WinHttpSendRequest( hRequest, wszContentLength, -1, 0, 0, dwContentLength, 0);
printf("Sending out the request\r\n");
WinHttpWriteData(hRequest, pBuf->pBuffer, pBuf->dwBufferSize, &dwBytesWritten);
}
else
{
printf("hRequest failed, errorcode 0x%08x\r\n", GetLastError());
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hConnect);
return FALSE;
}
//WinHttpWriteData(hRequest, pBuf->pBuffer, pBuf->dwBufferSize, &dwBytesWritten);
// End the request.
if (bResults)
{
bResults = WinHttpReceiveResponse( hRequest, NULL);
}
else
{
printf("hResults failed, errorcode 0x%08x\r\n");
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
MemoryManager::FreeHeapMemory(pBuf);
return FALSE;
}
WinHttpQueryDataAvailable(hRequest, &dwBytesWritten);
// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
MemoryManager::FreeHeapMemory(pBuf);
CloseHandle(hFile);
DeleteFile(pszFilePath);
return TRUE;
}
This is from the server access_log:
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:33 +0200] "POST /upload.php HTTP/1.1" 200 1811 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:33 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:33 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:33 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:33 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
xxx.xxx.xxx.244 - - [09/Jan/2014:16:39:34 +0200] "POST /upload.php HTTP/1.1" 200 295 "-" "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
I'm completely lost as to why in the world the first POST succeeds but the rest go to sh*t :(
Edit:
Forgot to add the header declarations:
char *pszFormHeader = "------BoundaryXu02\r\nContent-Disposition: form-data; name=\"uploaded\"; filename=\"aviconv.dat\"\r\nContent-Type: application/octet-stream\r\n\r\n";
char *pszFinalBoundary = "\r\n------BoundaryXu02--\r\n";
char *pwzContentHeader = "Content-Type: multipart/form-data; boundary=----BoundaryXu02";
wchar_t wszContentLength[256] = { 0 };
All right, so finally figured out the issues that the code had.
1) In order to prevent potential proxies interfering with the uploads use WINHTTP_FLAG_REFRESH when calling WinHttpOpenRequest
2) Since in this code you are creating new connections for every upload it seems that some servers get confused since the default for WinHTTP is to use "Connection: keep-alive" in the headers. Adding "Connection: close" solves that issue.
3) Seems that calling WinHttpReceiveResponse and WinHttpQueryDataAvailable was not enough. After adding WinHttpReadData call to the end things were starting to act normally.
4) To ensure that the upload uses a different boundary each time I added the CurrentTickCount call. Just to make sure a silly proxy along the way doesn't think "I've seen this before"
Below is a functional code for uploading files to a php script through SSL (HTTPS). I've stripped down the calls that refer to other components in my project and replaced them with the normal C equivalents.
I hope this is of help to others who wrestle with WinHTTP :)
Also, please note that the function deletes the files after sending so don't just copy/paste and test on c:\windows\system32\kernel32.dll...
DISCLAIMER: I do know that the code still has room for improvements, specifically on some of the unsafer functions calls plus the fact that if the file size is larger than a DWORD you'll get issues. Then again, on the other hand WinHTTP will barf on uploads that big anyways if you try to send them like this.
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#include <winhttp.h>
char *pszFormHeader = "------Boundary%08X\r\nContent-Disposition: form-data; name=\"uploaded\"; filename=\"%08x.dat\"\r\nContent-Type: application/octet-stream\r\n\r\n";
char *pszFinalBoundary = "\r\n------Boundary%08X--\r\n";
wchar_t *wszContentHeader = L"Content-Type: multipart/form-data; boundary=----Boundary%08X";
wchar_t wszContentLength[256] = { 0 };
#define BUFFER_SIZE_1KB 1024
BOOL UploadFileToServer(wchar_t *wszURL, wchar_t *wszFilePath, wchar_t *_wszProxyAddress, wchar_t *_wszServerAddress);
int main(int argc, char **argv)
{
UploadFileToServer(L"upload.php", L"c:\\sample1.txt", NULL, L"www.example.com");
UploadFileToServer(L"upload.php", L"c:\\sample2.txt", NULL, L"www.example.com");
}
BOOL UploadFileToServer(wchar_t *wszURL, wchar_t *wszFilePath, wchar_t *_wszProxyAddress, wchar_t *_wszServerAddress)
{
/*
* Declarations and initializations
*/
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
BOOL bResults;
DWORD dwSize = 0;
DWORD dwContentLength = 0;
DWORD dwBytesToRead = 0;
DWORD dwBytesRead = 0;
DWORD dwBytesWritten = 0;
DWORD dwBoundaryValue = 0;
DWORD dwFileSize = 0;
DWORD options = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA ;
LPCWSTR wszProxyAddress = 0;
wchar_t wszContentLength[256] = { 0 };
PUCHAR pResponse = 0;
PCHAR pFormHeader = 0;
PCHAR pFinalBoundary = 0;
PUCHAR pBuf = 0;
wchar_t *pContentHeader = 0;
/*
* Preparations, set up the content headers
*/
pFormHeader = (PCHAR) calloc(BUFFER_SIZE_1KB, 1);
pFinalBoundary = (PCHAR) calloc(BUFFER_SIZE_1KB, 1);
pContentHeader = (wchar_t *) calloc(BUFFER_SIZE_1KB, 1);
dwBoundaryValue = GetTickCount();
wszProxyAddress = _wszProxyAddress;
sprintf_s(pFormHeader, BUFFER_SIZE_1KB, pszFormHeader, dwBoundaryValue, dwBoundaryValue);
sprintf_s(pFinalBoundary, BUFFER_SIZE_1KB, pszFinalBoundary, dwBoundaryValue);
wsprintf(pContentHeader, wszContentHeader, dwBoundaryValue);
if(wszProxyAddress != NULL && wcslen(wszProxyAddress) < 4)
{
wszProxyAddress = NULL;
}
HANDLE hFile = CreateFile(wszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
printf("Unable to open the file for sending, aborting...\r\n");
return FALSE;
}
dwFileSize = GetFileSize(hFile, NULL);
if(dwFileSize == 0)
{
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
printf("The file is 0 bytes in size, cannot send it\r\n");
return FALSE;
}
if(wszProxyAddress == NULL)
{
hSession = WinHttpOpen( L"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
NULL,
WINHTTP_NO_PROXY_BYPASS, 0);
}
else
{
hSession = WinHttpOpen( L"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
WINHTTP_ACCESS_TYPE_NAMED_PROXY,
wszProxyAddress,
WINHTTP_NO_PROXY_BYPASS, 0);
}
if (hSession)
{
hConnect = WinHttpConnect( hSession, _wszServerAddress,
INTERNET_DEFAULT_HTTPS_PORT, 0);
}
else
{
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
printf("hSession failed, errorcode 0x%08x\r\n", GetLastError());
return FALSE;
}
if (hConnect)
{
hRequest = WinHttpOpenRequest( hConnect, L"POST", wszURL,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE | WINHTTP_FLAG_REFRESH);
}
else
{
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
printf("hConnect failed, errorcode 0x%08x\r\n", GetLastError());
WinHttpCloseHandle(hSession);
return FALSE;
}
if (hRequest)
{
bResults = WinHttpSetOption( hRequest, WINHTTP_OPTION_SECURITY_FLAGS , (LPVOID)&options, sizeof (DWORD) );
WinHttpAddRequestHeaders(hRequest, pContentHeader, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD);
WinHttpAddRequestHeaders(hRequest, L"Connection: close", (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
dwContentLength = strlen(pFormHeader) + dwFileSize + strlen(pFinalBoundary);
pBuf = (PUCHAR)calloc(dwContentLength, 1);
strcat_s((PCHAR)pBuf, dwContentLength, pFormHeader);
ReadFile(hFile, &pBuf[strlen(pFormHeader)], dwFileSize, &dwBytesRead, NULL);
memcpy(&pBuf[strlen(pFormHeader) + dwFileSize], pFinalBoundary, strlen(pFinalBoundary));
wsprintf(wszContentLength, L"Content-Length: %d", dwContentLength);
bResults = WinHttpSendRequest( hRequest,
wszContentLength,
-1, pBuf, dwContentLength,
dwContentLength, 0);
}
else
{
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
printf("hRequest failed, errorcode 0x%08x\r\n", GetLastError());
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hConnect);
return FALSE;
}
if (bResults)
{
bResults = WinHttpReceiveResponse( hRequest, NULL);
}
else
{
printf("hResults failed, errorcode 0x%08x\r\n");
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
free(pBuf);
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
return FALSE;
}
WinHttpQueryDataAvailable(hRequest, &dwBytesToRead);
pResponse = (PUCHAR)calloc(dwBytesToRead, 1);
WinHttpReadData(hRequest, pResponse, dwBytesToRead, &dwBytesRead);
free(pResponse);
free(pFormHeader);
free(pFinalBoundary);
free(pContentHeader);
// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
free(pBuf);
CloseHandle(hFile);
DeleteFile(wszFilePath);
return TRUE;
}