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