org.apache.sshd.client.config.hosts.HostPatternsHolder Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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.client.config.hosts;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* @author Apache MINA SSHD Project
*/
public abstract class HostPatternsHolder {
/**
* Used in a host pattern to denote zero or more consecutive characters
*/
public static final char WILDCARD_PATTERN = '*';
public static final String ALL_HOSTS_PATTERN = String.valueOf(WILDCARD_PATTERN);
/**
* Used in a host pattern to denote any one character
*/
public static final char SINGLE_CHAR_PATTERN = '?';
/**
* Used to negate a host pattern
*/
public static final char NEGATION_CHAR_PATTERN = '!';
/**
* The available pattern characters
*/
public static final String PATTERN_CHARS
= new String(new char[] { WILDCARD_PATTERN, SINGLE_CHAR_PATTERN, NEGATION_CHAR_PATTERN });
/** Port value separator if non-standard port pattern used */
public static final char PORT_VALUE_DELIMITER = ':';
/** Non-standard port specification host pattern enclosure start delimiter */
public static final char NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM = '[';
/** Non-standard port specification host pattern enclosure end delimiter */
public static final char NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM = ']';
private Collection patterns = new LinkedList<>();
protected HostPatternsHolder() {
super();
}
public Collection getPatterns() {
return patterns;
}
public void setPatterns(Collection patterns) {
this.patterns = patterns;
}
/**
* Checks if a given host name / address matches the entry's host pattern(s)
*
* @param host The host name / address - ignored if {@code null}/empty
* @param port The connection port
* @return {@code true} if the name / address matches the pattern(s)
* @see #isHostMatch(String, Pattern)
*/
public boolean isHostMatch(String host, int port) {
return isHostMatch(host, port, getPatterns());
}
/**
* @param pattern The pattern to check - ignored if {@code null}/empty
* @return {@code true} if the pattern is not empty and contains no wildcard characters
* @see #WILDCARD_PATTERN
* @see #SINGLE_CHAR_PATTERN
* @see #SINGLE_CHAR_PATTERN
*/
public static boolean isSpecificHostPattern(String pattern) {
if (GenericUtils.isEmpty(pattern)) {
return false;
}
for (int index = 0; index < PATTERN_CHARS.length(); index++) {
char ch = PATTERN_CHARS.charAt(index);
if (pattern.indexOf(ch) >= 0) {
return false;
}
}
return true;
}
public static boolean isHostMatch(String host, int port, Collection patterns) {
if (GenericUtils.isEmpty(patterns)) {
return false;
}
boolean matchFound = false;
for (HostPatternValue pv : patterns) {
boolean negated = pv.isNegated();
/*
* If already found a match we are interested only in negations
*/
if (matchFound && (!negated)) {
continue;
}
if (!isHostMatch(host, pv.getPattern())) {
continue;
}
if (!isPortMatch(port, pv.getPort())) {
continue;
}
/*
* According to https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5:
*
* If a negated entry is matched, then the Host entry is ignored, regardless of whether any other patterns
* on the line match.
*/
if (negated) {
return false;
}
matchFound = true;
}
return matchFound;
}
/**
* @param port1 1st port value - if non-positive then assumed to be {@link SshConstants#DEFAULT_PORT DEFAULT_PORT}
* @param port2 2nd port value - if non-positive then assumed to be {@link SshConstants#DEFAULT_PORT DEFAULT_PORT}
* @return {@code true} if ports are effectively equal
*/
public static boolean isPortMatch(int port1, int port2) {
return SshConstants.TO_EFFECTIVE_PORT.applyAsInt(port1) == SshConstants.TO_EFFECTIVE_PORT.applyAsInt(port2);
}
/**
* Checks if a given host name / address matches a host pattern
*
* @param host The host name / address - ignored if {@code null}/empty
* @param pattern The host {@link Pattern} - ignored if {@code null}
* @return {@code true} if the name / address matches the pattern
*/
public static boolean isHostMatch(String host, Pattern pattern) {
if (GenericUtils.isEmpty(host) || (pattern == null)) {
return false;
}
Matcher m = pattern.matcher(host);
return m.matches();
}
public static List parsePatterns(CharSequence... patterns) {
return parsePatterns(GenericUtils.isEmpty(patterns) ? Collections.emptyList() : Arrays.asList(patterns));
}
public static List parsePatterns(Collection extends CharSequence> patterns) {
if (GenericUtils.isEmpty(patterns)) {
return Collections.emptyList();
}
List result = new ArrayList<>(patterns.size());
for (CharSequence p : patterns) {
result.add(ValidateUtils.checkNotNull(toPattern(p), "No pattern for %s", p));
}
return result;
}
/**
* Converts a host pattern string to a regular expression matcher. Note: pattern matching is case
* insensitive
*
* @param patternString The original pattern string - ignored if {@code null}/empty
* @return The regular expression matcher {@link Pattern} and the indication whether it is a negating
* pattern or not - {@code null} if no original string
* @see #NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM
* @see #NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM
* @see #WILDCARD_PATTERN
* @see #SINGLE_CHAR_PATTERN
* @see #NEGATION_CHAR_PATTERN
*/
public static HostPatternValue toPattern(CharSequence patternString) {
String pattern = GenericUtils.replaceWhitespaceAndTrim(Objects.toString(patternString, null));
if (GenericUtils.isEmpty(pattern)) {
return null;
}
int patternLen = pattern.length();
int port = 0;
// Check if non-standard port value used
StringBuilder sb = new StringBuilder(patternLen);
if (pattern.charAt(0) == HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM) {
int pos = GenericUtils.lastIndexOf(pattern, HostPatternsHolder.PORT_VALUE_DELIMITER);
ValidateUtils.checkTrue(pos > 0, "Missing non-standard port value delimiter in %s", pattern);
ValidateUtils.checkTrue(pos < (patternLen - 1), "Missing non-standard port value number in %s", pattern);
ValidateUtils.checkTrue(pattern.charAt(pos - 1) == HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM,
"Invalid non-standard port value host pattern enclosure delimiters in %s", pattern);
String csPort = pattern.substring(pos + 1, patternLen);
port = Integer.parseInt(csPort);
ValidateUtils.checkTrue((port > 0) && (port <= 0xFFFF), "Invalid non-start port value (%d) in %s", port, pattern);
pattern = pattern.substring(1, pos - 1);
patternLen = pattern.length();
}
boolean negated = false;
for (int curPos = 0; curPos < patternLen; curPos++) {
char ch = pattern.charAt(curPos);
ValidateUtils.checkTrue(isValidPatternChar(ch), "Invalid host pattern char in %s", pattern);
switch (ch) {
case '.': // need to escape it
sb.append('\\').append(ch);
break;
case SINGLE_CHAR_PATTERN:
sb.append('.');
break;
case WILDCARD_PATTERN:
sb.append(".*");
break;
case NEGATION_CHAR_PATTERN:
ValidateUtils.checkTrue(!negated, "Double negation in %s", pattern);
ValidateUtils.checkTrue(curPos == 0, "Negation must be 1st char: %s", pattern);
negated = true;
break;
default:
sb.append(ch);
}
}
return new HostPatternValue(Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE), port, negated);
}
/**
* Checks if the given character is valid for a host pattern. Valid characters are:
*
* - A-Z
* - a-z
* - 0-9
* - Underscore (_)
* - Hyphen (-)
* - Dot (.)
* - Colon (:)
* - Percent (%) for scoped ipv6
* - The {@link #WILDCARD_PATTERN}
* - The {@link #SINGLE_CHAR_PATTERN}
*
*
* @param ch The character to validate
* @return {@code true} if valid pattern character
*/
public static boolean isValidPatternChar(char ch) {
if ((ch <= ' ') || (ch >= 0x7E)) {
return false;
}
if ((ch >= 'a') && (ch <= 'z')) {
return true;
}
if ((ch >= 'A') && (ch <= 'Z')) {
return true;
}
if ((ch >= '0') && (ch <= '9')) {
return true;
}
if ("-_.:%".indexOf(ch) >= 0) {
return true;
}
return PATTERN_CHARS.indexOf(ch) >= 0;
}
}