1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
|
##############################################################################
##
#W download.gi GAP4 package `Utils' Thomas Breuer
##
#Y Copyright (C) 2022, The GAP Group
#############################################################################
##
#V Download_Methods
##
## Use the following tools (in this order).
##
## - If the curlInterface package is available then call its
## 'DownloadURL' function.
## - If the URL starts with 'http://' and if the IO package is available
## then call the 'SingleHTTPRequest' function from this package.
## - If a 'wget' executable is available then call it.
## - If a 'curl' executable is available then call it.
##
## Note that currently the methods are *NOT* consistent in the case of
## failures:
##
## - The function 'SingleHTTPRequest' does not follow redirects as indicated
## by HTTP status codes 301 and 302.
## This happens for example if one asks for the file at
## 'http://www.gap-system.org/Packages/utils.html'.
##
BindGlobal( "Download_Methods", [] );
Add( Download_Methods, rec(
name:= "via DownloadURL (from the curlInterface package)",
isAvailable:= {} -> IsPackageLoaded( "curlInterface" ) and
CompareVersionNumbers(
InstalledPackageVersion( "curlInterface" ),
"2.3.0" ),
download:= function( url, opt )
local res;
opt:= ShallowCopy( opt );
if not IsBound( opt.failOnError ) then
opt.failOnError:= true;
fi;
# 'DownloadURL' handles the options 'verifyCert' and 'maxTime'.
res:= ValueGlobal( "DownloadURL" )( url, opt );
if res.success = true and
IsBound( opt.target ) and IsString( opt.target ) then
FileString( opt.target, res.result );
Unbind( res.result );
fi;
return res;
end ) );
Add( Download_Methods, rec(
name:= "via SingleHTTPRequest (from the IO package)",
isAvailable:= {} -> IsBoundGlobal( "SingleHTTPRequest" ),
download:= function( url, opt )
local rurl, pos, domain, uri, res;
if not StartsWith( url, "http://" ) then
return rec( success:= false, error:= "protocol is not http" );
elif IsBound( opt.maxTime ) and opt.maxTime <> 0 then
return rec( success:= false, error:= "no support for given timeout" );
fi;
rurl:= ReplacedString( url, "http://", "" );
pos:= Position( rurl, '/' );
domain:= rurl{ [ 1 .. pos-1 ] };
uri:= rurl{ [ pos .. Length( rurl ) ] };
if IsBound( opt.target ) and IsString( opt.target ) then
res:= ValueGlobal( "SingleHTTPRequest" )( domain, 80, "GET", uri,
rec(), false, opt.target );
else
res:= ValueGlobal( "SingleHTTPRequest" )( domain, 80, "GET", uri,
rec(), false, false );
fi;
if res.statuscode = 0 then
return rec( success:= false,
error:= res.status );
elif res.statuscode >= 400 then
return rec( success:= false,
error:= Concatenation( "HTTP error code ",
String( res.statuscode ) ) );
elif not ( IsBound( opt.target ) and IsString( opt.target ) ) then
return rec( success:= true, result:= res.body );
else
return rec( success:= true );
fi;
end ) );
Add( Download_Methods, rec(
name:= "via wget",
isAvailable:= function()
local exec;
exec:= Filename( DirectoriesSystemPrograms(), "wget" );
return exec <> fail and IsExecutableFile( exec );
end,
download:= function( url, opt )
local res, outstream, exec, args, code;
if IsBound( opt.maxTime ) and opt.maxTime <> 0 then
# wget 1.20.3 ignores a given timeout.
# (wget 1.21.3 would support timeout.)
return rec( success:= false, error:= "no support for given timeout" );
fi;
res:= "";
outstream:= OutputTextString( res, true );
exec:= Filename( DirectoriesSystemPrograms(), "wget" );
if IsBound( opt.target ) and IsString( opt.target ) then
args:= [ "--quiet", "-O", opt.target, url ];
else
args:= [ "--quiet", "-O", "-", url ];
fi;
if IsBound( opt.verifyCert ) and opt.verifyCert = false then
Add( args, "--no-check-certificate" );
fi;
if IsBound( opt.maxTime ) and IsPosInt( opt.maxTime ) then
Add( args, Concatenation( "--timeout=", String( opt.maxTime ) ) );
fi;
code:= Process( DirectoryCurrent(), exec, InputTextNone(), outstream, args );
CloseStream( outstream );
if code <> 0 then
# wget may have created the target file; try to remove it
if IsBound( opt.target ) and IsString( opt.target ) and
IsExistingFile( opt.target ) and RemoveFile( opt.target ) <> true then
Error( "Download cannot remove unwanted file ", opt.target );
fi;
return rec( success:= false,
error:= Concatenation( "Process returned ", String( code ) ) );
elif not ( IsBound( opt.target ) and IsString( opt.target ) ) then
return rec( success:= true, result:= res );
else
return rec( success:= true );
fi;
end ) );
Add( Download_Methods, rec(
name:= "via curl",
isAvailable:= function()
local exec;
exec:= Filename( DirectoriesSystemPrograms(), "curl" );
return exec <> fail and IsExecutableFile( exec );
end,
download:= function( url, opt )
local res, outstream, exec, args, code;
res:= "";
outstream:= OutputTextString( res, true );
exec:= Filename( DirectoriesSystemPrograms(), "curl" );
args:= [ "--silent", "-L", "--fail" ];
if IsBound( opt.verifyCert ) and opt.verifyCert = false then
Add( args, "-k" );
fi;
Add( args, "--output" );
if IsBound( opt.target ) and IsString( opt.target ) then
Add( args, opt.target );
else
Add( args, "-" );
fi;
if IsBound( opt.maxTime ) and IsPosInt( opt.maxTime ) then
Add( args, "--max-time" );
Add( args, opt.maxTime );
fi;
Add( args, url );
code:= Process( DirectoryCurrent(), exec, InputTextNone(), outstream, args );
CloseStream( outstream );
if code <> 0 then
return rec( success:= false,
error:= Concatenation( "Process returned ", String( code ) ) );
elif not ( IsBound( opt.target ) and IsString( opt.target ) ) then
return rec( success:= true, result:= res );
else
return rec( success:= true );
fi;
end ) );
#############################################################################
##
#M Download( <url>[, <opt>] )
##
## Try to download the file described by the string <url>,
## and return a record with the components 'success' ('true' or 'false'),
## and 'result' (a string, only if 'success' is 'true'),
## and 'error' (a string, only if 'success' is 'false').
##
InstallMethod( Download,
[ "IsString" ],
url -> Download( url, rec() ) );
InstallMethod( Download,
[ "IsString", "IsRecord" ],
function( url, opt )
local timeout, errors, r, res;
# Set the default for 'verifyCert' if necessary.
if not IsBound( opt.verifyCert ) and
UserPreference( "utils", "DownloadVerifyCertificate" ) = false then
opt.verifyCert:= false;
fi;
# Set the default for 'maxTime' if necessary.
if not IsBound( opt.maxTime ) then
timeout:= UserPreference( "utils", "DownloadMaxTime" );
if IsPosInt( timeout ) then
opt.maxTime:= timeout;
fi;
fi;
# Run over the methods.
errors:= [];
for r in Download_Methods do
if r.isAvailable() then
Info( InfoUtils, 2, "try Download method ", r.name );
res:= r.download( url, opt );
if res.success = true then
return res;
fi;
Info( InfoUtils, 2, "Download method ", r.name, " failed with\n",
"#I ", res.error );
Add( errors, Concatenation( r.name, ": ", res.error ) );
else
Info( InfoUtils, 2, "Download method ", r.name, " is not available" );
fi;
od;
# No method was successful.
if Length( errors ) = 0 then
# No method was available, inform the user
# that it is recommended to load or install some download tool.
Info( InfoWarning, 1,
"No 'Download' method is available.\n",
"#I Please consider to install one of the tools, ",
"see '?Download'" );
return rec( success:= false, error:= "no method was available" );
else
# At least one method was tried but without success.
if IsBound( opt.maxTime ) then
Add( errors,
Concatenation( "(maxTime option was set to ",
String( opt.maxTime ), ")" ) );
fi;
return rec( success:= false,
error:= JoinStringsWithSeparator( errors, "; " ) );
fi;
end );
|