001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.vfs2.provider;
018
019 import org.apache.commons.vfs2.FileName;
020 import org.apache.commons.vfs2.FileSystemException;
021 import org.apache.commons.vfs2.FileType;
022 import org.apache.commons.vfs2.util.Cryptor;
023 import org.apache.commons.vfs2.util.CryptorFactory;
024
025 /**
026 * Implementation for any url based filesystem.<br />
027 * Parses the url into user/password/host/port/path<br />
028 * Does not handle a query string (after ?)
029 *
030 * @author <a href="http://commons.apache.org/vfs/team-list.html">Commons VFS team</a>
031 * @see URLFileNameParser URLFileNameParser for the implementation which also handles the query string too
032 */
033 public class HostFileNameParser extends AbstractFileNameParser
034 {
035 private final int defaultPort;
036
037 public HostFileNameParser(final int defaultPort)
038 {
039 this.defaultPort = defaultPort;
040 }
041
042 public int getDefaultPort()
043 {
044 return defaultPort;
045 }
046
047 @Override
048 public boolean encodeCharacter(char ch)
049 {
050 return super.encodeCharacter(ch);
051 }
052
053 public FileName parseUri(final VfsComponentContext context, FileName base, final String filename)
054 throws FileSystemException
055 {
056 // FTP URI are generic URI (as per RFC 2396)
057 final StringBuilder name = new StringBuilder();
058
059 // Extract the scheme and authority parts
060 final Authority auth = extractToPath(filename, name);
061
062 // Decode and normalise the file name
063 UriParser.canonicalizePath(name, 0, name.length(), this);
064 UriParser.fixSeparators(name);
065 FileType fileType = UriParser.normalisePath(name);
066 final String path = name.toString();
067
068 return new GenericFileName(
069 auth.scheme,
070 auth.hostName,
071 auth.port,
072 defaultPort,
073 auth.userName,
074 auth.password,
075 path,
076 fileType);
077 }
078
079 /**
080 * Extracts the scheme, userinfo, hostname and port components of a
081 * generic URI.
082 *
083 * @param uri The absolute URI to parse.
084 * @param name Used to return the remainder of the URI.
085 */
086 protected Authority extractToPath(final String uri,
087 final StringBuilder name)
088 throws FileSystemException
089 {
090 final Authority auth = new Authority();
091
092 // Extract the scheme
093 auth.scheme = UriParser.extractScheme(uri, name);
094
095 // Expecting "//"
096 if (name.length() < 2 || name.charAt(0) != '/' || name.charAt(1) != '/')
097 {
098 throw new FileSystemException("vfs.provider/missing-double-slashes.error", uri);
099 }
100 name.delete(0, 2);
101
102 // Extract userinfo, and split into username and password
103 final String userInfo = extractUserInfo(name);
104 final String userName;
105 final String password;
106 if (userInfo != null)
107 {
108 int idx = userInfo.indexOf(':');
109 if (idx == -1)
110 {
111 userName = userInfo;
112 password = null;
113 }
114 else
115 {
116 userName = userInfo.substring(0, idx);
117 password = userInfo.substring(idx + 1);
118 }
119 }
120 else
121 {
122 userName = null;
123 password = null;
124 }
125 auth.userName = UriParser.decode(userName);
126 auth.password = UriParser.decode(password);
127
128 if (auth.password != null && auth.password.startsWith("{") && auth.password.endsWith("}"))
129 {
130 try
131 {
132 Cryptor cryptor = CryptorFactory.getCryptor();
133 auth.password = cryptor.decrypt(auth.password.substring(1, auth.password.length() - 1));
134 }
135 catch (Exception ex)
136 {
137 throw new FileSystemException("Unable to decrypt password", ex);
138 }
139 }
140
141 // Extract hostname, and normalise (lowercase)
142 final String hostName = extractHostName(name);
143 if (hostName == null)
144 {
145 throw new FileSystemException("vfs.provider/missing-hostname.error", uri);
146 }
147 auth.hostName = hostName.toLowerCase();
148
149 // Extract port
150 auth.port = extractPort(name, uri);
151
152 // Expecting '/' or empty name
153 if (name.length() > 0 && name.charAt(0) != '/')
154 {
155 throw new FileSystemException("vfs.provider/missing-hostname-path-sep.error", uri);
156 }
157
158 return auth;
159 }
160
161 /**
162 * Extracts the user info from a URI. The scheme:// part has been removed
163 * already.
164 */
165 protected String extractUserInfo(final StringBuilder name)
166 {
167 final int maxlen = name.length();
168 for (int pos = 0; pos < maxlen; pos++)
169 {
170 final char ch = name.charAt(pos);
171 if (ch == '@')
172 {
173 // Found the end of the user info
174 String userInfo = name.substring(0, pos);
175 name.delete(0, pos + 1);
176 return userInfo;
177 }
178 if (ch == '/' || ch == '?')
179 {
180 // Not allowed in user info
181 break;
182 }
183 }
184
185 // Not found
186 return null;
187 }
188
189 /**
190 * Extracts the hostname from a URI. The scheme://userinfo@ part has
191 * been removed.
192 */
193 protected String extractHostName(final StringBuilder name)
194 {
195 final int maxlen = name.length();
196 int pos = 0;
197 for (; pos < maxlen; pos++)
198 {
199 final char ch = name.charAt(pos);
200 if (ch == '/' || ch == ';' || ch == '?' || ch == ':'
201 || ch == '@' || ch == '&' || ch == '=' || ch == '+'
202 || ch == '$' || ch == ',')
203 {
204 break;
205 }
206 }
207 if (pos == 0)
208 {
209 return null;
210 }
211
212 final String hostname = name.substring(0, pos);
213 name.delete(0, pos);
214 return hostname;
215 }
216
217 /**
218 * Extracts the port from a URI. The scheme://userinfo@hostname
219 * part has been removed.
220 *
221 * @return The port, or -1 if the URI does not contain a port.
222 */
223 protected int extractPort(final StringBuilder name, final String uri) throws FileSystemException
224 {
225 if (name.length() < 1 || name.charAt(0) != ':')
226 {
227 return -1;
228 }
229
230 final int maxlen = name.length();
231 int pos = 1;
232 for (; pos < maxlen; pos++)
233 {
234 final char ch = name.charAt(pos);
235 if (ch < '0' || ch > '9')
236 {
237 break;
238 }
239 }
240
241 final String port = name.substring(1, pos);
242 name.delete(0, pos);
243 if (port.length() == 0)
244 {
245 throw new FileSystemException("vfs.provider/missing-port.error", uri);
246 }
247
248 return Integer.parseInt(port);
249 }
250
251 /**
252 * Parsed authority info (scheme, hostname, userinfo, port)
253 */
254 protected static class Authority
255 {
256 private String scheme;
257 private String hostName;
258 private String userName;
259 private String password;
260 private int port;
261
262 /** @since 2.0 */
263 public String getScheme()
264 {
265 return scheme;
266 }
267
268 /** @since 2.0 */
269 public void setScheme(String scheme)
270 {
271 this.scheme = scheme;
272 }
273
274 /** @since 2.0 */
275 public String getHostName()
276 {
277 return hostName;
278 }
279
280 /** @since 2.0 */
281 public void setHostName(String hostName)
282 {
283 this.hostName = hostName;
284 }
285
286 /** @since 2.0 */
287 public String getUserName()
288 {
289 return userName;
290 }
291
292 /** @since 2.0 */
293 public void setUserName(String userName)
294 {
295 this.userName = userName;
296 }
297
298 /** @since 2.0 */
299 public String getPassword()
300 {
301 return password;
302 }
303
304 /** @since 2.0 */
305 public void setPassword(String password)
306 {
307 this.password = password;
308 }
309
310 /** @since 2.0 */
311 public int getPort()
312 {
313 return port;
314 }
315
316 /** @since 2.0 */
317 public void setPort(int port)
318 {
319 this.port = port;
320 }
321 }
322 }