package ch.cyberduck.core; /* * Copyright (c) 2005 David Kocher. All rights reserved. * http://cyberduck.ch/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Bug fixes, suggestions and comments should be sent to: * dkocher@cyberduck.ch */ import ch.cyberduck.core.serializer.Deserializer; import ch.cyberduck.core.serializer.DeserializerFactory; import ch.cyberduck.core.serializer.Serializer; import ch.cyberduck.core.serializer.SerializerFactory; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; //import com.enterprisedt.net.ftp.FTPConnectMode; //import com.ibm.icu.text.IDNA; //import com.ibm.icu.text.StringPrepParseException; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.URLDecoder; import java.net.UnknownHostException; import java.util.Date; import java.util.TimeZone; /** * @version $Id: Host.java 5648 2010-01-01 22:32:01Z dkocher $ */ public class Host implements Serializable { private static Logger log = Logger.getLogger(Host.class); /** * The protocol identifier. Must be one of <code>sftp</code>, <code>ftp</code> or <code>ftps</code> * @see Protocol#FTP * @see Protocol#FTP_TLS * @see Protocol#SFTP * @uml.property name="protocol" * @uml.associationEnd */ private Protocol protocol; /** * The port number to connect to * @see Protocol#getDefaultPort() * @uml.property name="port" */ private int port = -1; /** * The fully qualified hostname * @uml.property name="hostname" */ private String hostname = Preferences.instance().getProperty("connection.hostname.default"); /** * IDN normalized hostname * @uml.property name="punycode" */ private String punycode; /** * The given name by the user for the bookmark * @uml.property name="nickname" */ private String nickname; /** * The initial working directory if any * @uml.property name="defaultpath" */ private String defaultpath; /** * The credentials to authenticate with * @uml.property name="credentials" * @uml.associationEnd */ private Credentials credentials = new Credentials(); /** * The character encoding to use for file listings * @uml.property name="encoding" */ private String encoding; /** * The connect mode to use if FTP */ // private FTPConnectMode connectMode; /** * The maximum number of concurrent sessions to this host * @uml.property name="maxConnections" */ private Integer maxConnections; /** * The custom download folder * @uml.property name="downloadFolder" */ private String downloadFolder; /** * The timezone the server is living in * @uml.property name="timezone" */ private TimeZone timezone; /** * Arbitrary text * @uml.property name="comment" */ private String comment; /** * @uml.property name="webURL" */ private String webURL; /** * Last accessed timestamp * @uml.property name="timestamp" */ private Date timestamp; /** * New host with the default protocol * * @param hostname The hostname of the server */ public Host(String hostname) { this(Protocol.forName(Preferences.instance().getProperty("connection.protocol.default")), hostname); } /** * New host with the default protocol for this port * * @param hostname The hostname of the server * @param port The port number to connect to */ public Host(String hostname, int port) { this(Protocol.getDefaultProtocol(port), hostname, port); } /** * @param protocol * @param hostname */ public Host(Protocol protocol, String hostname) { this(protocol, hostname, protocol.getDefaultPort()); } /** * @param protocol The protocol to use, must be either Session.FTP or Session.SFTP * @param hostname The hostname of the server * @param port The port number to connect to */ public Host(Protocol protocol, String hostname, int port) { this(protocol, hostname, port, null); } /** * @param protocol * @param hostname * @param port * @param defaultpath */ public Host(Protocol protocol, String hostname, int port, String defaultpath) { this.setProtocol(protocol); this.setPort(port); this.setHostname(hostname); this.setDefaultPath(defaultpath); } /** * @param dict A valid bookmark dictionary */ public <T> Host(T dict) { this.init(dict); } /** * @param serialized A valid bookmark dictionary */ public <T> void init(T serialized) { final Deserializer dict = DeserializerFactory.createDeserializer(serialized); Object protocolObj = dict.stringForKey("Protocol"); if(protocolObj != null) { this.setProtocol(Protocol.forName(protocolObj.toString())); } Object hostnameObj = dict.stringForKey("Hostname"); if(hostnameObj != null) { this.setHostname(hostnameObj.toString()); } Object usernameObj = dict.stringForKey("Username"); if(usernameObj != null) { credentials.setUsername(usernameObj.toString()); } Object passwordObj = dict.stringForKey("Password"); if(passwordObj != null) { credentials.setPassword(passwordObj.toString()); } Object keyObj = dict.stringForKey("Private Key File"); if(keyObj != null) { this.getCredentials().setIdentity(LocalFactory.createLocal(keyObj.toString())); } Object portObj = dict.stringForKey("Port"); if(portObj != null) { this.setPort(Integer.parseInt(portObj.toString())); } Object pathObj = dict.stringForKey("Path"); if(pathObj != null) { this.setDefaultPath(pathObj.toString()); } Object nicknameObj = dict.stringForKey("Nickname"); if(nicknameObj != null) { this.setNickname(nicknameObj.toString()); } Object encodingObj = dict.stringForKey("Encoding"); if(encodingObj != null) { this.setEncoding(encodingObj.toString()); } Object connectModeObj = dict.stringForKey("FTP Connect Mode"); // if(connectModeObj != null) { // if(connectModeObj.toString().equals(FTPConnectMode.ACTIVE.toString())) { // this.setFTPConnectMode(FTPConnectMode.ACTIVE); // } // if(connectModeObj.toString().equals(FTPConnectMode.PASV.toString())) { // this.setFTPConnectMode(FTPConnectMode.PASV); // } // } Object connObj = dict.stringForKey("Maximum Connections"); if(connObj != null) { this.setMaxConnections(Integer.valueOf(connObj.toString())); } Object downloadObj = dict.stringForKey("Download Folder"); if(downloadObj != null) { this.setDownloadFolder(downloadObj.toString()); } Object timezoneObj = dict.stringForKey("Timezone"); if(timezoneObj != null) { this.setTimezone(TimeZone.getTimeZone(timezoneObj.toString())); } Object commentObj = dict.stringForKey("Comment"); if(commentObj != null) { this.setComment(commentObj.toString()); } Object urlObj = dict.stringForKey("Web URL"); if(urlObj != null) { this.setWebURL(urlObj.toString()); } Object accessObj = dict.stringForKey("Access Timestamp"); if(accessObj != null) { this.setTimestamp(new Date(Long.parseLong(accessObj.toString()))); } } public <T> T getAsDictionary() { final Serializer dict = SerializerFactory.createSerializer(); dict.setStringForKey(this.getProtocol().getIdentifier(), "Protocol"); dict.setStringForKey(this.getNickname(), "Nickname"); if(StringUtils.isNotBlank(this.getHostname())) { dict.setStringForKey(this.getHostname(), "Hostname"); } dict.setStringForKey(String.valueOf(this.getPort()), "Port"); if(StringUtils.isNotBlank(this.getCredentials().getUsername())) { dict.setStringForKey(this.getCredentials().getUsername(), "Username"); } if(StringUtils.isNotBlank(this.getDefaultPath())) { dict.setStringForKey(this.getDefaultPath(), "Path"); } if(StringUtils.isNotBlank(this.getEncoding())) { dict.setStringForKey(this.getEncoding(), "Encoding"); } if(null != this.getCredentials().getIdentity()) { dict.setStringForKey(this.getCredentials().getIdentity().toURL(), "Private Key File"); } // if(this.getProtocol().equals(Protocol.FTP) || this.getProtocol().equals(Protocol.FTP_TLS)) { // if(null != this.getFTPConnectMode()) { // if(this.getFTPConnectMode().equals(FTPConnectMode.ACTIVE)) { // dict.setStringForKey(FTPConnectMode.ACTIVE.toString(), "FTP Connect Mode"); // } // else if(this.getFTPConnectMode().equals(FTPConnectMode.PASV)) { // dict.setStringForKey(FTPConnectMode.PASV.toString(), "FTP Connect Mode"); // } // } // } if(null != this.getMaxConnections()) { dict.setStringForKey(String.valueOf(this.getMaxConnections()), "Maximum Connections"); } if(!this.isDefaultDownloadFolder()) { dict.setStringForKey(this.getDownloadFolder().getAbbreviatedPath(), "Download Folder"); } if(null != this.getTimezone()) { dict.setStringForKey(this.getTimezone().getID(), "Timezone"); } if(StringUtils.isNotBlank(this.getComment())) { dict.setStringForKey(this.getComment(), "Comment"); } if(!this.isDefaultWebURL()) { dict.setStringForKey(this.getWebURL(), "Web URL"); } if(null != this.getTimestamp()) { dict.setStringForKey(String.valueOf(this.getTimestamp().getTime()), "Access Timestamp"); } return dict.<T>getSerialized(); } /** * Parses URL in the format ftp://username:pass@hostname:portnumber/path/to/file * * @param input * @return */ public static Host parse(String input) { input = input.trim(); int begin = 0; int cut; Protocol protocol = null; if(input.indexOf("://", begin) != -1) { cut = input.indexOf("://", begin); protocol = Protocol.forScheme(input.substring(begin, cut)); if(null != protocol) { begin += cut - begin + 3; } } if(null == protocol) { protocol = Protocol.forName( Preferences.instance().getProperty("connection.protocol.default")); } String username = null; String password = null; if(protocol.equals(Protocol.FTP)) { username = Preferences.instance().getProperty("connection.login.anon.name"); } else { username = Preferences.instance().getProperty("connection.login.name"); } if(input.lastIndexOf('@') != -1) { if(input.indexOf(':', begin) != -1 && input.lastIndexOf('@') > input.indexOf(':', begin)) { // ':' is not for the port number but username:pass seperator cut = input.indexOf(':', begin); username = input.substring(begin, cut); begin += username.length() + 1; cut = input.lastIndexOf('@'); password = input.substring(begin, cut); begin += password.length() + 1; } else { //no password given cut = input.lastIndexOf('@'); username = input.substring(begin, cut); begin += username.length() + 1; } } String hostname = Preferences.instance().getProperty("connection.hostname.default"); if(StringUtils.isNotBlank(input)) { hostname = input.substring(begin, input.length()); } String path = null; int port = protocol.getDefaultPort(); if(input.indexOf(':', begin) != -1) { cut = input.indexOf(':', begin); hostname = input.substring(begin, cut); begin += hostname.length() + 1; try { String portString; if(input.indexOf('/', begin) != -1) { portString = input.substring(begin, input.indexOf('/', begin)); begin += portString.length(); try { path = URLDecoder.decode(input.substring(begin, input.length()), "UTF-8"); } catch(UnsupportedEncodingException e) { log.error(e.getMessage()); } } else { portString = input.substring(begin, input.length()); } port = Integer.parseInt(portString); } catch(NumberFormatException e) { log.warn("Invalid port number given"); } } else if(input.indexOf('/', begin) != -1) { cut = input.indexOf('/', begin); hostname = input.substring(begin, cut); begin += hostname.length(); try { path = URLDecoder.decode(input.substring(begin, input.length()), "UTF-8"); } catch(UnsupportedEncodingException e) { log.error(e.getMessage()); } } final Host h = new Host(protocol, hostname, port, path); h.setCredentials(username, password); return h; } // ---------------------------------------------------------- /** * @param defaultpath The path to change the working directory to upon connecting */ public void setDefaultPath(String defaultpath) { this.defaultpath = StringUtils.isNotBlank(defaultpath) ? defaultpath : null; } /** * @return Null if no default path is set */ public String getDefaultPath() { return this.defaultpath; } /** * @param username * @param password */ public void setCredentials(final String username, final String password) { credentials.setUsername(username); credentials.setPassword(password); } /** * @return * @uml.property name="credentials" */ public Credentials getCredentials() { return credentials; } /** * @param protocol The protocol to use or null to use the default protocol for this port number * @uml.property name="protocol" */ public void setProtocol(Protocol protocol) { log.debug("setProtocol:" + protocol); this.protocol = protocol != null ? protocol : Protocol.forName(Preferences.instance().getProperty("connection.protocol.default")); } /** * @return * @see Protocol#FTP * @see Protocol#FTP_TLS * @see Protocol#SFTP * @uml.property name="protocol" */ public Protocol getProtocol() { return this.protocol; } /** * The given name for this bookmark * @return The user-given name of this bookmark * @uml.property name="nickname" */ public String getNickname() { if(StringUtils.isEmpty(nickname)) { return this.getDefaultNickname(); } return nickname; } /** * @return The default given name of this bookmark */ private String getDefaultNickname() { if(StringUtils.isNotEmpty(this.getHostname())) { return this.getHostname() + " \u2013 " + this.getProtocol().getName(); } return ""; } /** * Sets a user-given name for this bookmark * @param nickname * @uml.property name="nickname" */ public void setNickname(String nickname) { this.nickname = nickname; } /** * @return User readable hostname * @uml.property name="hostname" */ public String getHostname() { return this.getHostname(false); } /** * @param punycode Use the ToASCII operation as defined in the IDNA RFC */ public String getHostname(boolean punycode) { if(punycode && Preferences.instance().getBoolean("connection.hostname.idn")) { if(null == this.punycode && StringUtils.isNotEmpty(this.hostname)) { // try { // Convenience function that implements the IDNToASCII operation as defined in // the IDNA RFC. This operation is done on complete domain names, e.g: "www.example.com". // It is important to note that this operation can fail. If it fails, then the input // domain name cannot be used as an Internationalized Domain Name and the application // should have methods defined to deal with the failure. // IDNA.DEFAULT Use default options, i.e., do not process unassigned code points // and do not use STD3 ASCII rules If unassigned code points are found // the operation fails with ParseException // final String idn = IDNA.convertIDNToASCII(this.hostname, IDNA.DEFAULT).toString(); // log.info("IDN hostname for " + this.hostname + ":" + idn); // this.punycode = idn; // } // catch(StringPrepParseException e) { // log.error("Cannot convert hostname to IDNA:" + e.getMessage()); // } } if(StringUtils.isNotEmpty(this.punycode)) { return this.punycode; } } return this.hostname; } /** * Sets the name for this host Also reverts the nickname if no custom nickname is set * @param hostname * @uml.property name="hostname" */ public void setHostname(String hostname) { log.debug("setHostname:" + hostname); this.hostname = hostname.trim(); this.punycode = null; } /** * @param port The port number to connect to or -1 to use the default port for this protocol * @uml.property name="port" */ public void setPort(int port) { this.port = port; if(-1 == port) { this.port = this.getProtocol().getDefaultPort(); } } /** * @return The port number a socket should be opened to * @uml.property name="port" */ public int getPort() { return this.port; } /** * The character encoding to be used with this host * @param encoding * @uml.property name="encoding" */ public void setEncoding(String encoding) { log.debug("setEncoding:" + encoding); this.encoding = encoding; } /** * @return The character encoding to be used when connecting to this server or null if the default encoding should be used * @uml.property name="encoding" */ public String getEncoding() { return this.encoding; } // public void setFTPConnectMode(FTPConnectMode connectMode) { // log.debug("setFTPConnectMode:" + connectMode); // this.connectMode = connectMode; // } // // /** // * @return The connect mode to be used when connecting // * to this server or null if the default connect mode should be used // */ // public FTPConnectMode getFTPConnectMode() { // return this.connectMode; // } /** * Set a custom number of concurrent sessions allowed for this host If not set, connection.pool.max is used. * @param n null to use the default value or -1 if no limit * @uml.property name="maxConnections" */ public void setMaxConnections(Integer n) { log.debug("setMaxConnections:" + n); this.maxConnections = n; } /** * @return The number of concurrent sessions allowed. -1 if unlimited or null if the default should be used * @uml.property name="maxConnections" */ public Integer getMaxConnections() { return this.maxConnections; } /** * Set a custom download folder instead of queue.download.folder * @param folder * @uml.property name="downloadFolder" */ public void setDownloadFolder(String folder) { log.debug("setDownloadFolder:" + folder); this.downloadFolder = LocalFactory.createLocal(folder).toURL(); } /** * The custom folder if any or the default download location * @return * @uml.property name="downloadFolder" */ public Local getDownloadFolder() { if(null == this.downloadFolder) { return LocalFactory.createLocal(Preferences.instance().getProperty("queue.download.folder")); } return LocalFactory.createLocal(this.downloadFolder); } /** * @return True if no custom download location is set */ public boolean isDefaultDownloadFolder() { return null == this.downloadFolder; } /** * Set a timezone for the remote server different from the local default timezone May be useful to display modification dates of remote files correctly using the local timezone * @param timezone * @uml.property name="timezone" */ public void setTimezone(TimeZone timezone) { log.debug("setTimezone:" + timezone); this.timezone = timezone; } /** * @return The custom timezone or null if not set * @uml.property name="timezone" */ public TimeZone getTimezone() { return this.timezone; } /** * @param comment * @uml.property name="comment" */ public void setComment(String comment) { this.comment = comment; } /** * @return * @uml.property name="comment" */ public String getComment() { return this.comment; } /** * @return * @uml.property name="webURL" */ public String getWebURL() { if(null == webURL) { return this.getDefaultWebURL(); } final String protocol = "^http(s)?://.*$"; if(!webURL.matches(protocol)) { webURL = "http://" + webURL; } return webURL; } /** * @return True if no custom web URL has been set */ public boolean isDefaultWebURL() { return this.getWebURL().equals(this.getDefaultWebURL()); } /** * @return */ public String getDefaultWebURL() { return "http://" + this.getHostname(); } /** * @param webURL * @uml.property name="webURL" */ public void setWebURL(String webURL) { this.webURL = webURL; } /** * @return * @uml.property name="timestamp" */ public Date getTimestamp() { return timestamp; } /** * @param timestamp * @uml.property name="timestamp" */ public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } /** * @return The IP address of the remote host if available * @throws UnknownHostException If the address cannot be resolved */ public String getIp() throws UnknownHostException { try { return InetAddress.getByName(hostname).toString(); } catch(UnknownHostException e) { throw new UnknownHostException(hostname + " cannot be resolved"); } } /** * @return * @see #toURL() */ @Override public String toString() { return this.toURL(); } /** * protocol://user@host:port * * @return The URL of the remote host including user login hostname and port */ public String toURL() { StringBuilder url = new StringBuilder(this.getProtocol().getScheme()); url.append("://"); if(StringUtils.isNotEmpty(this.getCredentials().getUsername())) { url.append(this.getCredentials().getUsername()).append("@"); } url.append(this.getHostname(true)); if(this.getPort() != this.getProtocol().getDefaultPort()) { url.append(":").append(this.getPort()); } return url.toString(); } @Override public boolean equals(Object other) { if(null == other) { return false; } if(other instanceof Host) { Host o = (Host) other; return this.getNickname().equals(o.getNickname()); } if(other instanceof String) { //hack to allow comparision in BookmarkMenuDelegate return this.getNickname().equals(other); } return false; } @Override public int hashCode() { return this.toURL().hashCode(); } public boolean isReachable() { return ReachabilityFactory.instance().isReachable(this); } public void diagnose() { ReachabilityFactory.instance().diagnose(this); } }