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