How to Download a File with Winhttp in C/C++

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



Leave a reply



Submit