blob: e602cc347f68b6c3e623c1a0383ecc37fb5fd656 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.vfs2.provider;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.VFS;
import org.apache.commons.vfs2.util.Cryptor;
import org.apache.commons.vfs2.util.CryptorFactory;
/**
* Implementation for any URL based file system.
* <p>
* Parses the URL into user/password/host/port/path. Does not handle a query string (after ?).
* </p>
*
* @see URLFileNameParser URLFileNameParser for the implementation which also handles the query string too.
*/
public class HostFileNameParser extends AbstractFileNameParser {
/**
* Parsed authority info (scheme, hostname, username/password, port).
*/
protected static class Authority {
private String hostName;
private String password;
private int port;
private String scheme;
private String userName;
/**
* Gets the host name.
*
* @return the host name.
* @since 2.0
*/
public String getHostName() {
return hostName;
}
/**
* Gets the user password.
*
* @return the password or null.
* @since 2.0
*/
public String getPassword() {
return password;
}
/**
* Gets the port.
*
* @return the port or -1.
* @since 2.0
*/
public int getPort() {
return port;
}
/**
* Get the connection schema.
*
* @return the connection scheme.
* @since 2.0
*/
public String getScheme() {
return scheme;
}
/**
* Gets the user name.
*
* @return the user name or null.
* @since 2.0
*/
public String getUserName() {
return userName;
}
/**
* Sets the host name.
*
* @param hostName the host name.
* @since 2.0
*/
public void setHostName(final String hostName) {
this.hostName = hostName;
}
/**
* Sets the user password.
*
* @param password the user password.
* @since 2.0
*/
public void setPassword(final String password) {
this.password = password;
}
/**
* Sets the connection port.
*
* @param port the port number or -1.
* @since 2.0
*/
public void setPort(final int port) {
this.port = port;
}
/**
* Sets the connection schema.
*
* @param scheme the connection scheme.
* @since 2.0
*/
public void setScheme(final String scheme) {
this.scheme = scheme;
}
/**
* Sets the user name.
*
* @param userName the user name.
* @since 2.0
*/
public void setUserName(final String userName) {
this.userName = userName;
}
}
private final int defaultPort;
public HostFileNameParser(final int defaultPort) {
this.defaultPort = defaultPort;
}
/**
* Extracts the hostname from a URI.
*
* @param name string buffer with the "scheme://[userinfo@]" part has been removed already. Will be modified.
* @return the host name or null.
*/
protected String extractHostName(final StringBuilder name) {
final int maxlen = name.length();
int pos = 0;
for (; pos < maxlen; pos++) {
final char ch = name.charAt(pos);
if (ch == '/' || ch == ';' || ch == '?' || ch == ':' || ch == '@' || ch == '&' || ch == '=' || ch == '+'
|| ch == '$' || ch == ',') {
break;
}
}
if (pos == 0) {
return null;
}
final String hostname = name.substring(0, pos);
name.delete(0, pos);
return hostname;
}
/**
* Extracts the port from a URI.
*
* @param name string buffer with the "scheme://[userinfo@]hostname" part has been removed already. Will be
* modified.
* @param uri full URI for error reporting.
* @return The port, or -1 if the URI does not contain a port.
* @throws FileSystemException if URI is malformed.
* @throws NumberFormatException if port number cannot be parsed.
*/
protected int extractPort(final StringBuilder name, final String uri) throws FileSystemException {
if (name.length() < 1 || name.charAt(0) != ':') {
return -1;
}
final int maxlen = name.length();
int pos = 1;
for (; pos < maxlen; pos++) {
final char ch = name.charAt(pos);
if (ch < '0' || ch > '9') {
break;
}
}
final String port = name.substring(1, pos);
name.delete(0, pos);
if (port.isEmpty()) {
throw new FileSystemException("vfs.provider/missing-port.error", uri);
}
return Integer.parseInt(port);
}
/**
* Extracts the scheme, userinfo, hostname and port components of a generic URI.
*
* @param uri The absolute URI to parse.
* @param name Used to return the remainder of the URI.
* @return Authority extracted host authority, never null.
* @throws FileSystemException if authority cannot be extracted.
* @deprecated Use {@link #extractToPath(VfsComponentContext, String, StringBuilder)}.
*/
@Deprecated
protected Authority extractToPath(final String uri, final StringBuilder name) throws FileSystemException {
return extractToPath(null, uri, name);
}
/**
* Extracts the scheme, userinfo, hostname and port components of a generic URI.
*
* @param context component context.
* @param uri The absolute URI to parse.
* @param name Used to return the remainder of the URI.
* @return Authority extracted host authority, never null.
* @throws FileSystemException if authority cannot be extracted.
*/
protected Authority extractToPath(final VfsComponentContext context, final String uri, final StringBuilder name) throws FileSystemException {
final Authority auth = new Authority();
final FileSystemManager fsm;
if (context != null) {
fsm = context.getFileSystemManager();
} else {
fsm = VFS.getManager();
}
// Extract the scheme
auth.scheme = UriParser.extractScheme(fsm.getSchemes(), uri, name);
// Expecting "//"
if (name.length() < 2 || name.charAt(0) != '/' || name.charAt(1) != '/') {
throw new FileSystemException("vfs.provider/missing-double-slashes.error", uri);
}
name.delete(0, 2);
// Extract userinfo, and split into username and password
final String userInfo = extractUserInfo(name);
final String userName;
final String password;
if (userInfo != null) {
final int idx = userInfo.indexOf(':');
if (idx == -1) {
userName = userInfo;
password = null;
} else {
userName = userInfo.substring(0, idx);
password = userInfo.substring(idx + 1);
}
} else {
userName = null;
password = null;
}
auth.userName = UriParser.decode(userName);
auth.password = UriParser.decode(password);
if (auth.password != null && auth.password.startsWith("{") && auth.password.endsWith("}")) {
try {
final Cryptor cryptor = CryptorFactory.getCryptor();
auth.password = cryptor.decrypt(auth.password.substring(1, auth.password.length() - 1));
} catch (final Exception ex) {
throw new FileSystemException("Unable to decrypt password", ex);
}
}
// Extract hostname, and normalize (lowercase)
final String hostName = extractHostName(name);
if (hostName == null) {
throw new FileSystemException("vfs.provider/missing-hostname.error", uri);
}
auth.hostName = hostName.toLowerCase();
// Extract port
auth.port = extractPort(name, uri);
// Expecting '/' or empty name
if (name.length() > 0 && name.charAt(0) != '/') {
throw new FileSystemException("vfs.provider/missing-hostname-path-sep.error", uri);
}
return auth;
}
/**
* Extracts the user info from a URI.
*
* @param name string buffer with the "scheme://" part has been removed already. Will be modified.
* @return the user information up to the '@' or null.
*/
protected String extractUserInfo(final StringBuilder name) {
final int maxlen = name.length();
for (int pos = 0; pos < maxlen; pos++) {
final char ch = name.charAt(pos);
if (ch == '@') {
// Found the end of the user info
final String userInfo = name.substring(0, pos);
name.delete(0, pos + 1);
return userInfo;
}
if (ch == '/' || ch == '?') {
// Not allowed in user info
break;
}
}
// Not found
return null;
}
public int getDefaultPort() {
return defaultPort;
}
@Override
public FileName parseUri(final VfsComponentContext context, final FileName base, final String fileName)
throws FileSystemException {
// FTP URI are generic URI (as per RFC 2396)
final StringBuilder name = new StringBuilder();
// Extract the scheme and authority parts
final Authority auth = extractToPath(context, fileName, name);
// Decode and normalize the file name
UriParser.canonicalizePath(name, 0, name.length(), this);
UriParser.fixSeparators(name);
final FileType fileType = UriParser.normalisePath(name);
final String path = name.toString();
return new GenericFileName(auth.scheme, auth.hostName, auth.port, defaultPort, auth.userName, auth.password,
path, fileType);
}
}