File: download.gi

package info (click to toggle)
gap-utils 0.93-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 1,504 kB
  • sloc: xml: 2,167; javascript: 155; makefile: 105
file content (249 lines) | stat: -rw-r--r-- 8,801 bytes parent folder | download
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 );