1 | /*
|
---|
2 | * Copyright 2000-2005 The Apache Software Foundation
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
---|
5 | * you may not use this file except in compliance with the License.
|
---|
6 | * You may obtain a copy of the License at
|
---|
7 | *
|
---|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
---|
9 | *
|
---|
10 | * Unless required by applicable law or agreed to in writing, software
|
---|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
---|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
---|
13 | * See the License for the specific language governing permissions and
|
---|
14 | * limitations under the License.
|
---|
15 | *
|
---|
16 | */
|
---|
17 |
|
---|
18 | package org.apache.tools.ant.taskdefs;
|
---|
19 |
|
---|
20 | import org.apache.tools.ant.BuildException;
|
---|
21 | import org.apache.tools.ant.Project;
|
---|
22 | import org.apache.tools.ant.Task;
|
---|
23 | import org.apache.tools.ant.util.FileUtils;
|
---|
24 | import org.apache.tools.ant.util.JavaEnvUtils;
|
---|
25 |
|
---|
26 | import java.io.File;
|
---|
27 | import java.io.FileOutputStream;
|
---|
28 | import java.io.IOException;
|
---|
29 | import java.io.InputStream;
|
---|
30 | import java.io.PrintStream;
|
---|
31 | import java.net.HttpURLConnection;
|
---|
32 | import java.net.URL;
|
---|
33 | import java.net.URLConnection;
|
---|
34 | import java.util.Date;
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Gets a particular file from a URL source.
|
---|
38 | * Options include verbose reporting, timestamp based fetches and controlling
|
---|
39 | * actions on failures. NB: access through a firewall only works if the whole
|
---|
40 | * Java runtime is correctly configured.
|
---|
41 | *
|
---|
42 | * @since Ant 1.1
|
---|
43 | *
|
---|
44 | * @ant.task category="network"
|
---|
45 | */
|
---|
46 | public class Get extends Task {
|
---|
47 |
|
---|
48 | private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
|
---|
49 |
|
---|
50 | private URL source; // required
|
---|
51 | private File dest; // required
|
---|
52 | private boolean verbose = false;
|
---|
53 | private boolean useTimestamp = false; //off by default
|
---|
54 | private boolean ignoreErrors = false;
|
---|
55 | private String uname = null;
|
---|
56 | private String pword = null;
|
---|
57 |
|
---|
58 |
|
---|
59 |
|
---|
60 | /**
|
---|
61 | * Does the work.
|
---|
62 | *
|
---|
63 | * @exception BuildException Thrown in unrecoverable error.
|
---|
64 | */
|
---|
65 | public void execute() throws BuildException {
|
---|
66 |
|
---|
67 | //set up logging
|
---|
68 | int logLevel = Project.MSG_INFO;
|
---|
69 | DownloadProgress progress = null;
|
---|
70 | if (verbose) {
|
---|
71 | progress = new VerboseProgress(System.out);
|
---|
72 | }
|
---|
73 |
|
---|
74 | //execute the get
|
---|
75 | try {
|
---|
76 | doGet(logLevel, progress);
|
---|
77 | } catch (IOException ioe) {
|
---|
78 | log("Error getting " + source + " to " + dest);
|
---|
79 | if (!ignoreErrors) {
|
---|
80 | throw new BuildException(ioe, getLocation());
|
---|
81 | }
|
---|
82 | }
|
---|
83 | }
|
---|
84 |
|
---|
85 | /**
|
---|
86 | * make a get request, with the supplied progress and logging info.
|
---|
87 | * All the other config parameters are set at the task level,
|
---|
88 | * source, dest, ignoreErrors, etc.
|
---|
89 | * @param logLevel level to log at, see {@link Project#log(String, int)}
|
---|
90 | * @param progress progress callback; null for no-callbacks
|
---|
91 | * @return true for a successful download, false otherwise.
|
---|
92 | * The return value is only relevant when {@link #ignoreErrors} is true, as
|
---|
93 | * when false all failures raise BuildExceptions.
|
---|
94 | * @throws IOException for network trouble
|
---|
95 | * @throws BuildException for argument errors, or other trouble when ignoreErrors
|
---|
96 | * is false.
|
---|
97 | */
|
---|
98 | public boolean doGet(int logLevel, DownloadProgress progress)
|
---|
99 | throws IOException {
|
---|
100 | if (source == null) {
|
---|
101 | throw new BuildException("src attribute is required", getLocation());
|
---|
102 | }
|
---|
103 |
|
---|
104 | if (dest == null) {
|
---|
105 | throw new BuildException("dest attribute is required", getLocation());
|
---|
106 | }
|
---|
107 |
|
---|
108 | if (dest.exists() && dest.isDirectory()) {
|
---|
109 | throw new BuildException("The specified destination is a directory",
|
---|
110 | getLocation());
|
---|
111 | }
|
---|
112 |
|
---|
113 | if (dest.exists() && !dest.canWrite()) {
|
---|
114 | throw new BuildException("Can't write to " + dest.getAbsolutePath(),
|
---|
115 | getLocation());
|
---|
116 | }
|
---|
117 | //dont do any progress, unless asked
|
---|
118 | if (progress == null) {
|
---|
119 | progress = new NullProgress();
|
---|
120 | }
|
---|
121 | log("Getting: " + source, logLevel);
|
---|
122 | log("To: " + dest.getAbsolutePath(), logLevel);
|
---|
123 |
|
---|
124 | //set the timestamp to the file date.
|
---|
125 | long timestamp = 0;
|
---|
126 |
|
---|
127 | boolean hasTimestamp = false;
|
---|
128 | if (useTimestamp && dest.exists()) {
|
---|
129 | timestamp = dest.lastModified();
|
---|
130 | if (verbose) {
|
---|
131 | Date t = new Date(timestamp);
|
---|
132 | log("local file date : " + t.toString(), logLevel);
|
---|
133 | }
|
---|
134 | hasTimestamp = true;
|
---|
135 | }
|
---|
136 |
|
---|
137 | //set up the URL connection
|
---|
138 | URLConnection connection = source.openConnection();
|
---|
139 | //modify the headers
|
---|
140 | //NB: things like user authentication could go in here too.
|
---|
141 | if (hasTimestamp) {
|
---|
142 | connection.setIfModifiedSince(timestamp);
|
---|
143 | }
|
---|
144 | // prepare Java 1.1 style credentials
|
---|
145 | if (uname != null || pword != null) {
|
---|
146 | String up = uname + ":" + pword;
|
---|
147 | String encoding;
|
---|
148 | //we do not use the sun impl for portability,
|
---|
149 | //and always use our own implementation for consistent
|
---|
150 | //testing
|
---|
151 | Base64Converter encoder = new Base64Converter();
|
---|
152 | encoding = encoder.encode(up.getBytes());
|
---|
153 | connection.setRequestProperty ("Authorization",
|
---|
154 | "Basic " + encoding);
|
---|
155 | }
|
---|
156 |
|
---|
157 | //connect to the remote site (may take some time)
|
---|
158 | connection.connect();
|
---|
159 | //next test for a 304 result (HTTP only)
|
---|
160 | if (connection instanceof HttpURLConnection) {
|
---|
161 | HttpURLConnection httpConnection
|
---|
162 | = (HttpURLConnection) connection;
|
---|
163 | long lastModified = httpConnection.getLastModified();
|
---|
164 | if (httpConnection.getResponseCode()
|
---|
165 | == HttpURLConnection.HTTP_NOT_MODIFIED
|
---|
166 | || (lastModified != 0 && hasTimestamp
|
---|
167 | && timestamp > lastModified)) {
|
---|
168 | //not modified so no file download. just return
|
---|
169 | //instead and trace out something so the user
|
---|
170 | //doesn't think that the download happened when it
|
---|
171 | //didn't
|
---|
172 | log("Not modified - so not downloaded", logLevel);
|
---|
173 | return false;
|
---|
174 | }
|
---|
175 | // test for 401 result (HTTP only)
|
---|
176 | if (httpConnection.getResponseCode()
|
---|
177 | == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
---|
178 | String message = "HTTP Authorization failure";
|
---|
179 | if (ignoreErrors) {
|
---|
180 | log(message, logLevel);
|
---|
181 | return false;
|
---|
182 | } else {
|
---|
183 | throw new BuildException(message);
|
---|
184 | }
|
---|
185 | }
|
---|
186 |
|
---|
187 | }
|
---|
188 |
|
---|
189 | //REVISIT: at this point even non HTTP connections may
|
---|
190 | //support the if-modified-since behaviour -we just check
|
---|
191 | //the date of the content and skip the write if it is not
|
---|
192 | //newer. Some protocols (FTP) don't include dates, of
|
---|
193 | //course.
|
---|
194 |
|
---|
195 | InputStream is = null;
|
---|
196 | for (int i = 0; i < 3; i++) {
|
---|
197 | //this three attempt trick is to get round quirks in different
|
---|
198 | //Java implementations. Some of them take a few goes to bind
|
---|
199 | //property; we ignore the first couple of such failures.
|
---|
200 | try {
|
---|
201 | is = connection.getInputStream();
|
---|
202 | break;
|
---|
203 | } catch (IOException ex) {
|
---|
204 | log("Error opening connection " + ex, logLevel);
|
---|
205 | }
|
---|
206 | }
|
---|
207 | if (is == null) {
|
---|
208 | log("Can't get " + source + " to " + dest, logLevel);
|
---|
209 | if (ignoreErrors) {
|
---|
210 | return false;
|
---|
211 | }
|
---|
212 | throw new BuildException("Can't get " + source + " to " + dest,
|
---|
213 | getLocation());
|
---|
214 | }
|
---|
215 |
|
---|
216 | FileOutputStream fos = new FileOutputStream(dest);
|
---|
217 | progress.beginDownload();
|
---|
218 | boolean finished = false;
|
---|
219 | try {
|
---|
220 | byte[] buffer = new byte[100 * 1024];
|
---|
221 | int length;
|
---|
222 | while ((length = is.read(buffer)) >= 0) {
|
---|
223 | fos.write(buffer, 0, length);
|
---|
224 | progress.onTick();
|
---|
225 | }
|
---|
226 | finished = true;
|
---|
227 | } finally {
|
---|
228 | FileUtils.close(fos);
|
---|
229 | FileUtils.close(is);
|
---|
230 |
|
---|
231 | // we have started to (over)write dest, but failed.
|
---|
232 | // Try to delete the garbage we'd otherwise leave
|
---|
233 | // behind.
|
---|
234 | if (!finished) {
|
---|
235 | dest.delete();
|
---|
236 | }
|
---|
237 | }
|
---|
238 | progress.endDownload();
|
---|
239 |
|
---|
240 | //if (and only if) the use file time option is set, then
|
---|
241 | //the saved file now has its timestamp set to that of the
|
---|
242 | //downloaded file
|
---|
243 | if (useTimestamp) {
|
---|
244 | long remoteTimestamp = connection.getLastModified();
|
---|
245 | if (verbose) {
|
---|
246 | Date t = new Date(remoteTimestamp);
|
---|
247 | log("last modified = " + t.toString()
|
---|
248 | + ((remoteTimestamp == 0)
|
---|
249 | ? " - using current time instead"
|
---|
250 | : ""), logLevel);
|
---|
251 | }
|
---|
252 | if (remoteTimestamp != 0) {
|
---|
253 | FILE_UTILS.setFileLastModified(dest, remoteTimestamp);
|
---|
254 | }
|
---|
255 | }
|
---|
256 |
|
---|
257 | //successful download
|
---|
258 | return true;
|
---|
259 | }
|
---|
260 |
|
---|
261 |
|
---|
262 | /**
|
---|
263 | * Set the URL to get.
|
---|
264 | *
|
---|
265 | * @param u URL for the file.
|
---|
266 | */
|
---|
267 | public void setSrc(URL u) {
|
---|
268 | this.source = u;
|
---|
269 | }
|
---|
270 |
|
---|
271 | /**
|
---|
272 | * Where to copy the source file.
|
---|
273 | *
|
---|
274 | * @param dest Path to file.
|
---|
275 | */
|
---|
276 | public void setDest(File dest) {
|
---|
277 | this.dest = dest;
|
---|
278 | }
|
---|
279 |
|
---|
280 | /**
|
---|
281 | * If true, show verbose progress information.
|
---|
282 | *
|
---|
283 | * @param v if "true" then be verbose
|
---|
284 | */
|
---|
285 | public void setVerbose(boolean v) {
|
---|
286 | verbose = v;
|
---|
287 | }
|
---|
288 |
|
---|
289 | /**
|
---|
290 | * If true, log errors but do not treat as fatal.
|
---|
291 | *
|
---|
292 | * @param v if "true" then don't report download errors up to ant
|
---|
293 | */
|
---|
294 | public void setIgnoreErrors(boolean v) {
|
---|
295 | ignoreErrors = v;
|
---|
296 | }
|
---|
297 |
|
---|
298 | /**
|
---|
299 | * If true, conditionally download a file based on the timestamp
|
---|
300 | * of the local copy.
|
---|
301 | *
|
---|
302 | * <p>In this situation, the if-modified-since header is set so
|
---|
303 | * that the file is only fetched if it is newer than the local
|
---|
304 | * file (or there is no local file) This flag is only valid on
|
---|
305 | * HTTP connections, it is ignored in other cases. When the flag
|
---|
306 | * is set, the local copy of the downloaded file will also have
|
---|
307 | * its timestamp set to the remote file time.</p>
|
---|
308 | *
|
---|
309 | * <p>Note that remote files of date 1/1/1970 (GMT) are treated as
|
---|
310 | * 'no timestamp', and web servers often serve files with a
|
---|
311 | * timestamp in the future by replacing their timestamp with that
|
---|
312 | * of the current time. Also, inter-computer clock differences can
|
---|
313 | * cause no end of grief.</p>
|
---|
314 | * @param v "true" to enable file time fetching
|
---|
315 | */
|
---|
316 | public void setUseTimestamp(boolean v) {
|
---|
317 | useTimestamp = v;
|
---|
318 | }
|
---|
319 |
|
---|
320 |
|
---|
321 | /**
|
---|
322 | * Username for basic auth.
|
---|
323 | *
|
---|
324 | * @param u username for authentication
|
---|
325 | */
|
---|
326 | public void setUsername(String u) {
|
---|
327 | this.uname = u;
|
---|
328 | }
|
---|
329 |
|
---|
330 | /**
|
---|
331 | * password for the basic authentication.
|
---|
332 | *
|
---|
333 | * @param p password for authentication
|
---|
334 | */
|
---|
335 | public void setPassword(String p) {
|
---|
336 | this.pword = p;
|
---|
337 | }
|
---|
338 |
|
---|
339 | /*********************************************************************
|
---|
340 | * BASE 64 encoding of a String or an array of bytes.
|
---|
341 | *
|
---|
342 | * Based on RFC 1421.
|
---|
343 | *
|
---|
344 | *********************************************************************/
|
---|
345 |
|
---|
346 | protected static class Base64Converter {
|
---|
347 |
|
---|
348 | public final char[] alphabet = {
|
---|
349 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7
|
---|
350 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15
|
---|
351 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23
|
---|
352 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31
|
---|
353 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39
|
---|
354 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47
|
---|
355 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55
|
---|
356 | '4', '5', '6', '7', '8', '9', '+', '/'}; // 56 to 63
|
---|
357 |
|
---|
358 | public String encode(String s) {
|
---|
359 | return encode(s.getBytes());
|
---|
360 | }
|
---|
361 |
|
---|
362 | public String encode(byte[] octetString) {
|
---|
363 | int bits24;
|
---|
364 | int bits6;
|
---|
365 |
|
---|
366 | char[] out = new char[((octetString.length - 1) / 3 + 1) * 4];
|
---|
367 | int outIndex = 0;
|
---|
368 | int i = 0;
|
---|
369 |
|
---|
370 | while ((i + 3) <= octetString.length) {
|
---|
371 | // store the octets
|
---|
372 | bits24 = (octetString[i++] & 0xFF) << 16;
|
---|
373 | bits24 |= (octetString[i++] & 0xFF) << 8;
|
---|
374 | bits24 |= octetString[i++];
|
---|
375 |
|
---|
376 | bits6 = (bits24 & 0x00FC0000) >> 18;
|
---|
377 | out[outIndex++] = alphabet[bits6];
|
---|
378 | bits6 = (bits24 & 0x0003F000) >> 12;
|
---|
379 | out[outIndex++] = alphabet[bits6];
|
---|
380 | bits6 = (bits24 & 0x00000FC0) >> 6;
|
---|
381 | out[outIndex++] = alphabet[bits6];
|
---|
382 | bits6 = (bits24 & 0x0000003F);
|
---|
383 | out[outIndex++] = alphabet[bits6];
|
---|
384 | }
|
---|
385 | if (octetString.length - i == 2) {
|
---|
386 | // store the octets
|
---|
387 | bits24 = (octetString[i] & 0xFF) << 16;
|
---|
388 | bits24 |= (octetString[i + 1] & 0xFF) << 8;
|
---|
389 | bits6 = (bits24 & 0x00FC0000) >> 18;
|
---|
390 | out[outIndex++] = alphabet[bits6];
|
---|
391 | bits6 = (bits24 & 0x0003F000) >> 12;
|
---|
392 | out[outIndex++] = alphabet[bits6];
|
---|
393 | bits6 = (bits24 & 0x00000FC0) >> 6;
|
---|
394 | out[outIndex++] = alphabet[bits6];
|
---|
395 |
|
---|
396 | // padding
|
---|
397 | out[outIndex++] = '=';
|
---|
398 | } else if (octetString.length - i == 1) {
|
---|
399 | // store the octets
|
---|
400 | bits24 = (octetString[i] & 0xFF) << 16;
|
---|
401 | bits6 = (bits24 & 0x00FC0000) >> 18;
|
---|
402 | out[outIndex++] = alphabet[bits6];
|
---|
403 | bits6 = (bits24 & 0x0003F000) >> 12;
|
---|
404 | out[outIndex++] = alphabet[bits6];
|
---|
405 |
|
---|
406 | // padding
|
---|
407 | out[outIndex++] = '=';
|
---|
408 | out[outIndex++] = '=';
|
---|
409 | }
|
---|
410 | return new String(out);
|
---|
411 | }
|
---|
412 | }
|
---|
413 |
|
---|
414 | public interface DownloadProgress {
|
---|
415 | /**
|
---|
416 | * begin a download
|
---|
417 | */
|
---|
418 | public void beginDownload();
|
---|
419 |
|
---|
420 | /**
|
---|
421 | * tick handler
|
---|
422 | *
|
---|
423 | */
|
---|
424 | public void onTick();
|
---|
425 |
|
---|
426 | /**
|
---|
427 | * end a download
|
---|
428 | */
|
---|
429 | public void endDownload();
|
---|
430 | }
|
---|
431 |
|
---|
432 | /**
|
---|
433 | * do nothing with progress info
|
---|
434 | */
|
---|
435 | public static class NullProgress implements DownloadProgress {
|
---|
436 |
|
---|
437 | /**
|
---|
438 | * begin a download
|
---|
439 | */
|
---|
440 | public void beginDownload() {
|
---|
441 |
|
---|
442 | }
|
---|
443 |
|
---|
444 | /**
|
---|
445 | * tick handler
|
---|
446 | *
|
---|
447 | */
|
---|
448 | public void onTick() {
|
---|
449 | }
|
---|
450 |
|
---|
451 | /**
|
---|
452 | * end a download
|
---|
453 | */
|
---|
454 | public void endDownload() {
|
---|
455 |
|
---|
456 | }
|
---|
457 | }
|
---|
458 |
|
---|
459 | /**
|
---|
460 | * verbose progress system prints to some output stream
|
---|
461 | */
|
---|
462 | public static class VerboseProgress implements DownloadProgress {
|
---|
463 | private int dots = 0;
|
---|
464 | PrintStream out;
|
---|
465 |
|
---|
466 | public VerboseProgress(PrintStream out) {
|
---|
467 | this.out = out;
|
---|
468 | }
|
---|
469 |
|
---|
470 | /**
|
---|
471 | * begin a download
|
---|
472 | */
|
---|
473 | public void beginDownload() {
|
---|
474 | dots = 0;
|
---|
475 | }
|
---|
476 |
|
---|
477 | /**
|
---|
478 | * tick handler
|
---|
479 | *
|
---|
480 | */
|
---|
481 | public void onTick() {
|
---|
482 | out.print(".");
|
---|
483 | if (dots++ > 50) {
|
---|
484 | out.flush();
|
---|
485 | dots = 0;
|
---|
486 | }
|
---|
487 | }
|
---|
488 |
|
---|
489 | /**
|
---|
490 | * end a download
|
---|
491 | */
|
---|
492 | public void endDownload() {
|
---|
493 | out.println();
|
---|
494 | out.flush();
|
---|
495 | }
|
---|
496 | }
|
---|
497 |
|
---|
498 | }
|
---|