[Again] [Synapse] Download file from Sourceforge
lazarus / 38-Networking and Web Programming
----------------------------------------------------------------------
BigChimp · 2011-06-09 10:32
Hi,
Probably a fairly stupid question, but rather a stupid question than
no chance of an answer
I'm trying to extend the LazUpdater program
(http://forge.lazarusforum.de/projects/lazupdater), which deals with
installing/updating Lazarus/FPC SVN.
If there's no SVN client on Windows, I'm trying to download some SVN
components from Sourceforge, but it doesn't work - probably because of
redirection to a mirror.
The code below always gives an empty document (Buffer.Size=0)
Does anybody know a fix for this?
Code: [Select]function TForm_Main.DoSVNClientDownload(): Boolean;
const
SourceUrl=
'http://downloads.sourceforge.net/project/win32svn/1.6.17/svn-
win32-1.6.17.zip?r=&ts=1307454210&use_mirror=kent';
var
Buffer: TMemoryStream;
TargetFile: string;
begin
TargetFile := ExtractFilePath(ParamStr(0)) +
'svn-win32-1.6.17.zip';
try
Buffer := TMemoryStream.Create;
if not HttpGetBinary(SourceURL, Buffer) then
raise Exception.Create('Cannot load document from remote
server');
Application.ProcessMessages;
if Buffer.Size=0 then raise Exception.Create('Downloaded
document is empty.');
Buffer.Position := 0;
Buffer.SaveToFile(TargetFile);
finally
FreeAndNil(Buffer);
end;
Result:=True;
end;
----------------------------------------------------------------------
AmatCoder · 2011-06-09 10:49
Have you tried a direct download? Something like:
Code: [Select]SourceUrl=
'http://heanet.dl.sourceforge.net/project/win32svn/1.6.17/svn-
win32-1.6.17.zip'
----------------------------------------------------------------------
BigChimp · 2011-06-09 11:08
No, I haven't; just fiddlexd with the parameters sent to the HTTP
server.
Thanks, this seems to work brilliantly. I knew the solution was
simple, just too tired and frustrated to see it...
----------------------------------------------------------------------
Ocye · 2011-06-14 12:59
SF change their redirection mode this year. The first snippet did work
well until the modification but needed some ugly hard constants as in
the second.
Code: [Select]while not Result do
begin
HTTPMethod('GET', aURL);
case Resultcode of
301,302,307 : for i:=0 to Headers.Count-1 do
if FindPart('Location: ',Headers.Strings[i])>0
then
begin
aURL:=StringReplace(Headers.Strings[i],'Location: ','',[]);
self.Clear;//httpsend
break;
end;
100..200 : Result:=true;
else exit;
end; //case
end;
Code: [Select] while not Result do
begin
HTTPMethod('GET', aURL);
case Resultcode of
301,302,307 : for i:=0 to Headers.Count-1 do
if (FindPart('Location: ',Headers.Strings[i])>0)
or
(FindPart('location: ',Headers.Strings[i])>0)
then
begin
j:=Pos('use_mirror=',Headers.Strings[i]);
if j>0 then
aURL:='http://'+RightStr(Headers.Strings[i],le
ngth(Headers.Strings[i])-j-
10)+'.dl.sourceforge.net/project/scrabble/'+aDir+aFileName else
aURl:=StringReplace(Headers.Strings[i],'Location: ','',[]);
self.Clear;//httpsend
break;
end;
100..200 : Result:=true;
500:raise EDownloadError.Create('No internet connection
available');//Internal Server Error ('+aURL+')');
else raise EDownloadError.Create('Download failed with error
code '+inttostr(ResultCode)+' ('+ResultString+')');
end;//case
end;//while
PS: IMHO, it's not a good idea to access a specific server like heanet
or kent.
----------------------------------------------------------------------
BigChimp · 2012-02-19 08:33
Edit: updated with new code... that doesn't work...
Sorry to dig up an old thread, but I'm having trouble downloading
again and thought it might be my mediocre understanding of your code,
Ocye.
Demo project attached; I currently have this code (using Synapse,
infoln is basically writeln):
Code: [Select]const
SFProjectPart = '//sourceforge.net/projects/';
SFFilesPart = '/files/';
SFDownloadPart ='/download';
MaxRetries = 3;
var
Buffer: TMemoryStream;
HTTPGetResult: boolean;
i, j: integer;
HTTPSender: THTTPSend;
IsSFDownload: boolean;
SFProjectBegin: integer;
RetryAttempt: integer;
SFDirectory: string; //Sourceforge directory
SFDirectoryBegin: integer;
SFFileBegin: integer;
SFFilename: string; //Sourceforge name of file
SFProject: string;
begin
Result := False;
IsSFDownload:=false;
// Detect SourceForge download; e.g. from URL
// 1 2 3 4 5 6
7 8 9
// 12345578901234567890123455789012345678901234557890123456789012345
57890123456789012345578901234567890
// http://sourceforge.net/projects/base64decoder/files/base64decoder
/version%202.0/b64util.zip/download
// ^^^project^^^
^^^directory............^^^ ^^^file^^^
i:=Pos(SFProjectPart, URL);
if i>0 then
begin
// Possibly found project; now extract project, directory and
filename parts.
SFProjectBegin:=i+Length(SFProjectPart);
j := PosEx(SFFilesPart, URL, SFProjectBegin);
if (j>0) then
begin
SFProject:=Copy(URL, SFProjectBegin, j-SFProjectBegin);
SFDirectoryBegin:=PosEx(SFFilesPart, URL,
SFProjectBegin)+Length(SFFilesPart);
if SFDirectoryBegin>0 then
begin
// Find file
// URL might have trailing arguments... so: search for first
// /download coming up from the right, but it should be after
// /files/
i:=RPos(SFDownloadPart, URL);
// Now look for previous / so we can make out the file
// This might perhaps be the trailing / in /files/
SFFileBegin:=RPosEx('/',URL,i-1)+1;
if SFFileBegin>0 then
begin
SFFilename:=Copy(URL,SFFileBegin, i-SFFileBegin);
//Include trailing /
SFDirectory:=Copy(URL, SFDirectoryBegin, SFFileBegin-
SFDirectoryBegin);
IsSFDownload:=true;
end;
end;
end;
end;
if IsSFDownload then
begin
try
// Rewrite URL if needed for Sourceforge download redirection
HTTPSender := THTTPSend.Create;
while not Result do
begin
HTTPSender.HTTPMethod('GET', URL);
infoln('debug: headers:');
infoln(HTTPSender.Headers.Text);
case HTTPSender.Resultcode of
301, 302, 307: for i := 0 to HTTPSender.Headers.Count - 1 do
if (Pos('Location: ', HTTPSender.Headers.Strings[i]) >
0) or
(Pos('location: ', HTTPSender.Headers.Strings[i]) > 0)
then
begin
j := Pos('use_mirror=',
HTTPSender.Headers.Strings[i]);
if j > 0 then
URL :=
'http://' +
RightStr(HTTPSender.Headers.Strings[i],
length(HTTPSender.Headers.Strings[i]) - j - 10) +
'.dl.sourceforge.net/project/' +
SFProject + '/' + SFDirectory + SFFilename
else
URl :=
StringReplace(HTTPSender.Headers.Strings[i],
'Location: ', '', []);
HTTPSender.Clear;//httpsend
break;
end;
100..200: Result := True; //No changes necessary
500: raise Exception.Create('No internet connection
available');
//Internal Server Error ('+aURL+')');
else
raise Exception.Create('Download failed with error code '
+
IntToStr(HTTPSender.ResultCode) + ' (' +
HTTPSender.ResultString + ')');
end;//case
end;//while
infoln('debug: resulting url after sf redir: *' + URL + '*');
finally
HTTPSender.Free;
end;
end;
It tries to detects a sourceforge download; assigns the project name,
the file wanted for download and the directory. Then follows redirects
until it hits the right page.
However, in my case, it immediately gives a 200 OK on the first HTTP
GET.
After this code, It then goes on to try and download the revised UR
using HTTPGetBinary.
Attached in zip (DownloadedPage.html) is what I get.
I suspect I need to actually download the resulting file after 200 ok,
then
1. see if it is HTML, not binary (presumably by searching the headers
for Content-Type: text/html?)
2. Take the body, look for either direct link or class="direct-
download", which will find:
Quote 3. Take that line, look
for
into a valid URL:
http://downloads.sourceforge.net/project/base64decoder/base64decoder/v
ersion%202.0/b64util.zip?r=&ts=1329648745&use_mirror=kent
}
const
Refresh='0 then
begin
URLStart:=AnsiPos(URLMarker,
HTMLBody[Counter])+Length(URLMarker);
if URLStart>RefreshStart then
begin
// Look for closing "
URL:=Copy(HTMLBody[Counter],
URLStart,
PosEx('"',HTMLBody[Counter],URLStart+1)-URLStart);
infoln('debug: new url after sf noscript:');
infoln(URL);
break;
end;
end;
end;
finally
HTMLBody.Free;
end;
result:=URL;
end;
function SourceForgeURL(URL: string): string;
// Detects sourceforge download and tries to deal with
// redirection, and extracting direct download link.
// Thanks to
// Ocye: http://lazarus.freepascal.org/index.php/topic,13425.msg70575.
html#msg70575
const
SFProjectPart = '//sourceforge.net/projects/';
SFFilesPart = '/files/';
SFDownloadPart ='/download';
var
HTTPSender: THTTPSend;
i, j: integer;
FoundCorrectURL: boolean;
SFDirectory: string; //Sourceforge directory
SFDirectoryBegin: integer;
SFFileBegin: integer;
SFFilename: string; //Sourceforge name of file
SFProject: string;
SFProjectBegin: integer;
begin
// Detect SourceForge download; e.g. from URL
// 1 2 3 4 5 6
7 8 9
// 12345578901234567890123455789012345678901234557890123456789012345
57890123456789012345578901234567890
// http://sourceforge.net/projects/base64decoder/files/base64decoder
/version%202.0/b64util.zip/download
// ^^^project^^^
^^^directory............^^^ ^^^file^^^
FoundCorrectURL:=true; //Assume not a SF download
i:=Pos(SFProjectPart, URL);
if i>0 then
begin
// Possibly found project; now extract project, directory and
filename parts.
SFProjectBegin:=i+Length(SFProjectPart);
j := PosEx(SFFilesPart, URL, SFProjectBegin);
if (j>0) then
begin
SFProject:=Copy(URL, SFProjectBegin, j-SFProjectBegin);
SFDirectoryBegin:=PosEx(SFFilesPart, URL,
SFProjectBegin)+Length(SFFilesPart);
if SFDirectoryBegin>0 then
begin
// Find file
// URL might have trailing arguments... so: search for first
// /download coming up from the right, but it should be after
// /files/
i:=RPos(SFDownloadPart, URL);
// Now look for previous / so we can make out the file
// This might perhaps be the trailing / in /files/
SFFileBegin:=RPosEx('/',URL,i-1)+1;
if SFFileBegin>0 then
begin
SFFilename:=Copy(URL,SFFileBegin, i-SFFileBegin);
//Include trailing /
SFDirectory:=Copy(URL, SFDirectoryBegin, SFFileBegin-
SFDirectoryBegin);
FoundCorrectURL:=false;
end;
end;
end;
end;
if not FoundCorrectURL then
begin
try
// Rewrite URL if needed for Sourceforge download redirection
// Detect direct link in HTML body and get URL from that
HTTPSender := THTTPSend.Create;
//Who knows, this might help:
HTTPSender.UserAgent:='curl/7.21.0 (i686-pc-linux-gnu)
libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18';
while not FoundCorrectURL do
begin
HTTPSender.HTTPMethod('GET', URL);
infoln('debug: headers:');
infoln(HTTPSender.Headers.Text);
case HTTPSender.Resultcode of
301, 302, 307:
begin
for i := 0 to HTTPSender.Headers.Count - 1 do
if (Pos('Location: ', HTTPSender.Headers.Strings[i]) >
0) or
(Pos('location: ', HTTPSender.Headers.Strings[i]) >
0) then
begin
j := Pos('use_mirror=',
HTTPSender.Headers.Strings[i]);
if j > 0 then
URL :=
'http://' +
RightStr(HTTPSender.Headers.Strings[i],
length(HTTPSender.Headers.Strings[i]) - j - 10)
+
'.dl.sourceforge.net/project/' +
SFProject + '/' + SFDirectory + SFFilename
else
URL:=StringReplace(
HTTPSender.Headers.Strings[i], 'Location: ', '',
[]);
HTTPSender.Clear;//httpsend
FoundCorrectURL:=true;
break; //out of rewriting loop
end;
end;
100..200:
begin
//Assume a sourceforge timer/direct link page
URL:=SFDirectLinkURL(URL, HTTPSender.Document); //Find
out
FoundCorrectURL:=true; //We're done by now
end;
500: raise Exception.Create('No internet connection
available');
//Internal Server Error ('+aURL+')');
else
raise Exception.Create('Download failed with error code '
+
IntToStr(HTTPSender.ResultCode) + ' (' +
HTTPSender.ResultString + ')');
end;//case
end;//while
infoln('debug: resulting url after sf redir: *' + URL + '*');
finally
HTTPSender.Free;
end;
end;
result:=URL;
end;
function DownloadHTTP(URL, TargetFile: string): boolean;
// Download file; retry if necessary.
// Deals with SourceForge download links
var
Buffer: TMemoryStream;
begin
result:=false;
URL:=SourceForgeURL(URL); //Deal with sourceforge URLs
try
Buffer := TMemoryStream.Create;
DownloadHTTPStream(URL, Buffer);
Buffer.SaveToFile(TargetFile);
result:=true;
finally
FreeAndNil(Buffer);
end;
end;
----------------------------------------------------------------------
lainz · 2014-08-22 21:54
Thankyou is what I was looking for.
Still working today Aug 22 2014
----------------------------------------------------------------------
minesadorada · 2014-08-23 06:46
This code is incorporated into LazAutoUpdater as a drop-in component
for simplicity:
http://wiki.lazarus.freepascal.org/LazAutoUpdater