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.BufferedInputStream;
19  import java.io.BufferedOutputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.net.Socket;
27  import java.net.SocketException;
28  import java.net.URL;
29  import java.text.SimpleDateFormat;
30  import java.util.Date;
31  import java.util.HashMap;
32  
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.lang.SystemUtils;
35  import org.apache.log4j.Logger;
36  import org.codehaus.plexus.digest.DigesterException;
37  import org.codehaus.plexus.digest.Md5Digester;
38  import org.codehaus.plexus.digest.Sha1Digester;
39  
40  /**
41   * Handle a connection from a maven.
42   * 
43   * @author digulla
44   *
45   */
46  public class RequestHandler extends Thread
47  {
48      public static final Logger log = Logger.getLogger(RequestHandler.class);
49      
50      private Socket clientSocket;
51  
52      public RequestHandler (Socket clientSocket)
53      {
54          this.clientSocket = clientSocket;
55      }
56  
57      @Override
58      public void run ()
59      {
60          if (clientSocket == null)
61              throw new RuntimeException ("Connection is already closed");
62          
63          try
64          {
65              log.debug ("Got connection from "+clientSocket.getInetAddress());
66              
67              String line;
68              boolean keepAlive = false;
69              do
70              {
71                  String downloadURL = null;
72                  StringBuffer fullRequest = new StringBuffer (1024);
73                  while ((line = readLine ()) != null)
74                  {
75                      if (line.length() == 0)
76                          break;
77                      
78                      //log.debug ("Got: "+line);
79                      fullRequest.append (line);
80                      fullRequest.append ('\n');
81                      
82                      if ("Proxy-Connection: keep-alive".equals (line))
83                          keepAlive = true;
84                      
85                      if (line.startsWith("GET "))
86                      {
87                          int pos = line.lastIndexOf(' ');
88                          line = line.substring(4, pos);
89                          downloadURL = line;
90                      }
91                  }
92                  
93                  if (downloadURL == null)
94                  {
95                      if (line == null)
96                          break;
97                      
98                      log.error ("Found no URL to download in request:\n"+fullRequest.toString());
99                  }
100                 else
101                 {
102                     log.info ("Got request for "+downloadURL);
103                     serveURL (downloadURL);
104                 }
105             }
106             while (line != null && keepAlive);
107             
108             log.debug ("Terminating connection with "+clientSocket.getInetAddress());
109         }
110         catch (Exception e)
111         {
112             log.error ("Conversation with client aborted", e);
113         }
114         finally
115         {
116             close();
117         }
118     }
119 
120     public void close ()
121     {
122         try
123         {
124             if (out != null)
125                 out.close();
126         }
127         catch (Exception e)
128         {
129             log.error ("Exception while closing the outputstream", e);
130         }
131         out = null;
132         
133         try
134         {
135             if (in != null)
136                 in.close();
137         }
138         catch (Exception e)
139         {
140             log.error ("Exception while closing the inputstream", e);
141         }
142         in = null;
143 
144         try
145         {
146             if (clientSocket != null)
147                 clientSocket.close();
148         }
149         catch (Exception e)
150         {
151             log.error ("Exception while closing the socket", e);
152         }
153         clientSocket = null;
154     }
155 
156     private void serveURL (String downloadURL) throws IOException
157     {
158         URL url = new URL (downloadURL);
159         url = Config.getMirror (url);
160         
161         if (!"http".equals(url.getProtocol()))
162             throw new IOException ("Can only handle HTTP requests, got "+downloadURL);
163         
164         File f = getPatchFile(url);
165         if (!f.exists())
166             f = getCacheFile(url);
167         
168         if (!f.exists())
169         {
170             ProxyDownload d = new ProxyDownload (url, f);
171             try
172             {
173                 d.download();
174             }
175             catch (DownloadFailed e)
176             {
177                 log.error(e.getMessage());
178                 
179                 println (e.getStatusLine());
180                 println ();
181                 getOut().flush();
182                 return;
183             }
184         }
185         else
186         {
187             log.debug ("Serving from local cache "+f.getAbsolutePath());
188         }
189         
190         println ("HTTP/1.1 200 OK");
191         print ("Date: ");
192         Date d = new Date (f.lastModified());
193         println (INTERNET_FORMAT.format(d));
194         print ("Content-length: ");
195         println (String.valueOf(f.length()));
196         print ("Content-type: ");
197         String ext = StringUtils.substringAfterLast(downloadURL, ".").toLowerCase();
198         String type = CONTENT_TYPES.get (ext);
199         if (type == null)
200         {
201             log.warn("Unknown extension "+ext+". Using content type text/plain.");
202             type = "text/plain";
203         }
204         println (type);
205         println ();
206         InputStream data = new BufferedInputStream (new FileInputStream (f));
207         IOUtils.copy (data, out);
208         data.close();
209     }
210 
211     public static File getPatchFile (URL url)
212     {
213         File dir = Config.getPatchesDirectory();
214         File f = getCacheFile(url, dir);
215         
216         if (!f.exists())
217         {
218             String ext = StringUtils.substringAfterLast(url.getPath(), ".").toLowerCase();
219             if ("md5".equals (ext) || "sha1".equals (ext))
220             {
221                 File source = new File (StringUtils.substringBeforeLast(f.getAbsolutePath(), "."));
222                 if (source.exists())
223                 {
224                     generateChecksum (source, f, ext);
225                 }
226             }
227         }
228         
229         return f;
230     }
231     
232     public static void generateChecksum (File source, File f, String ext)
233     {
234         try
235         {
236             String checksum = null;
237             if ("md5".equals (ext))
238             {
239                 Md5Digester digester = new Md5Digester ();
240                 checksum = digester.calc(source);
241             }
242             else if ("sha1".equals (ext))
243             {
244                 Sha1Digester digester = new Sha1Digester ();
245                 checksum = digester.calc(source);
246             }
247             
248             if (checksum != null)
249             {
250                 FileWriter w = new FileWriter (f);
251                 w.write(checksum);
252                 w.write(SystemUtils.LINE_SEPARATOR);
253                 w.close ();
254             }
255         }
256         catch (DigesterException e)
257         {
258             log.warn ("Error creating "+ext.toUpperCase()+" checksum for "+source.getAbsolutePath(), e);
259         }
260         catch (IOException e)
261         {
262             log.warn ("Error writing "+ext.toUpperCase()+" checksum for "+source.getAbsolutePath()+" to "+f.getAbsolutePath(), e);
263         }
264         
265     }
266 
267     public static File getCacheFile (URL url)
268     {
269         File dir = Config.getCacheDirectory();
270         return getCacheFile(url, dir);
271     }
272 
273     public static File getCacheFile (URL url, File root)
274     {
275         root = new File (root, url.getHost());
276         if (url.getPort() != -1 && url.getPort() != 80)
277             root = new File (root, String.valueOf(url.getPort()));
278         File f = new File (root, url.getPath());
279         return f;
280     }
281 
282     public final static HashMap<String,String> CONTENT_TYPES = new HashMap<String,String> ();
283     static {
284         CONTENT_TYPES.put ("xml", "application/xml");
285         CONTENT_TYPES.put ("pom", "application/xml");
286         
287         CONTENT_TYPES.put ("jar", "application/java-archive");
288         
289         CONTENT_TYPES.put ("md5", "text/plain");
290         CONTENT_TYPES.put ("sha1", "text/plain");
291         CONTENT_TYPES.put ("asc", "text/plain");
292 
293         CONTENT_TYPES.put ("", "");
294         CONTENT_TYPES.put ("", "");
295         CONTENT_TYPES.put ("", "");
296         CONTENT_TYPES.put ("", "");
297         CONTENT_TYPES.put ("", "");
298         CONTENT_TYPES.put ("", "");
299         CONTENT_TYPES.put ("", "");
300         CONTENT_TYPES.put ("", "");
301         CONTENT_TYPES.put ("", "");
302         CONTENT_TYPES.put ("", "");
303         CONTENT_TYPES.put ("", "");
304         CONTENT_TYPES.put ("", "");
305         CONTENT_TYPES.put ("", "");
306         CONTENT_TYPES.put ("", "");
307     }
308     
309     private final static SimpleDateFormat INTERNET_FORMAT = new SimpleDateFormat ("EEE, d MMM yyyy HH:mm:ss zzz");
310     private byte[] NEW_LINE = new byte[] { '\r', '\n' };
311     
312     private void println (String string) throws IOException
313     {
314         print (string);
315         println ();
316     }
317 
318     private void println () throws IOException
319     {
320         getOut().write(NEW_LINE);
321     }
322     
323     private void print (String string) throws IOException
324     {
325         getOut().write (string.getBytes("ISO-8859-1"));
326     }
327 
328     private OutputStream out;
329     
330     protected OutputStream getOut () throws IOException
331     {
332         if (out == null)
333             out = new BufferedOutputStream (clientSocket.getOutputStream());
334         
335         return out;
336     }
337 
338     private BufferedInputStream in;
339     
340     private String readLine () throws IOException
341     {
342         if (in == null)
343             in = new BufferedInputStream (clientSocket.getInputStream());
344         
345         StringBuffer buffer = new StringBuffer (256);
346         int c;
347         
348         try
349         {
350             while ((c = in.read()) != -1)
351             {
352                 if (c == '\r')
353                     continue;
354                 
355                 if (c == '\n')
356                     break;
357                 
358                 buffer.append((char)c);
359             }
360         }
361         catch (SocketException e)
362         {
363             if ("Connection reset".equals (e.getMessage()))
364                 return null;
365             
366             throw e;
367         }
368 
369         if (c == -1)
370             return null;
371         
372         if (buffer.length() == 0)
373             return "";
374         
375         return buffer.toString();
376     }
377 }