View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util.resource;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.MalformedURLException;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.net.URLConnection;
30  import java.nio.channels.FileChannel;
31  import java.nio.channels.ReadableByteChannel;
32  import java.nio.file.InvalidPathException;
33  import java.nio.file.StandardOpenOption;
34  import java.security.Permission;
35  
36  import org.eclipse.jetty.util.IO;
37  import org.eclipse.jetty.util.StringUtil;
38  import org.eclipse.jetty.util.URIUtil;
39  import org.eclipse.jetty.util.log.Log;
40  import org.eclipse.jetty.util.log.Logger;
41  
42  
43  /* ------------------------------------------------------------ */
44  /** File Resource.
45   *
46   * Handle resources of implied or explicit file type.
47   * This class can check for aliasing in the filesystem (eg case
48   * insensitivity).  By default this is turned on, or it can be controlled 
49   * by calling the static method @see FileResource#setCheckAliases(boolean)
50   * 
51   */
52  public class FileResource extends Resource
53  {
54      private static final Logger LOG = Log.getLogger(FileResource.class);
55  
56      /* ------------------------------------------------------------ */
57      private final File _file;
58      private final String _uri;
59      private final URI _alias;
60      
61      /* -------------------------------------------------------- */
62      public FileResource(URL url)
63          throws IOException, URISyntaxException
64      {
65          File file;
66          try
67          {
68              // Try standard API to convert URL to file.
69              file =new File(url.toURI());
70              assertValidPath(file.toString());
71          }
72          catch (URISyntaxException e) 
73          {
74              throw e;
75          }
76          catch (Exception e)
77          {
78              if (!url.toString().startsWith("file:"))
79                  throw new IllegalArgumentException("!file:");
80              
81              LOG.ignore(e);
82              try
83              {
84                  // Assume that File.toURL produced unencoded chars. So try encoding them.
85                  String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));           
86                  URI uri = new URI(file_url);
87                  if (uri.getAuthority()==null) 
88                      file = new File(uri);
89                  else
90                      file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
91              }
92              catch (Exception e2)
93              {
94                  LOG.ignore(e2);
95                  // Still can't get the file.  Doh! try good old hack!
96                  URLConnection connection=url.openConnection();
97                  Permission perm = connection.getPermission();
98                  file = new File(perm==null?url.getFile():perm.getName());
99              }
100         }
101         
102         _file=file;
103         _uri=normalizeURI(_file,url.toURI());
104         _alias=checkFileAlias(_file);
105     }
106 
107     /* -------------------------------------------------------- */
108     public FileResource(URI uri)
109     {
110         File file=new File(uri);
111         _file=file;
112         URI file_uri=_file.toURI();
113         _uri=normalizeURI(_file,uri);
114         assertValidPath(file.toString());
115 
116         // Is it a URI alias?
117         if (!URIUtil.equalsIgnoreEncodings(_uri,file_uri.toString()))
118             _alias=_file.toURI();
119         else
120             _alias=checkFileAlias(_file);
121     }
122 
123     /* -------------------------------------------------------- */
124     FileResource(File file)
125     {
126         assertValidPath(file.toString());
127         _file=file;
128         _uri=normalizeURI(_file,_file.toURI());
129         _alias=checkFileAlias(_file);
130     }
131 
132     /* -------------------------------------------------------- */
133     private static String normalizeURI(File file, URI uri)
134     {
135         String u =uri.toASCIIString();
136         if (file.isDirectory())
137         {
138             if(!u.endsWith("/"))
139                 u+="/";
140         } 
141         else if (file.exists() && u.endsWith("/"))
142             u=u.substring(0,u.length()-1);
143         return u;
144     }
145 
146     /* -------------------------------------------------------- */
147     private static URI checkFileAlias(File file)
148     {
149         try
150         {
151             String abs=file.getAbsolutePath();
152             String can=file.getCanonicalPath();
153 
154             if (!abs.equals(can))
155             {
156                 if (LOG.isDebugEnabled())
157                     LOG.debug("ALIAS abs={} can={}",abs,can);
158 
159                 URI alias=new File(can).toURI();
160                 // Have to encode the path as File.toURI does not!
161                 return new URI("file://"+URIUtil.encodePath(alias.getPath()));  
162             }
163         }
164         catch(Exception e)
165         {
166             LOG.warn("bad alias for {}: {}",file,e.toString());
167             LOG.debug(e);
168             try
169             {
170                 return new URI("http://eclipse.org/bad/canonical/alias");
171             }
172             catch(Exception e2)
173             {
174                 LOG.ignore(e2);
175                 throw new RuntimeException(e);
176             }
177         }
178 
179         return null;
180     }
181     
182     /* -------------------------------------------------------- */
183     @Override
184     public Resource addPath(String path)
185         throws IOException,MalformedURLException
186     {
187         assertValidPath(path);
188         path = org.eclipse.jetty.util.URIUtil.canonicalPath(path);
189 
190         if (path==null)
191             throw new MalformedURLException();   
192         
193         if ("/".equals(path))
194             return this;
195         
196         path=URIUtil.encodePath(path);
197         // The encoded path should be a suffix of the resource (give or take a directory / )
198         URI uri;
199         try
200         {
201             if (_file.isDirectory())
202             {
203                 // treat all paths being added as relative
204                 uri=new URI(URIUtil.addPaths(_uri,path));
205             }
206             else
207             {
208                 uri=new URI(_uri+path);
209             }
210         }
211         catch(final URISyntaxException e)
212         {
213             throw new InvalidPathException(path, e.getMessage());
214         }
215 
216         return new FileResource(uri);
217     }
218 
219     private void assertValidPath(String path)
220     {
221         int idx = StringUtil.indexOfControlChars(path);
222         if (idx >= 0)
223         {
224             throw new InvalidPathException(path, "Invalid Character at index " + idx);
225         }
226     }
227 
228     /* ------------------------------------------------------------ */
229     @Override
230     public URI getAlias()
231     {
232         return _alias;
233     }
234     
235     /* -------------------------------------------------------- */
236     /**
237      * Returns true if the resource exists.
238      */
239     @Override
240     public boolean exists()
241     {
242         return _file.exists();
243     }
244         
245     /* -------------------------------------------------------- */
246     /**
247      * Returns the last modified time
248      */
249     @Override
250     public long lastModified()
251     {
252         return _file.lastModified();
253     }
254 
255     /* -------------------------------------------------------- */
256     /**
257      * Returns true if the resource is a container/directory.
258      */
259     @Override
260     public boolean isDirectory()
261     {
262         return _file.exists() && _file.isDirectory() || _uri.endsWith("/");
263     }
264 
265     /* --------------------------------------------------------- */
266     /**
267      * Return the length of the resource
268      */
269     @Override
270     public long length()
271     {
272         return _file.length();
273     }
274         
275 
276     /* --------------------------------------------------------- */
277     /**
278      * Returns the name of the resource
279      */
280     @Override
281     public String getName()
282     {
283         return _file.getAbsolutePath();
284     }
285         
286     /* ------------------------------------------------------------ */
287     /**
288      * Returns an File representing the given resource or NULL if this
289      * is not possible.
290      */
291     @Override
292     public File getFile()
293     {
294         return _file;
295     }
296         
297     /* --------------------------------------------------------- */
298     /**
299      * Returns an input stream to the resource
300      */
301     @Override
302     public InputStream getInputStream() throws IOException
303     {
304         return new FileInputStream(_file);
305     }
306 
307     /* ------------------------------------------------------------ */
308     @Override
309     public ReadableByteChannel getReadableByteChannel() throws IOException
310     {
311         return FileChannel.open(_file.toPath(),StandardOpenOption.READ);
312     }
313         
314     /* --------------------------------------------------------- */
315     /**
316      * Deletes the given resource
317      */
318     @Override
319     public boolean delete()
320         throws SecurityException
321     {
322         return _file.delete();
323     }
324 
325     /* --------------------------------------------------------- */
326     /**
327      * Rename the given resource
328      */
329     @Override
330     public boolean renameTo( Resource dest)
331         throws SecurityException
332     {
333         if( dest instanceof FileResource)
334             return _file.renameTo( ((FileResource)dest)._file);
335         else
336             return false;
337     }
338 
339     /* --------------------------------------------------------- */
340     /**
341      * Returns a list of resources contained in the given resource
342      */
343     @Override
344     public String[] list()
345     {
346         String[] list =_file.list();
347         if (list==null)
348             return null;
349         for (int i=list.length;i-->0;)
350         {
351             if (new File(_file,list[i]).isDirectory() &&
352                 !list[i].endsWith("/"))
353                 list[i]+="/";
354         }
355         return list;
356     }
357     
358     /* ------------------------------------------------------------ */
359     /** 
360      * @param o
361      * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource. 
362      */
363     @Override
364     public boolean equals( Object o)
365     {
366         if (this == o)
367             return true;
368 
369         if (null == o || ! (o instanceof FileResource))
370             return false;
371 
372         FileResource f=(FileResource)o;
373         return f._file == _file || (null != _file && _file.equals(f._file));
374     }
375 
376     /* ------------------------------------------------------------ */
377     /**
378      * @return the hashcode.
379      */
380     @Override
381     public int hashCode()
382     {
383        return null == _file ? super.hashCode() : _file.hashCode();
384     }
385     
386     /* ------------------------------------------------------------ */
387     @Override
388     public void copyTo(File destination)
389         throws IOException
390     {
391         if (isDirectory())
392         {
393             IO.copyDir(getFile(),destination);
394         }
395         else
396         {
397             if (destination.exists())
398                 throw new IllegalArgumentException(destination+" exists");
399             IO.copy(getFile(),destination);
400         }
401     }
402 
403     @Override
404     public boolean isContainedIn(Resource r) throws MalformedURLException
405     {
406         return false;
407     }
408 
409     @Override
410     public void close()
411     {
412     }
413 
414     @Override
415     public URL getURL()
416     {
417         try
418         {
419             return new URL(_uri);
420         }
421         catch (MalformedURLException e)
422         {
423             throw new IllegalStateException(e);
424         }
425     }
426     
427     @Override
428     public URI getURI()
429     {
430         return _file.toURI();
431     }
432 
433     @Override
434     public String toString()
435     {
436         return _uri;
437     }
438 
439 }