How to download a file with WinHTTP in C/C++?
Looks like this thread on MSDN is the same and has the solution
http://social.msdn.microsoft.com/forums/en-US/vclanguage/thread/45ccd91c-6794-4f9b-8f4f-865c76cc146d
C++ Download file from internet with Winhttp
You should try to read the API or a tutorial
Downloading an executable (.exe) in C using WinHTTP
StringCchCatA()
operates on null-terminated strings, but an EXE file is not textual data, it is binary data, and will have 0x00
bytes in it, which would cause StringCchCatA
to truncate data.
If the file you are downloading is more than 20000 bytes, you are going to have to expand your buffer once you have filled it up to its max capacity. Unless you are downloading small files, you should generally use a fixed-sized buffer (the WinHttpReadData()
documentation suggests 8KB), and just reuse and append that buffer to a temp file on each loop iteration. WinHttpReadData()
tells you how many bytes are in the buffer after each read.
You are also not checking the return values of WinHttpQueryDataAvailable()
or WinHttpReadData()
for failure to break the download loop if needed.
Try something more like this instead:
...
DWORD dwFileCap = 20000;
DWORD dwFileSize = 0;
DWORD dwReadSize = 8192;
DWORD dwAvailableSize = 0;
DWORD dwDownloaded = 0;
// TODO: create a file instead...
LPSTR fileData = (LPSTR) GlobalAlloc(GMEM_FIXED, dwFileCap);
if (!fileData)
{
// error handling ...
return NULL;
}
LPSTR readBuffer = (LPSTR) GlobalAlloc(GMEM_FIXED, dwReadSize);
if (!readBuffer)
{
// error handling and cleanup ...
return NULL;
}
LPCWSTR accept[2] = { L"application/octet-stream", NULL };
HINTERNET hSession = WinHttpOpen(userAgent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession)
{
// error handling and cleanup ...
return NULL;
}
hConnect = WinHttpConnect(hSession, domain, INTERNET_DEFAULT_HTTP_PORT, 0);
if (!hConnect)
{
// error handling and cleanup ...
return NULL;
}
hRequest = WinHttpOpenRequest(hConnect, L"GET", pathL, NULL, WINHTTP_NO_REFERER, accept, NULL);
if (!hRequest)
{
// error handling and cleanup ...
return NULL;
}
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
{
// error handling and cleanup ...
return NULL;
}
if (!WinHttpReceiveResponse(hRequest, NULL))
{
// error handling and cleanup ...
return NULL;
}
bool done = false;
do
{
dwAvailableSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwAvailableSize))
{
// error handling and cleanup ...
return NULL;
}
do
{
if (!WinHttpReadData(hRequest, readBuffer, min(dwAvailableSize, dwReadSize), &dwDownloaded))
{
// error handling and cleanup...
return NULL;
}
if (dwDownloaded == 0)
{
done = true;
break;
}
// TODO: if using a file instead, ignore this part ...
if ((dwFileSize + dwDownloaded) > dwFileCap)
{
DWORD newCapacity = double(dwFileCap) * 1.5;
LPSTR newFileData = (LPSTR) GlobalReAlloc((HGLOBAL)fileData, newCapacity, 0);
if (!newFileData)
{
// error handling and cleanup ...
return NULL;
}
fileData = newFileData;
dwFileCap = newCapacity;
}
//
// TODO: if using a file instead, write the bytes to the file here...
memcpy(fileData + dwFileSize, readBuffer, dwDownloaded);
dwFileSize += dwDownloaded;
dwAvailableSize -= dwDownloaded;
}
while (dwAvailableSize > 0);
}
while (!done);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
GlobalFree((HGLOBAL)readBuffer);
// TODO: if using a file instead, close the file here...
// use file data up to dwFileSize bytes as needed ...
// TODO: if using a file instead, ignore this part ...
GlobalFree((HGLOBAL)fileData);
Save a file downloaded via WinHTTP to disk, using Delphi XE
Warren, you must use the AxCtrls.TOleStream
class to comunicate the response stream with the Classes.TFileStream
something like this
IWinHttpRequest.ResponseStream -> TOleStream -> TFileStream
Check this sample code
{$APPTYPE CONSOLE}
uses
Variants,
ActiveX,
Classes,
AxCtrls,
WinHttp_TLB,
SysUtils;
function Download(const url, filename: String): Boolean;
var
http: IWinHttpRequest;
wUrl: WideString;
fs:TFileStream;
HttpStream :IStream;
sz,rd,wr:Int64;
FStatus : Integer;
OleStream: TOleStream;
begin
try
wUrl := url;
http := CoWinHttpRequest.Create;
http.open('GET', wurl, False);
http.send(EmptyParam);
FStatus := http.status; // 200=OK!
result := FStatus=200;
if result then
begin
HttpStream:=IUnknown(http.ResponseStream) as IStream;
OleStream:= TOleStream.Create(HttpStream);
try
fs:= TFileStream.Create(FileName, fmCreate);
try
OleStream.Position:= 0;
fs.CopyFrom(OleStream, OleStream.Size);
finally
fs.Free;
end;
finally
OleStream.Free;
end;
end;
except
result := false;
// do not raise exceptions.
end;
end;
begin
try
Download('http://foo.html','C:\Foo\anyfile.foo');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Progress indication with HTTP file download using WinHTTP
Per the docs:
WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE
Data is available to be retrieved with
WinHttpReadData. The
lpvStatusInformation parameter points
to a DWORD that contains the number of
bytes of data available. The
dwStatusInformationLength parameter
itself is 4 (the size of a DWORD).
and
WINHTTP_CALLBACK_STATUS_READ_COMPLETE
Data was successfully read from the
server. The lpvStatusInformation
parameter contains a pointer to the
buffer specified in the call to
WinHttpReadData. The
dwStatusInformationLength parameter
contains the number of bytes read.
There may be other relevant notifications, but these two seem to be the key ones. Getting "percent" is not necessarily trivial because you may not know how much data you're getting (not all downloads have content-length set...); you can get the headers with:
WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE
The response header has been received
and is available with
WinHttpQueryHeaders. The
lpvStatusInformation parameter is
NULL.
and if Content-Length IS available then the percentage can be computed by keeping track of the total number of bytes at each "data available" notification, otherwise your guess is as good as mine;-).
Downloading a file with WINHTTP using POST/GET and headers (with VBA)
BIG THANK YOU TO RYAN WILDRY! Problem Solved.
I needed to set two specific cookies and change the actual login link - using the main domain was not working.
For anyone else who finds this post one day - Look through the cookies using the developer tools and find the ones that you need to pass on login. I personally tested them by having a message box with the response headers pop up which I compared to the response headers that I usually get when I login manually.
Related Topics
Warning: Returning Reference to Temporary
How to Read from a Text File, Character by Character in C++
How to Get the Executable Name of a Window
Generate Include File Name in a MACro
Two Phase Name Lookup for C++ Templates - Why
Infinite Loops - Top or Bottom
What Is _Declspec and When Do I Need to Use It
How to Estimate the Thread Context Switching Overhead
Does Resizing a Vector Invalidate Iterators
The C 'Clock()' Function Just Returns a Zero
If I Want to Specialise Just One Method in a Template, How to Do It
Why the Sizeof(Bool) Is Not Defined to Be One, by the Standard Itself
How to Handle Key Press Events in C++
New() Without Delete() Is Undefined Behavior or Merely Memory Leak