1. /*
  2. * Copyright 2000-2004 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. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.net.HttpURLConnection;
  23. import java.net.URL;
  24. import java.net.URLConnection;
  25. import java.util.Date;
  26. import org.apache.tools.ant.BuildException;
  27. import org.apache.tools.ant.Project;
  28. import org.apache.tools.ant.Task;
  29. import org.apache.tools.ant.util.FileUtils;
  30. import org.apache.tools.ant.util.JavaEnvUtils;
  31. /**
  32. * Gets a particular file from a URL source.
  33. * Options include verbose reporting, timestamp based fetches and controlling
  34. * actions on failures. NB: access through a firewall only works if the whole
  35. * Java runtime is correctly configured.
  36. *
  37. *
  38. * @since Ant 1.1
  39. *
  40. * @ant.task category="network"
  41. */
  42. public class Get extends Task {
  43. private URL source; // required
  44. private File dest; // required
  45. private boolean verbose = false;
  46. private boolean useTimestamp = false; //off by default
  47. private boolean ignoreErrors = false;
  48. private String uname = null;
  49. private String pword = null;
  50. /**
  51. * Does the work.
  52. *
  53. * @exception BuildException Thrown in unrecoverable error.
  54. */
  55. public void execute() throws BuildException {
  56. if (source == null) {
  57. throw new BuildException("src attribute is required", getLocation());
  58. }
  59. if (dest == null) {
  60. throw new BuildException("dest attribute is required", getLocation());
  61. }
  62. if (dest.exists() && dest.isDirectory()) {
  63. throw new BuildException("The specified destination is a directory",
  64. getLocation());
  65. }
  66. if (dest.exists() && !dest.canWrite()) {
  67. throw new BuildException("Can't write to " + dest.getAbsolutePath(),
  68. getLocation());
  69. }
  70. try {
  71. log("Getting: " + source);
  72. //set the timestamp to the file date.
  73. long timestamp = 0;
  74. boolean hasTimestamp = false;
  75. if (useTimestamp && dest.exists()) {
  76. timestamp = dest.lastModified();
  77. if (verbose) {
  78. Date t = new Date(timestamp);
  79. log("local file date : " + t.toString());
  80. }
  81. hasTimestamp = true;
  82. }
  83. //set up the URL connection
  84. URLConnection connection = source.openConnection();
  85. //modify the headers
  86. //NB: things like user authentication could go in here too.
  87. if (useTimestamp && hasTimestamp) {
  88. connection.setIfModifiedSince(timestamp);
  89. }
  90. // prepare Java 1.1 style credentials
  91. if (uname != null || pword != null) {
  92. String up = uname + ":" + pword;
  93. String encoding;
  94. // check to see if sun's Base64 encoder is available.
  95. try {
  96. Object encoder =
  97. Class.forName("sun.misc.BASE64Encoder").newInstance();
  98. encoding = (String)
  99. encoder.getClass().getMethod("encode", new Class[] {byte[].class})
  100. .invoke(encoder, new Object[] {up.getBytes()});
  101. } catch (Exception ex) { // sun's base64 encoder isn't available
  102. Base64Converter encoder = new Base64Converter();
  103. encoding = encoder.encode(up.getBytes());
  104. }
  105. connection.setRequestProperty ("Authorization",
  106. "Basic " + encoding);
  107. }
  108. //connect to the remote site (may take some time)
  109. connection.connect();
  110. //next test for a 304 result (HTTP only)
  111. if (connection instanceof HttpURLConnection) {
  112. HttpURLConnection httpConnection
  113. = (HttpURLConnection) connection;
  114. if (httpConnection.getResponseCode()
  115. == HttpURLConnection.HTTP_NOT_MODIFIED) {
  116. //not modified so no file download. just return
  117. //instead and trace out something so the user
  118. //doesn't think that the download happened when it
  119. //didn't
  120. log("Not modified - so not downloaded");
  121. return;
  122. }
  123. // test for 401 result (HTTP only)
  124. if (httpConnection.getResponseCode()
  125. == HttpURLConnection.HTTP_UNAUTHORIZED) {
  126. String message = "HTTP Authorization failure";
  127. if (ignoreErrors) {
  128. log(message, Project.MSG_WARN);
  129. return;
  130. } else {
  131. throw new BuildException(message);
  132. }
  133. }
  134. }
  135. //REVISIT: at this point even non HTTP connections may
  136. //support the if-modified-since behaviour -we just check
  137. //the date of the content and skip the write if it is not
  138. //newer. Some protocols (FTP) don't include dates, of
  139. //course.
  140. InputStream is = null;
  141. for (int i = 0; i < 3; i++) {
  142. try {
  143. is = connection.getInputStream();
  144. break;
  145. } catch (IOException ex) {
  146. log("Error opening connection " + ex);
  147. }
  148. }
  149. if (is == null) {
  150. log("Can't get " + source + " to " + dest);
  151. if (ignoreErrors) {
  152. return;
  153. }
  154. throw new BuildException("Can't get " + source + " to " + dest,
  155. getLocation());
  156. }
  157. FileOutputStream fos = new FileOutputStream(dest);
  158. boolean finished = false;
  159. try {
  160. byte[] buffer = new byte[100 * 1024];
  161. int length;
  162. int dots = 0;
  163. while ((length = is.read(buffer)) >= 0) {
  164. fos.write(buffer, 0, length);
  165. if (verbose) {
  166. System.out.print(".");
  167. if (dots++ > 50) {
  168. System.out.flush();
  169. dots = 0;
  170. }
  171. }
  172. }
  173. if (verbose) {
  174. System.out.println();
  175. }
  176. finished = true;
  177. } finally {
  178. if (fos != null) {
  179. fos.close();
  180. }
  181. is.close();
  182. // we have started to (over)write dest, but failed.
  183. // Try to delete the garbage we'd otherwise leave
  184. // behind.
  185. if (!finished) {
  186. dest.delete();
  187. }
  188. }
  189. //if (and only if) the use file time option is set, then
  190. //the saved file now has its timestamp set to that of the
  191. //downloaded file
  192. if (useTimestamp) {
  193. long remoteTimestamp = connection.getLastModified();
  194. if (verbose) {
  195. Date t = new Date(remoteTimestamp);
  196. log("last modified = " + t.toString()
  197. + ((remoteTimestamp == 0)
  198. ? " - using current time instead"
  199. : ""));
  200. }
  201. if (remoteTimestamp != 0) {
  202. FileUtils.newFileUtils()
  203. .setFileLastModified(dest, remoteTimestamp);
  204. }
  205. }
  206. } catch (IOException ioe) {
  207. log("Error getting " + source + " to " + dest);
  208. if (ignoreErrors) {
  209. return;
  210. }
  211. throw new BuildException(ioe, getLocation());
  212. }
  213. }
  214. /**
  215. * Set the URL to get.
  216. *
  217. * @param u URL for the file.
  218. */
  219. public void setSrc(URL u) {
  220. this.source = u;
  221. }
  222. /**
  223. * Where to copy the source file.
  224. *
  225. * @param dest Path to file.
  226. */
  227. public void setDest(File dest) {
  228. this.dest = dest;
  229. }
  230. /**
  231. * If true, show verbose progress information.
  232. *
  233. * @param v if "true" then be verbose
  234. */
  235. public void setVerbose(boolean v) {
  236. verbose = v;
  237. }
  238. /**
  239. * If true, log errors but do not treat as fatal.
  240. *
  241. * @param v if "true" then don't report download errors up to ant
  242. */
  243. public void setIgnoreErrors(boolean v) {
  244. ignoreErrors = v;
  245. }
  246. /**
  247. * If true, conditionally download a file based on the timestamp
  248. * of the local copy.
  249. *
  250. * <p>In this situation, the if-modified-since header is set so
  251. * that the file is only fetched if it is newer than the local
  252. * file (or there is no local file) This flag is only valid on
  253. * HTTP connections, it is ignored in other cases. When the flag
  254. * is set, the local copy of the downloaded file will also have
  255. * its timestamp set to the remote file time.</p>
  256. *
  257. * <p>Note that remote files of date 1/1/1970 (GMT) are treated as
  258. * 'no timestamp', and web servers often serve files with a
  259. * timestamp in the future by replacing their timestamp with that
  260. * of the current time. Also, inter-computer clock differences can
  261. * cause no end of grief.</p>
  262. * @param v "true" to enable file time fetching
  263. */
  264. public void setUseTimestamp(boolean v) {
  265. if (!JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
  266. useTimestamp = v;
  267. }
  268. }
  269. /**
  270. * Username for basic auth.
  271. *
  272. * @param u username for authentication
  273. */
  274. public void setUsername(String u) {
  275. this.uname = u;
  276. }
  277. /**
  278. * password for the basic authentication.
  279. *
  280. * @param p password for authentication
  281. */
  282. public void setPassword(String p) {
  283. this.pword = p;
  284. }
  285. /*********************************************************************
  286. * BASE 64 encoding of a String or an array of bytes.
  287. *
  288. * Based on RFC 1421.
  289. *
  290. *********************************************************************/
  291. private static class Base64Converter {
  292. public final char [ ] alphabet = {
  293. 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7
  294. 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15
  295. 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23
  296. 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31
  297. 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39
  298. 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47
  299. 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55
  300. '4', '5', '6', '7', '8', '9', '+', '/' }; // 56 to 63
  301. public String encode(String s) {
  302. return encode (s.getBytes());
  303. }
  304. public String encode(byte[ ] octetString) {
  305. int bits24;
  306. int bits6;
  307. char [ ] out
  308. = new char[((octetString.length - 1) / 3 + 1) * 4];
  309. int outIndex = 0;
  310. int i = 0;
  311. while ((i + 3) <= octetString.length) {
  312. // store the octets
  313. bits24 = (octetString[i++] & 0xFF) << 16;
  314. bits24 |= (octetString[i++] & 0xFF) << 8;
  315. bits6 = (bits24 & 0x00FC0000) >> 18;
  316. out[outIndex++] = alphabet[bits6];
  317. bits6 = (bits24 & 0x0003F000) >> 12;
  318. out[outIndex++] = alphabet[bits6];
  319. bits6 = (bits24 & 0x00000FC0) >> 6;
  320. out[outIndex++] = alphabet[bits6];
  321. bits6 = (bits24 & 0x0000003F);
  322. out[outIndex++] = alphabet[bits6];
  323. }
  324. if (octetString.length - i == 2) {
  325. // store the octets
  326. bits24 = (octetString[i] & 0xFF) << 16;
  327. bits24 |= (octetString[i + 1] & 0xFF) << 8;
  328. bits6 = (bits24 & 0x00FC0000) >> 18;
  329. out[outIndex++] = alphabet[bits6];
  330. bits6 = (bits24 & 0x0003F000) >> 12;
  331. out[outIndex++] = alphabet[bits6];
  332. bits6 = (bits24 & 0x00000FC0) >> 6;
  333. out[outIndex++] = alphabet[bits6];
  334. // padding
  335. out[outIndex++] = '=';
  336. } else if (octetString.length - i == 1) {
  337. // store the octets
  338. bits24 = (octetString[i] & 0xFF) << 16;
  339. bits6 = (bits24 & 0x00FC0000) >> 18;
  340. out[outIndex++] = alphabet[bits6];
  341. bits6 = (bits24 & 0x0003F000) >> 12;
  342. out[outIndex++] = alphabet[ bits6 ];
  343. // padding
  344. out[outIndex++] = '=';
  345. out[outIndex++] = '=';
  346. }
  347. return new String(out);
  348. }
  349. }
  350. }