All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vngx.jsch.Util Maven / Gradle / Ivy

Go to download

**vngx-jsch** (beta) is an updated version of the popular JSch SSH library written in pure Java. It has been updated to Java 6 with all the latest language features and improved code clarity.

There is a newer version: 0.10
Show newest version
/*
 * Copyright (c) 2002-2010 Atsuhiko Yamanaka, JCraft,Inc.  All rights reserved.
 * Copyright (c) 2010-2011 Michael Laudati, N1 Concepts LLC.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The names of the authors may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
 * INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.vngx.jsch;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import org.vngx.jsch.exception.JSchException;
import org.vngx.jsch.hash.Hash;
import org.vngx.jsch.hash.HashManager;

/**
 * Static utility class containing helper methods including Base64 encoding/
 * decoding, String utility methods and more.
 *
 * @author Atsuhiko Yamanaka
 * @author Michael Laudati
 */
public final class Util {

	/** Constant array of Base 64 characters. */
	private static final byte[] B64 = str2byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
	/** Constant array of hexadecimal characters. */
	private static final char[] HEXDEC_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

	/** MD5 hash implementation to use for generating fingerprints. */
	private static Hash $md5Hash;


	/** Private constructor to prevent instantiation of static utility class. */
	private Util() { }

	/**
	 * Decodes a byte array of base 64 encoded data.
	 *
	 * @param buffer containing base 64 encoded data
	 * @param start position in buffer
	 * @param length of data to decode
	 * @return decoded byte data
	 */
	public static byte[] fromBase64(byte[] buffer, int start, int length) {
		byte[] decoded = new byte[length];
		int j = 0;
		for( int i = start, len = start + length; i < len; i += 4 ) {
			decoded[j] = (byte) ((val(buffer[i]) << 2) | ((val(buffer[i + 1]) & 0x30) >>> 4));
			if( buffer[i + 2] == (byte) '=' ) {
				j++;
				break;
			}
			decoded[j + 1] = (byte) (((val(buffer[i + 1]) & 0x0f) << 4) | ((val(buffer[i + 2]) & 0x3c) >>> 2));
			if( buffer[i + 3] == (byte) '=' ) {
				j += 2;
				break;
			}
			decoded[j + 2] = (byte) (((val(buffer[i + 2]) & 0x03) << 6) | (val(buffer[i + 3]) & 0x3f));
			j += 3;
		}
		return Arrays.copyOf(decoded, j);
	}

	/**
	 * Returns the index of the specified byte value in the B64 array or 0 if
	 * not found or equal to '='.
	 *
	 * @param input value
	 * @return index in B64 array
	 */
	private static byte val(byte input) {
		if( input == '=' ) {
			return 0;
		}
		for( int j = 0; j < B64.length; j++ ) {
			if( input == B64[j] ) {
				return (byte) j;
			}
		}
		return 0;
	}

	/**
	 * Encodes a byte array of data to base 64 encoding.
	 *
	 * @param buffer containing data to encode
	 * @param start position in buffer
	 * @param length of data to encode
	 * @return base 64 encoded data
	 */
	public static byte[] toBase64(byte[] buffer, int start, int length) {
		byte[] encoded = new byte[length * 2];
		int i = 0, j, k;
		int tmp = (length / 3) * 3 + start; // TODO why divide then multiply by 3
		for( j = start; j < tmp; j += 3 ) {
			k = (buffer[j] >>> 2) & 0x3f;
			encoded[i++] = B64[k];
			k = (buffer[j] & 0x03) << 4 | (buffer[j + 1] >>> 4) & 0x0f;
			encoded[i++] = B64[k];
			k = (buffer[j + 1] & 0x0f) << 2 | (buffer[j + 2] >>> 6) & 0x03;
			encoded[i++] = B64[k];
			k = buffer[j + 2] & 0x3f;
			encoded[i++] = B64[k];
		}

		tmp = (start + length) - tmp;
		if( tmp == 1 ) {
			k = (buffer[j] >>> 2) & 0x3f;
			encoded[i++] = B64[k];
			k = ((buffer[j] & 0x03) << 4) & 0x3f;
			encoded[i++] = B64[k];
			encoded[i++] = (byte) '=';
			encoded[i++] = (byte) '=';
		} else if( tmp == 2 ) {
			k = (buffer[j] >>> 2) & 0x3f;
			encoded[i++] = B64[k];
			k = (buffer[j] & 0x03) << 4 | (buffer[j + 1] >>> 4) & 0x0f;
			encoded[i++] = B64[k];
			k = ((buffer[j + 1] & 0x0f) << 2) & 0x3f;
			encoded[i++] = B64[k];
			encoded[i++] = (byte) '=';
		}
		return Arrays.copyOf(encoded, i);
	}

	/**
	 * Splits the specified source string into pieces using the specified split
	 * value as the delimiter, returning null if source is null.
	 *
	 * @param source
	 * @param split
	 * @return array of delimited values
	 */
	public static String[] split(String source, String split) {
		return source != null ? source.split(split) : null;
	}

	/**
	 * Pattern matching function which returns true if the specified pattern
	 * matches the specified name.
	 *
	 * @param pattern
	 * @param name
	 * @return true if pattern matches name
	 */
	public static boolean glob(byte[] pattern, byte[] name) {
		return glob0(pattern, 0, name, 0);
	}

	/**
	 * Pattern matching function which returns true if the specified pattern
	 * matches the specified name starting from specified pattern index and
	 * name index.
	 *
	 * @param pattern
	 * @param patternIndex
	 * @param name
	 * @param nameIndex
	 * @return true if pattern matches name from given indexes
	 */
	private static boolean glob0(byte[] pattern, int patternIndex, byte[] name, int nameIndex) {
		if( name.length > 0 && name[0] == '.' ) {
			if( pattern.length > 0 && pattern[0] == '.' ) {
				if( pattern.length == 2 && pattern[1] == '*' ) {
					return true;
				}
				return glob(pattern, patternIndex + 1, name, nameIndex + 1);
			}
			return false;
		}
		return glob(pattern, patternIndex, name, nameIndex);
	}

	/**
	 * Pattern matching function which returns true if the specified pattern
	 * matches the specified name starting from specified pattern index and
	 * name index.
	 *
	 * @param pattern
	 * @param patternIndex
	 * @param name
	 * @param nameIndex
	 * @return true if pattern matches name from given indexes
	 */
	private static boolean glob(byte[] pattern, int patternIndex, byte[] name, int nameIndex) {
		int patternlen = pattern.length;
		if( patternlen == 0 ) {
			return false;
		}

		int namelen = name.length;
		int i = patternIndex;
		int j = nameIndex;

		while( i < patternlen && j < namelen ) {
			if( pattern[i] == '\\' ) {
				if( i + 1 == patternlen ) {
					return false;
				}
				i++;
				if( pattern[i] != name[j] ) {
					return false;
				}
				i += skipUTF8Char(pattern[i]);
				j += skipUTF8Char(name[j]);
				continue;
			}

			if( pattern[i] == '*' ) {
				while( i < patternlen ) {
					if( pattern[i] == '*' ) {
						i++;
						continue;
					}
					break;
				}
				if( patternlen == i ) {
					return true;
				}

				byte tmp = pattern[i];
				if( tmp == '?' ) {
					while( j < namelen ) {
						if( glob(pattern, i, name, j) ) {
							return true;
						}
						j += skipUTF8Char(name[j]);
					}
					return false;
				} else if( tmp == '\\' ) {
					if( i + 1 == patternlen ) {
						return false;
					}
					i++;
					tmp = pattern[i];
					while( j < namelen ) {
						if( tmp == name[j] && glob(pattern, i + skipUTF8Char(tmp), name, j + skipUTF8Char(name[j])) ) {
							return true;
						}
						j += skipUTF8Char(name[j]);
					}
					return false;
				}

				while( j < namelen ) {
					if( tmp == name[j] ) {
						if( glob(pattern, i, name, j) ) {
							return true;
						}
					}
					j += skipUTF8Char(name[j]);
				}
				return false;
			}

			if( pattern[i] == '?' ) {
				i++;
				j += skipUTF8Char(name[j]);
				continue;
			}

			if( pattern[i] != name[j] ) {
				return false;
			}

			i += skipUTF8Char(pattern[i]);
			j += skipUTF8Char(name[j]);

			if( !(j < namelen) ) {			// name is end
				if( !(i < patternlen) ) {	// pattern is end
					return true;
				}
				if( pattern[i] == '*' ) {
					break;
				}
			}
			continue;
		}

		if( i == patternlen && j == namelen ) {
			return true;
		}

		if( !(j < namelen) /*name is end*/ && pattern[i] == '*' ) {
			boolean ok = true;
			while( i < patternlen ) {
				if( pattern[i++] != '*' ) {
					ok = false;
					break;
				}
			}
			return ok;
		}
		return false;
	}

	/**
	 * Adds escape characters for '\', '?' and '*' found in the specified path.
	 *
	 * @param path to quote escape characters
	 * @return quoted path
	 */
	public static String quote(String path) {
		byte[] pathBytes = str2byte(path);
		int count = 0;
		for( byte b : pathBytes ) {
			if( b == '\\' || b == '?' || b == '*' ) {
				count++;
			}
		}
		if( count == 0 ) {
			return path;
		}
		byte[] quotedPath = new byte[pathBytes.length + count];
		for( int i = 0, j = 0; i < pathBytes.length; i++ ) {
			byte b = pathBytes[i];
			if( b == '\\' || b == '?' || b == '*' ) {
				quotedPath[j++] = '\\';
			}
			quotedPath[j++] = b;
		}
		return byte2str(quotedPath);
	}

	/**
	 * Removes any escape characters from the specified path.
	 *
	 * @param path
	 * @return unescaped path
	 */
	public static String unquote(String path) {
		byte[] original = str2byte(path);
		byte[] unquoted = unquote(original);
		if( original.length == unquoted.length ) {
			return path;
		}
		return byte2str(unquoted);
	}

	/**
	 * Removes any escape characters from the specified path.
	 *
	 * @param path
	 * @return unescaped path
	 */
	public static byte[] unquote(byte[] path) {
		int pathLength = path.length, i = 0;
		while( i < pathLength ) {
			if( path[i] == '\\' && i + 1 < pathLength ) {
				System.arraycopy(path, i + 1, path, i, path.length - (i + 1));
				pathLength--;
			}
			i++;
		}
		if( pathLength == path.length ) {
			return path;
		}
		return Arrays.copyOf(path, pathLength);
	}

	/**
	 * Creates a fingerprint hash of the specified data using the supplied hash.
	 *
	 * @param hash
	 * @param data
	 * @return fingerprint of specified data
	 * @throws JSchException if any errors occur
	 */
	public static String getFingerPrint(Hash hash, byte[] data) throws JSchException {
		try {
			hash.update(data, 0, data.length);
			byte[] digest = hash.digest();
			StringBuilder sb = new StringBuilder(digest.length * 2 + digest.length / 2);
			for( int i = 0, curByte; i < digest.length; i++ ) {
				curByte = digest[i] & 0xff;
				sb.append(HEXDEC_CHARS[(curByte >>> 4) & 0xf]);
				sb.append(HEXDEC_CHARS[(curByte) & 0xf]);
				if( i + 1 < digest.length ) {
					sb.append(':');
				}
			}
			return sb.toString();
		} catch(Exception e) {
			throw new JSchException("Failed to generate fingerprint", e);
		}
	}

	/**
	 * Returns the MD5 hash fingerprint of the specified data.
	 *
	 * @param data to create MD5 hash fingerprint of
	 * @return MD5 hash fingerprint
	 * @throws JSchException if any errors occur
	 */
	public synchronized static String getFingerPrint(byte[] data) throws JSchException {
		if( $md5Hash == null ) {
			$md5Hash = HashManager.getManager().createHash(Hash.HASH_MD5);
		}
		return getFingerPrint($md5Hash, data);
	}

	/**
	 * Converts the specified String to a byte array using the specified 
	 * encoding.  If a UnsupportedEncodingException is thrown, then the default
	 * encoding is used.
	 *
	 * @param str
	 * @param encoding
	 * @return bytes of string
	 */
	public static byte[] str2byte(String str, String encoding) {
		if( str == null ) {
			return null;
		}
		try {
			return str.getBytes(encoding);
		} catch(UnsupportedEncodingException e) {
			return str.getBytes();
		}
	}

	/**
	 * Converts the specified string to bytes with default UTF-8 encoding.
	 *
	 * @param str
	 * @return UTF-8 encoded bytes of String
	 */
	public static byte[] str2byte(String str) {
		return str2byte(str, "UTF-8");
	}

	/**
	 * Converts the specified range of bytes in the src array to a String using
	 * the specified character encoding.  If an UnsupportedEncodingException is
	 * thrown, uses the default encoding.
	 *
	 * @param src
	 * @param offset
	 * @param length
	 * @param encoding
	 * @return String value
	 */
	public static String byte2str(byte[] src, int offset, int length, String encoding) {
		try {
			return new String(src, offset, length, encoding);
		} catch(UnsupportedEncodingException e) {
			return new String(src, offset, length);
		}
	}

	/**
	 * Converts the specified range of bytes in the src array to a String using
	 * the default UTF-8 character encoding.
	 *
	 * @param src
	 * @param offset
	 * @param length
	 * @return String value
	 */
	public static String byte2str(byte[] src, int offset, int length) {
		return byte2str(src, offset, length, "UTF-8");
	}

	/**
	 * Converts the specified bytes to a String using the specified encoding. If
	 * an UnsupportedEncodingException is thrown, uses the default encoding.
	 *
	 * @param str
	 * @param encoding
	 * @return string from bytes using encoding
	 */
	public static String byte2str(byte[] str, String encoding) {
		return byte2str(str, 0, str.length, encoding);
	}

	/**
	 * Converts the specified bytes to String using default UTF-8 encoding.
	 *
	 * @param str
	 * @return string converted from bytes
	 */
	public static String byte2str(byte[] str) {
		return byte2str(str, 0, str.length, "UTF-8");
	}

	/**
	 * Zeros out the specified byte array.
	 *
	 * @param bytes array to zero out
	 */
	public static void bzero(byte[] bytes) {
		if( bytes != null ) {
			Arrays.fill(bytes, (byte) 0);
		}
	}

	/**
	 * Returns the amount of bytes to skip to next character.
	 *
	 * @param b
	 * @return
	 */
	private static int skipUTF8Char(byte b) {
		if( (byte) (b & 0x80) == 0 ) {
			return 1;
		} else if( (byte) (b & 0xe0) == (byte) 0xc0 ) {
			return 2;
		} else if( (byte) (b & 0xf0) == (byte) 0xe0 ) {
			return 3;
		}
		return 1;
	}

	/**
	 * Joins the String values contained in values using the specified glue
	 * between each join.
	 *
	 * @param values to join
	 * @param glue
	 * @return concatenated String
	 */
	public static String join(Collection values, String glue) {
		if( values == null || values.isEmpty() ) {
			return values != null ? "" : null;
		} else if( glue == null ) {
			glue = "";
		}
		StringBuilder buffer = new StringBuilder(10 * values.size());
		Iterator iter = values.iterator();
		for( int i = 0, size = values.size() - 1; i < size; i++ ) {
			buffer.append(iter.next()).append(glue);
		}
		buffer.append(iter.next());
		return buffer.toString();
	}

	/**
	 * Returns a new array which contains the contents of first followed by
	 * the contents of second.
	 *
	 * @param first array to join
	 * @param second array to join
	 * @return new array containing first and second
	 */
	public static byte[] join(byte[] first, byte[] second) {
		byte[] combined = Arrays.copyOf(first, first.length + second.length);
		System.arraycopy(second, 0, combined, first.length, second.length);
		return combined;
	}

	/**
	 * 

Sanitizes the specified {@code source} by replacing any control * characters. When displaying text to a user, such as error or debug * messages, the client software SHOULD replace any control characters * (except tab, carriage return, and newline) with safe sequences to avoid * attacks by sending terminal control characters.

* *

Replaces any characters outside the ASCII 'safe' range [32-126] * excluding '\n', '\r' and '\t' with the Unicode replacement character * '\uFFFD'.

* *

RFC 4251 - * The Secure Shell (SSH) Protocol Architecture: Control Character Filtering *

* * @param source string to sanitize * @return sanitized string with replaced control characters */ public static String sanitize(String source) { if( source == null || source.isEmpty() ) { return source; } StringBuilder buffer = new StringBuilder(source); char c; for( int i = 0, size = buffer.length(); i < size; i++ ) { c = buffer.charAt(i); if( (c < 32 || c > 126) && (c != '\n' && c != '\r' && c!= '\t') ) { buffer.setCharAt(i, '\uFFFD'); } } return buffer.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy