blob: 94ead313c15b7312c4ee54f1de4c624ded616a7c [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.sshd.common.scp;
import java.io.Serializable;
import java.util.Objects;
import org.apache.sshd.common.auth.MutableUserHolder;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* Represents a local or remote SCP location in the format {@code user@host:path}
* for a remote path and a simple path for a local one. If user is omitted for a
* remote path then current user is used.
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ScpLocation implements MutableUserHolder, Serializable, Cloneable {
public static final char HOST_PART_SEPARATOR = ':';
public static final char USERNAME_PART_SEPARATOR = '@';
private static final long serialVersionUID = 5450230457030600136L;
private String host;
private String username;
private String path;
public ScpLocation() {
this(null);
}
/**
* @param locSpec The location specification - ignored if {@code null}/empty
* @see #update(String, ScpLocation)
* @throws IllegalArgumentException if invalid specification
*/
public ScpLocation(String locSpec) {
update(locSpec, this);
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public boolean isLocal() {
return GenericUtils.isEmpty(getHost());
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
this.username = username;
}
/**
* Resolves the effective username to use for a remote location.
* If username not set then uses the current username
*
* @return The resolved username
* @see #getUsername()
* @see OsUtils#getCurrentUser()
*/
public String resolveUsername() {
String user = getUsername();
if (GenericUtils.isEmpty(user)) {
return OsUtils.getCurrentUser();
} else {
return user;
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
@Override
public int hashCode() {
return Objects.hash(getHost(), resolveUsername(), OsUtils.getComparablePath(getPath()));
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
ScpLocation other = (ScpLocation) obj;
if (this.isLocal() != other.isLocal()) {
return false;
}
String thisPath = OsUtils.getComparablePath(getPath());
String otherPath = OsUtils.getComparablePath(other.getPath());
if (!Objects.equals(thisPath, otherPath)) {
return false;
}
if (isLocal()) {
return true;
}
// we know other is also remote or we would not have reached this point
return Objects.equals(resolveUsername(), other.resolveUsername())
&& Objects.equals(getHost(), other.getHost());
}
@Override
public ScpLocation clone() {
try {
return getClass().cast(super.clone());
} catch (CloneNotSupportedException e) { // unexpected
throw new RuntimeException("Failed to clone " + toString(), e);
}
}
@Override
public String toString() {
String p = getPath();
if (isLocal()) {
return p;
}
return resolveUsername() + String.valueOf(USERNAME_PART_SEPARATOR)
+ getHost() + String.valueOf(HOST_PART_SEPARATOR) + p;
}
/**
* Parses a local or remote SCP location in the format {@code user@host:path}
*
* @param locSpec The location specification - ignored if {@code null}/empty
* @return The {@link ScpLocation} or {@code null} if no specification provider
* @throws IllegalArgumentException if invalid specification
* @see #update(String, ScpLocation)
*/
public static ScpLocation parse(String locSpec) {
return GenericUtils.isEmpty(locSpec) ? null : update(locSpec, new ScpLocation());
}
/**
* Parses a local or remote SCP location in the format {@code user@host:path}
*
* @param <L> Type of {@link ScpLocation} being updated
* @param locSpec The location specification - ignored if {@code null}/empty
* @param location The {@link ScpLocation} to update - never {@code null}
* @return The updated location (unless no specification)
* @throws IllegalArgumentException if invalid specification
*/
public static <L extends ScpLocation> L update(String locSpec, L location) {
ValidateUtils.checkNotNull(location, "No location to update");
if (GenericUtils.isEmpty(locSpec)) {
return location;
}
location.setHost(null);
location.setUsername(null);
int pos = locSpec.indexOf(HOST_PART_SEPARATOR);
if (pos < 0) { // assume a local path
location.setPath(locSpec);
return location;
}
/*
* NOTE !!! in such a case there may be confusion with a host named 'a',
* but there is a limit to how smart we can be...
*/
if ((pos == 1) && OsUtils.isWin32()) {
char drive = locSpec.charAt(0);
if (((drive >= 'a') && (drive <= 'z')) || ((drive >= 'A') && (drive <= 'Z'))) {
location.setPath(locSpec);
return location;
}
}
String login = locSpec.substring(0, pos);
ValidateUtils.checkTrue(pos < (locSpec.length() - 1), "Invalid remote specification (missing path): %s", locSpec);
location.setPath(locSpec.substring(pos + 1));
pos = login.indexOf(USERNAME_PART_SEPARATOR);
ValidateUtils.checkTrue(pos != 0, "Invalid remote specification (missing username): %s", locSpec);
if (pos < 0) {
location.setHost(login);
} else {
location.setUsername(login.substring(0, pos));
ValidateUtils.checkTrue(pos < (login.length() - 1), "Invalid remote specification (missing host): %s", locSpec);
location.setHost(login.substring(pos + 1));
}
return location;
}
}