1   /*
2    * Copyright 2002-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  package de.pdark.dsmp;
17  
18  import java.io.BufferedOutputStream;
19  import java.io.File;
20  import java.io.FileOutputStream;
21  import java.io.FileReader;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.net.URL;
26  
27  import org.apache.commons.httpclient.Credentials;
28  import org.apache.commons.httpclient.Header;
29  import org.apache.commons.httpclient.HostConfiguration;
30  import org.apache.commons.httpclient.HttpClient;
31  import org.apache.commons.httpclient.HttpStatus;
32  import org.apache.commons.httpclient.UsernamePasswordCredentials;
33  import org.apache.commons.httpclient.auth.AuthScope;
34  import org.apache.commons.httpclient.methods.GetMethod;
35  import org.apache.log4j.Logger;
36  
37  /**
38   * Download a file via a proxy server and store it somewhere.
39   * 
40   * @author digulla
41   *
42   */
43  public class ProxyDownload
44  {
45      public static final Logger log = Logger.getLogger(ProxyDownload.class);
46      private final URL url;
47      private final File dest;
48  
49      /**
50       * Download <code>url</code> to <code>dest</code>.
51       * 
52       * <p>If the directory to store <code>dest</code> doesn't exist,
53       * it will be created.
54       * 
55       * @param url The resource to download
56       * @param dest Where to store it.
57       */
58      public ProxyDownload (URL url, File dest)
59      {
60          this.url = url;
61          this.dest = dest;
62      }
63      
64      /**
65       * Create the neccessary paths to store the destination file.
66       * 
67       * @throws IOException
68       */
69      public void mkdirs () throws IOException
70      {
71          File parent = dest.getParentFile();
72          IOUtils.mkdirs (parent);
73      }
74      
75      /**
76       * Do the download.
77       * 
78       * @throws IOException
79       * @throws DownloadFailed
80       */
81      public void download () throws IOException, DownloadFailed
82      {
83          if (!Config.isAllowed(url))
84          {
85              throw new DownloadFailed ("HTTP/1.1 "+HttpStatus.SC_FORBIDDEN+" Download denied by rule in DSMP config");
86          }
87          
88          // If there is a status file in the cache, return it instead of trying it again
89          // As usual with caches, this one is a key area which will always cause
90          // trouble.
91          // TODO There should be a simple way to get rid of the cached statuses
92          // TODO Maybe retry a download after a certain time?
93          File statusFile = new File (dest.getAbsolutePath()+".status");
94          if (statusFile.exists())
95          {
96              try
97              {
98                  FileReader r = new FileReader (statusFile);
99                  char[] buffer = new char[(int)statusFile.length()];
100                 int len = r.read (buffer);
101                 r.close ();
102                 String status = new String (buffer, 0, len);
103                 throw new DownloadFailed (status);
104             }
105             catch (IOException e)
106             {
107                 log.warn ("Error writing 'File not found'-Status to "+statusFile.getAbsolutePath(), e);
108             }
109         }
110         
111         mkdirs();
112         
113         HttpClient client = new HttpClient();
114 
115         String msg = "";
116         if (Config.useProxy(url))
117         {
118             Credentials defaultcreds = new UsernamePasswordCredentials(Config.getProxyUsername(), Config.getProxyPassword());
119             AuthScope scope = new AuthScope(Config.getProxyHost(), Config.getProxyPort(), AuthScope.ANY_REALM);
120             HostConfiguration hc = new HostConfiguration ();
121             hc.setProxy(Config.getProxyHost(), Config.getProxyPort());
122             client.setHostConfiguration(hc);
123             client.getState().setProxyCredentials(scope, defaultcreds);
124             msg = "via proxy ";
125         }
126         log.info("Downloading "+msg+"to "+dest.getAbsolutePath());
127         
128         GetMethod get = new GetMethod(url.toString());
129         get.setFollowRedirects(true);
130         try
131         {
132             int status = client.executeMethod( get );
133 
134             log.info ("Download status: "+status);
135             if (0 == 1 && log.isDebugEnabled())
136             {
137                 Header[] header = get.getResponseHeaders();
138                 for (int i=0; i<header.length; i++)
139                     log.debug (header[i].toString().trim());
140             }
141             
142             log.info ("Content: "+valueOf (get.getResponseHeader("Content-Length"))+" bytes; "
143                     +valueOf (get.getResponseHeader("Content-Type")));
144             
145             if (status != HttpStatus.SC_OK)
146             {
147                 // Remember "File not found"
148                 if (status == HttpStatus.SC_NOT_FOUND)
149                 {
150                     try
151                     {
152                         FileWriter w = new FileWriter (statusFile);
153                         w.write (get.getStatusLine().toString());
154                         w.close ();
155                     }
156                     catch (IOException e)
157                     {
158                         log.warn ("Error writing 'File not found'-Status to "+statusFile.getAbsolutePath(), e);
159                     }
160                 }
161                 throw new DownloadFailed (get);
162             }
163             
164             File dl = new File (dest.getAbsolutePath()+".new");
165             OutputStream out = new BufferedOutputStream (new FileOutputStream (dl));
166             IOUtils.copy (get.getResponseBodyAsStream(), out);
167             out.close ();
168             
169             File bak = new File (dest.getAbsolutePath()+".bak");
170             if (bak.exists())
171                 bak.delete();
172             if (dest.exists())
173                 dest.renameTo(bak);
174             dl.renameTo(dest);
175         }
176         finally
177         {
178             get.releaseConnection();
179         }
180     }
181 
182     private String valueOf (Header responseHeader)
183     {
184         return responseHeader == null ? "unknown" : responseHeader.getValue();
185     }
186 }