View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.file;
21  
22  import org.waarp.common.command.exception.CommandAbstractException;
23  import org.waarp.common.command.exception.Reply501Exception;
24  import org.waarp.common.command.exception.Reply530Exception;
25  import org.waarp.common.command.exception.Reply550Exception;
26  import org.waarp.common.command.exception.Reply553Exception;
27  import org.waarp.common.logging.WaarpLogger;
28  import org.waarp.common.logging.WaarpLoggerFactory;
29  import org.waarp.common.utility.DetectionUtils;
30  import org.waarp.common.utility.ParametersChecker;
31  import org.waarp.common.utility.WaarpStringUtils;
32  
33  import java.io.File;
34  import java.io.IOException;
35  import java.io.UnsupportedEncodingException;
36  import java.net.URLDecoder;
37  import java.util.ArrayList;
38  import java.util.List;
39  
40  /**
41   * Abstract Main Implementation of Directory
42   */
43  public abstract class AbstractDir implements DirInterface {
44    /**
45     * Internal Logger
46     */
47    private static final WaarpLogger logger =
48        WaarpLoggerFactory.getLogger(AbstractDir.class);
49    /**
50     * Current Directory
51     */
52    protected String currentDir;
53    /**
54     * SessionInterface
55     */
56    protected SessionInterface session;
57  
58    /**
59     * Opts command for MLSx. (-1) means not supported, 0 supported but not
60     * active, 1 supported and active
61     */
62    protected OptsMLSxInterface optsMLSx;
63    /**
64     * Hack to say Windows or Unix (root like X:\ or /)
65     */
66    protected static final boolean ISUNIX;
67    /**
68     * Roots for Windows system
69     */
70    protected static final File[] roots;
71  
72    /*
73     * Init Windows Support
74     */
75    static {
76      ISUNIX = !DetectionUtils.isWindows();
77      if (!ISUNIX) {
78        roots = File.listRoots();
79      } else {
80        roots = new File[] { new File("/") };
81      }
82    }
83  
84    /**
85     * @param file
86     *
87     * @return The corresponding Root file
88     */
89    protected final File getCorrespondingRoot(final File file) {
90      if (ISUNIX) {
91        return new File("/");
92      }
93      final String path = file.getAbsolutePath();
94      for (final File root : roots) {
95        if (path.startsWith(root.getAbsolutePath())) {
96          return root;
97        }
98      }
99      // hack !
100     logger.warn("No root found for " + file.getAbsolutePath());
101     return roots[0];
102   }
103 
104   /**
105    * Normalize Path to Internal unique representation
106    *
107    * @param path
108    *
109    * @return the normalized path
110    */
111   public static String normalizePath(final String path) {
112     return path.replace('\\', SEPARATORCHAR);
113   }
114 
115   /**
116    * Convert the URI representation of a file path to a simple path.
117    * <p>
118    * If the path is not an URI, this method does nothing.
119    *
120    * @param path
121    *
122    * @return the normalized path
123    */
124   public static String pathFromURI(String path) {
125 
126     if (path.startsWith("file://")) {
127       int charToRemove = 7;
128 
129       if (path.charAt(7) == '/' && path.charAt(9) == ':') {
130         charToRemove++;
131       }
132 
133       path = path.substring(charToRemove);
134       if (path.contains("%")) {
135         try {
136           path = URLDecoder.decode(path, WaarpStringUtils.UTF8.name());
137         } catch (final UnsupportedEncodingException e) {
138           logger.warn("Cannot convert filename to UTF-8: " + path);
139         } catch (final IllegalArgumentException e) {
140           // ignore: it was propably not url-encoded!
141         }
142       }
143     }
144     return path;
145   }
146 
147   /**
148    * @return the SessionInterface
149    */
150   @Override
151   public final SessionInterface getSession() {
152     return session;
153   }
154 
155   @Override
156   public final String validatePath(final String path)
157       throws CommandAbstractException {
158     String extDir;
159     if (isAbsolute(path)) {
160       extDir = path;
161       final File newDir = new File(extDir);
162       try {
163         return validatePath(newDir);
164       } catch (final Reply553Exception e) {
165         // ignore and continue as it could be not absolute
166       }
167     }
168     if (path.charAt(0) == SEPARATORCHAR) {
169       extDir = path;
170     } else {
171       extDir = currentDir + SEPARATOR + path;
172     }
173     // Get the baseDir (mount point)
174     final String baseDir = getSession().getAuth().getBaseDirectory();
175     // Get the translated real file path (removing '..')
176     final File newDir = new File(baseDir, extDir);
177     return validatePath(newDir);
178   }
179 
180   /**
181    * @param path
182    *
183    * @return True if the given Path is an absolute one under Windows System or
184    *     should be an absolute one on Unix
185    */
186   public final boolean isAbsolute(final String path) {
187     File file = new File(path);
188     if (!ISUNIX) {
189       return file.isAbsolute();
190     } else {
191       file = file.getParentFile();
192       return file != null && file.isAbsolute() && file.isDirectory() &&
193              !file.getAbsolutePath().equals(File.separator);
194     }
195   }
196 
197   /**
198    * Consolidate Path as relative or absolute path to an absolute path
199    *
200    * @param path
201    *
202    * @return the consolidated path
203    *
204    * @throws CommandAbstractException
205    */
206   protected final String consolidatePath(final String path)
207       throws CommandAbstractException {
208     if (ParametersChecker.isEmpty(path)) {
209       throw new Reply501Exception("Path must not be empty");
210     }
211     // First check if the path is relative or absolute
212     if (isAbsolute(path)) {
213       return normalizePath(path);
214     }
215     String extDir = normalizePath(path);
216     if (extDir.charAt(0) != SEPARATORCHAR) {
217       extDir = currentDir + SEPARATOR + extDir;
218     }
219     return extDir;
220   }
221 
222   /**
223    * Construct the CanonicalPath without taking into account symbolic link
224    *
225    * @param dir
226    *
227    * @return the canonicalPath
228    */
229   protected final String getCanonicalPath(final File dir) {
230     if (ISUNIX) {
231       // resolve it without getting symbolic links
232       final StringBuilder builder = new StringBuilder();
233       // Get the path in reverse order from end to start
234       final List<String> list = new ArrayList<String>();
235       File newdir = dir;
236       String lastdir = newdir.getName();
237       list.add(lastdir);
238       File parent = newdir.getParentFile();
239       while (parent != null) {
240         newdir = parent;
241         lastdir = newdir.getName();
242         list.add(lastdir);
243         parent = newdir.getParentFile();
244       }
245       // Now filter on '..' or '.'
246       for (int i = list.size() - 1; i >= 0; i--) {
247         final String curdir = list.get(i);
248         if (".".equals(curdir)) {
249           list.remove(i);// removes '.'
250         } else if ("..".equals(curdir)) {
251           list.remove(i);// removes '..'
252           final int len = list.size();
253           if (len > 0 && i < len) {
254             list.remove(i);// and removes parent dir
255           }
256         }
257       }
258       if (list.isEmpty()) {
259         return "/";
260       }
261 
262       for (int i = list.size() - 1; i >= 0; i--) {
263         builder.append('/').append(list.get(i));
264       }
265       return builder.toString();
266     }
267     // Windows version
268     // no link so just use the default version of canonical Path
269     try {
270       return dir.getCanonicalPath();
271     } catch (final IOException e) {
272       return dir.getAbsolutePath();
273     }
274   }
275 
276   /**
277    * Same as validatePath but from a FileInterface
278    *
279    * @param dir
280    *
281    * @return the construct and validated path (could be different than the one
282    *     given as argument, example: '..'
283    *     are removed)
284    *
285    * @throws CommandAbstractException
286    */
287   protected final String validatePath(final File dir)
288       throws CommandAbstractException {
289     String extDir;
290     extDir = normalizePath(getCanonicalPath(dir));
291     // Get the relative business path
292     extDir = getSession().getAuth().getRelativePath(extDir);
293     // Check if this business path is valid
294     if (getSession().getAuth().isBusinessPathValid(extDir)) {
295       return extDir;
296     }
297     throw new Reply553Exception("Pathname not allowed");
298   }
299 
300   @Override
301   public final boolean isPathInCurrentDir(final String path) {
302     final String extDir;
303     if (isAbsolute(path)) {
304       extDir = path;
305       final File newDir = new File(extDir);
306       return isPathInCurrentDir(newDir);
307     }
308     if (path.charAt(0) == SEPARATORCHAR) {
309       extDir = path;
310     } else {
311       extDir = currentDir + SEPARATOR + path;
312     }
313     // Get the baseDir (mount point)
314     final String baseDir = getSession().getAuth().getBaseDirectory();
315     // Get the translated real file path (removing '..')
316     final File newDir = new File(baseDir, extDir);
317     return isPathInCurrentDir(newDir);
318   }
319 
320   /**
321    * Validate a file according to the current Directory
322    *
323    * @param dir
324    *
325    * @return True if validated
326    *
327    * @throws CommandAbstractException
328    */
329   protected final boolean isPathInCurrentDir(final File dir) {
330     String extDir;
331     extDir = normalizePath(getCanonicalPath(dir));
332     // Get the relative business path
333     extDir = getSession().getAuth().getRelativePath(extDir);
334     // Check if this business path is valid
335     if (extDir.startsWith(currentDir)) {
336       return true;
337     }
338     logger.warn("File not OK: {} not in {}", extDir, currentDir);
339     return false;
340   }
341 
342   /**
343    * Finds all files matching a wildcard expression (based on '?', '~' or
344    * '*').
345    *
346    * @param pathWithWildcard The wildcard expression with a business
347    *     path.
348    *
349    * @return List of String as relative paths matching the wildcard
350    *     expression.
351    *     Those files are tested as valid
352    *     from business point of view. If Wildcard support is not active,
353    *     if the
354    *     path contains any wildcards,
355    *     it will throw an error.
356    *
357    * @throws CommandAbstractException
358    */
359   protected abstract List<String> wildcardFiles(String pathWithWildcard)
360       throws CommandAbstractException;
361 
362   @Override
363   public final String getPwd() {
364     return currentDir;
365   }
366 
367   @Override
368   public final boolean changeParentDirectory() throws CommandAbstractException {
369     return changeDirectory("..");
370   }
371 
372   @Override
373   public FileInterface setFile(final String path, final boolean append)
374       throws CommandAbstractException {
375     checkIdentify();
376     final String newpath = consolidatePath(path);
377     final List<String> paths = wildcardFiles(newpath);
378     if (paths.size() != 1) {
379       throw new Reply550Exception(
380           "File not found: " + paths.size() + " founds");
381     }
382     final String extDir = paths.get(0);
383     return newFile(extDir, append);
384   }
385 
386   @Override
387   public final void checkIdentify() throws Reply530Exception {
388     if (!getSession().getAuth().isIdentified()) {
389       throw new Reply530Exception("User not authentified");
390     }
391   }
392 
393   @Override
394   public final void clear() {
395     currentDir = null;
396   }
397 
398   @Override
399   public final void initAfterIdentification() {
400     currentDir = getSession().getAuth().getBusinessPath();
401   }
402 
403   @Override
404   public final OptsMLSxInterface getOptsMLSx() {
405     return optsMLSx;
406   }
407 
408 }