View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider;
18  
19  import org.apache.commons.vfs2.FileName;
20  import org.apache.commons.vfs2.FileSystemException;
21  import org.apache.commons.vfs2.FileType;
22  import org.apache.commons.vfs2.util.Cryptor;
23  import org.apache.commons.vfs2.util.CryptorFactory;
24  
25  /**
26   * Implementation for any url based filesystem.
27   * <p>
28   * Parses the url into user/password/host/port/path.
29   * Does not handle a query string (after ?)
30   *
31   * @see URLFileNameParser URLFileNameParser for the implementation which also handles the query string too
32   */
33  public class HostFileNameParser extends AbstractFileNameParser
34  {
35      private final int defaultPort;
36  
37      public HostFileNameParser(final int defaultPort)
38      {
39          this.defaultPort = defaultPort;
40      }
41  
42      public int getDefaultPort()
43      {
44          return defaultPort;
45      }
46  
47      @Override
48      public FileName parseUri(final VfsComponentContext context, final FileName base, final String filename)
49              throws FileSystemException
50      {
51          // FTP URI are generic URI (as per RFC 2396)
52          final StringBuilder name = new StringBuilder();
53  
54          // Extract the scheme and authority parts
55          final Authority auth = extractToPath(filename, name);
56  
57          // Decode and normalise the file name
58          UriParser.canonicalizePath(name, 0, name.length(), this);
59          UriParser.fixSeparators(name);
60          final FileType fileType = UriParser.normalisePath(name);
61          final String path = name.toString();
62  
63          return new GenericFileName(
64              auth.scheme,
65              auth.hostName,
66              auth.port,
67              defaultPort,
68              auth.userName,
69              auth.password,
70              path,
71              fileType);
72      }
73  
74      /**
75       * Extracts the scheme, userinfo, hostname and port components of a
76       * generic URI.
77       *
78       * @param uri  The absolute URI to parse.
79       * @param name Used to return the remainder of the URI.
80       * @return Authority extracted host authority, never null.
81       * @throws FileSystemException if authority cannot be extracted.
82       */
83      protected Authority extractToPath(final String uri,
84                                        final StringBuilder name)
85          throws FileSystemException
86      {
87          final Authority auth = new Authority();
88  
89          // Extract the scheme
90          auth.scheme = UriParser.extractScheme(uri, name);
91  
92          // Expecting "//"
93          if (name.length() < 2 || name.charAt(0) != '/' || name.charAt(1) != '/')
94          {
95              throw new FileSystemException("vfs.provider/missing-double-slashes.error", uri);
96          }
97          name.delete(0, 2);
98  
99          // Extract userinfo, and split into username and password
100         final String userInfo = extractUserInfo(name);
101         final String userName;
102         final String password;
103         if (userInfo != null)
104         {
105             final int idx = userInfo.indexOf(':');
106             if (idx == -1)
107             {
108                 userName = userInfo;
109                 password = null;
110             }
111             else
112             {
113                 userName = userInfo.substring(0, idx);
114                 password = userInfo.substring(idx + 1);
115             }
116         }
117         else
118         {
119             userName = null;
120             password = null;
121         }
122         auth.userName = UriParser.decode(userName);
123         auth.password = UriParser.decode(password);
124 
125         if (auth.password != null && auth.password.startsWith("{") && auth.password.endsWith("}"))
126         {
127             try
128             {
129                 final Cryptor cryptor = CryptorFactory.getCryptor();
130                 auth.password = cryptor.decrypt(auth.password.substring(1, auth.password.length() - 1));
131             }
132             catch (final Exception ex)
133             {
134                 throw new FileSystemException("Unable to decrypt password", ex);
135             }
136         }
137 
138         // Extract hostname, and normalise (lowercase)
139         final String hostName = extractHostName(name);
140         if (hostName == null)
141         {
142             throw new FileSystemException("vfs.provider/missing-hostname.error", uri);
143         }
144         auth.hostName = hostName.toLowerCase();
145 
146         // Extract port
147         auth.port = extractPort(name, uri);
148 
149         // Expecting '/' or empty name
150         if (name.length() > 0 && name.charAt(0) != '/')
151         {
152             throw new FileSystemException("vfs.provider/missing-hostname-path-sep.error", uri);
153         }
154 
155         return auth;
156     }
157 
158     /**
159      * Extracts the user info from a URI.
160      *
161      * @param name string buffer with the "scheme://" part has been removed already. Will be modified.
162      * @return the user information up to the '@' or null.
163      */
164     protected String extractUserInfo(final StringBuilder name)
165     {
166         final int maxlen = name.length();
167         for (int pos = 0; pos < maxlen; pos++)
168         {
169             final char ch = name.charAt(pos);
170             if (ch == '@')
171             {
172                 // Found the end of the user info
173                 final String userInfo = name.substring(0, pos);
174                 name.delete(0, pos + 1);
175                 return userInfo;
176             }
177             if (ch == '/' || ch == '?')
178             {
179                 // Not allowed in user info
180                 break;
181             }
182         }
183 
184         // Not found
185         return null;
186     }
187 
188     /**
189      * Extracts the hostname from a URI.
190      *
191      * @param name string buffer with the "scheme://[userinfo@]" part has been removed already. Will be modified.
192      * @return the host name  or null.
193      */
194     protected String extractHostName(final StringBuilder name)
195     {
196         final int maxlen = name.length();
197         int pos = 0;
198         for (; pos < maxlen; pos++)
199         {
200             final char ch = name.charAt(pos);
201             if (ch == '/' || ch == ';' || ch == '?' || ch == ':'
202                 || ch == '@' || ch == '&' || ch == '=' || ch == '+'
203                 || ch == '$' || ch == ',')
204             {
205                 break;
206             }
207         }
208         if (pos == 0)
209         {
210             return null;
211         }
212 
213         final String hostname = name.substring(0, pos);
214         name.delete(0, pos);
215         return hostname;
216     }
217 
218     /**
219      * Extracts the port from a URI.
220      * @param name string buffer with the "scheme://[userinfo@]hostname" part has been removed already.
221      *     Will be modified.
222      * @param uri full URI for error reporting.
223      * @return The port, or -1 if the URI does not contain a port.
224      * @throws FileSystemException if URI is malformed.
225      * @throws NumberFormatException if port number cannot be parsed.
226      */
227     protected int extractPort(final StringBuilder name, final String uri) throws FileSystemException
228     {
229         if (name.length() < 1 || name.charAt(0) != ':')
230         {
231             return -1;
232         }
233 
234         final int maxlen = name.length();
235         int pos = 1;
236         for (; pos < maxlen; pos++)
237         {
238             final char ch = name.charAt(pos);
239             if (ch < '0' || ch > '9')
240             {
241                 break;
242             }
243         }
244 
245         final String port = name.substring(1, pos);
246         name.delete(0, pos);
247         if (port.length() == 0)
248         {
249             throw new FileSystemException("vfs.provider/missing-port.error", uri);
250         }
251 
252         return Integer.parseInt(port);
253     }
254 
255     /**
256      * Parsed authority info (scheme, hostname, username/password, port).
257      */
258     protected static class Authority
259     {
260         private String scheme;
261         private String hostName;
262         private String userName;
263         private String password;
264         private int port;
265 
266         /**
267          * Get the connection schema.
268          * @return the connection scheme.
269          * @since 2.0
270          */
271         public String getScheme()
272         {
273             return scheme;
274         }
275 
276         /**
277          * Set the connection schema.
278          * @param scheme the connection scheme.
279          * @since 2.0
280          */
281         public void setScheme(final String scheme)
282         {
283             this.scheme = scheme;
284         }
285 
286         /**
287          * Get the host name.
288          * @return the host name.
289          * @since 2.0
290          */
291         public String getHostName()
292         {
293             return hostName;
294         }
295 
296         /**
297          * Set the host name.
298          * @param hostName the host name.
299          * @since 2.0
300          */
301         public void setHostName(final String hostName)
302         {
303             this.hostName = hostName;
304         }
305 
306         /**
307          * Get the user name.
308          * @return the user name or null.
309          * @since 2.0
310          */
311         public String getUserName()
312         {
313             return userName;
314         }
315 
316         /**
317          * Set the user name.
318          * @param userName the user name.
319          * @since 2.0
320          */
321         public void setUserName(final String userName)
322         {
323             this.userName = userName;
324         }
325 
326         /**
327          * Get the user password.
328          * @return the password or null.
329          * @since 2.0
330          */
331         public String getPassword()
332         {
333             return password;
334         }
335 
336         /**
337          * Set the user password.
338          * @param password the user password.
339          * @since 2.0
340          */
341         public void setPassword(final String password)
342         {
343             this.password = password;
344         }
345 
346         /**
347          * Get the port.
348          * @return the port or -1.
349          * @since 2.0
350          */
351         public int getPort()
352         {
353             return port;
354         }
355 
356         /**
357          * Set the connection port.
358          * @param port the port number or -1.
359          * @since 2.0
360          */
361         public void setPort(final int port)
362         {
363             this.port = port;
364         }
365     }
366 }