/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed 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.
*
* Initial developer(s): Csaba Simon
* Contributor(s): Gilles Rayrat
*/
package com.continuent.tungsten.common.mysql;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.log4j.Logger;
/**
* Utility class.
*
* @author <a href="mailto:csaba.simon@continuent.com">Csaba Simon</a>
* @version 1.0
*/
public class Utils
{
/** Logger for this class */
private static final Logger logger = Logger.getLogger(Utils.class);
/** All hexadecimal characters */
final static String hexChars[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
/**
* Generate a random string. This is used for password encryption.
*
* @return the generated random String
*/
public static String generateRandomString(int count)
{
Random random = new Random();
StringBuffer buffer = new StringBuffer();
while (count-- != 0)
{
// a random number in the 32...127 range
char ch = (char) (random.nextInt(96) + 32);
buffer.append(ch);
}
return buffer.toString();
}
/**
* Trims white spaces and remove quotes from the string.
*
* @param s the string
* @return a string without quotes
*/
public static String removeQuotes(String s)
{
if (s == null)
{
return null;
}
String trimmed = s.trim();
if (trimmed.length() == 0)
{
return trimmed;
}
int i = nextNonQuoteIndex(trimmed, 0, true);
int j = nextNonQuoteIndex(trimmed, trimmed.length() - 1, false);
return trimmed.substring(i, j + 1);
}
/**
* Computes the index of the next character in the given string that is not
* a quote, starting from given index and going either forward or backward
*
* @param trimmed the string to analyze
* @param i start index
* @param forward whether to increase index (true) or to decrease it (false)
* @return the index of the next non quote character
*/
private static int nextNonQuoteIndex(String trimmed, int i, boolean forward)
{
while (trimmed.charAt(i) == '\u0022' || trimmed.charAt(i) == '\''
|| trimmed.charAt(i) == '\u0060'
|| trimmed.charAt(i) == '\u00B4'
|| trimmed.charAt(i) == '\u2018'
|| trimmed.charAt(i) == '\u2019'
|| trimmed.charAt(i) == '\u201C'
|| trimmed.charAt(i) == '\u201D')
{
if (forward)
i++;
else
i--;
}
return i;
}
/**
* Replace parameters $1, $2, ... with '?'.
*
* @param statement the statement
* @return a string where parameters get replaced with question mark
*/
public static String replaceParametersWithQuestionMarks(String statement)
{
if (statement == null)
{
return null;
}
String result = "";
int i = 0;
int len = statement.length();
char last = '\0';
while (i < len)
{
char c = statement.charAt(i);
if (c == '$')
{
last = c;
i++;
continue;
}
if ((last == '$') && (c >= '0') && (c <= '9'))
{
i++;
while ((i < len) && (Character.isDigit(statement.charAt(i))))
{
i++;
}
last = '\0';
c = '?';
i--;
}
result += c;
i++;
}
return result;
}
/**
* Turns 16-byte stream into a human-readable 32-byte hex string This code
* was copied from the PostgreSQL JDBC driver code (MD5Digest.java)
*
* @param bytes bytes to convert
* @param hex the converted hex bytes
* @param offset from where to start the conversion
*/
public static void bytesToHex(byte[] bytes, byte[] hex, int offset)
{
final char lookup[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f'};
int i, c, j, pos = offset;
for (i = 0; i < 16; i++)
{
c = bytes[i] & 0xFF;
j = c >> 4;
hex[pos++] = (byte) lookup[j];
j = (c & 0xF);
hex[pos++] = (byte) lookup[j];
}
}
/**
* Converts a byte to readable hexadecimal format in a string<br>
* This code was originally taken from Jeff Boyle's article on devX.com
*
* @param in byte[] buffer to convert to string format
* @return a String containing the hex values corresponding to the input
* byte, all attached
*/
public static String byteToHexString(byte in)
{
StringBuffer out = new StringBuffer(2);
// Strip off high nibble
byte ch = (byte) (in & 0xF0);
// shift the bits down
ch = (byte) (ch >>> 4);
ch = (byte) (ch & 0x0F);
// must do this is high order bit is on!
out.append(hexChars[(int) ch]); // convert the nibble to a String
// Character
ch = (byte) (in & 0x0F); // Strip off low nibble
out.append(hexChars[(int) ch]); // convert the nibble to a String
// Character
return out.toString();
}
/**
* Converts the given byte array into a readable hexa-decimal formatted
* string, starting from given offset<br>
* This code was originally taken from Jeff Boyle's article on devX.com
*
* @return a String containing the hex values corresponding to the input
* bytes, bytes separated by a space
* @param in byte[] buffer to convert to string format
* @param offset where to start the conversion from
*/
public static String byteArrayToHexString(byte in[], int offset)
{
if (in == null || in.length <= 0 || offset >= in.length)
return null;
StringBuffer out = new StringBuffer(in.length * 3);
for (int i = offset; i < in.length; i++)
{
out.append(byteToHexString(in[i]));
out.append(' '); // separate bytes with a space
}
return out.toString();
}
/**
* Converts the given byte array into a readable hexa-decimal formatted
* string
*
* @return a String containing the hex values corresponding to the input
* bytes, bytes separated by a space
* @param in byte[] buffer to convert to string format
*/
public static String byteArrayToHexString(byte in[])
{
return byteArrayToHexString(in, 0);
}
/**
* Converts the given array of bytes into a Java ArrayList
*
* @param byteArray array of bytes to convert
* @return an ArrayList containing the given byteArray data
*/
public static ArrayList<Byte> byteArrayToArrayList(byte[] byteArray)
{
return byteArrayToArrayList(byteArray, 0);
}
/**
* Converts the given array of bytes into a Java ArrayList starting from the
* given offset
*
* @param byteArray array of bytes to convert
* @param offset where to start conversion from
* @return an ArrayList containing the given byteArray data
*/
public static ArrayList<Byte> byteArrayToArrayList(byte[] byteArray,
int offset)
{
ArrayList<Byte> list = new ArrayList<Byte>(byteArray.length);
for (int i = offset; i < byteArray.length; i++)
{
list.add(Byte.valueOf(byteArray[i]));
}
return list;
}
/**
* Removes comments (between slash-star and star-slash plus lines beginning
* with double slash) comments from a sql statement and returns the
* resulting statement.
*
* @param sqlStatement a sql statement in which we want to remove comments
* @return the SQL statement without comments
*/
public static String removeComments(String sqlStatement)
{
StringBuffer result = new StringBuffer();
int index = sqlStatement.indexOf("/*");
int lastIndex = 0;
while (index >= 0)
{
result.append(sqlStatement.substring(lastIndex, index));
// Comment marker found... Look for the end marker
int nextIndex = sqlStatement.indexOf("*/", index + 2);
if (nextIndex == -1)
return sqlStatement;
lastIndex = nextIndex + 2;
index = sqlStatement.indexOf("*/", lastIndex);
}
result.append(sqlStatement.substring(lastIndex));
// Do another round line by line to remove line-comments
BufferedReader reader = new BufferedReader(new StringReader(result
.toString().trim()));
result = new StringBuffer();
String line = null;
try
{
while ((line = reader.readLine()) != null)
{
line = line.trim();
int firstCommentIdx = line.indexOf("--");
int doubleSlashCommentIdx = line.indexOf("//");
if (firstCommentIdx == -1
|| (doubleSlashCommentIdx != -1 && doubleSlashCommentIdx < firstCommentIdx))
{
firstCommentIdx = doubleSlashCommentIdx;
}
String toAppend = null;
if (firstCommentIdx == -1)
toAppend = line;
else
toAppend = line.substring(0, firstCommentIdx);
toAppend = toAppend.trim();
if (!"".equals(toAppend))
{
result.append(line);
result.append("\n");
}
}
}
catch (IOException neverHappens)
{
}
return result.toString().trim();
}
/**
* Applies the given mask to the given IP
*
* @param ip the IPV4 address to apply mask to
* @param mask the IPV4 mask to apply to the given IP
* @return an InetAddress representing the given masked IP
*/
public static InetAddress applyMask(String ip, String mask)
{
byte[] rawIP = null;
byte[] rawMask = null;
try
{
rawIP = InetAddress.getByName(ip).getAddress();
rawMask = InetAddress.getByName(mask).getAddress();
if (rawIP.length != rawMask.length)
{
logger.error("IP " + ip + " and mask " + mask
+ " use different formats");
return null;
}
byte[] maskedAddressBytes = new byte[rawIP.length];
for (int i = 0; i < rawIP.length; i++)
{
byte currentAddressByte = rawIP[i];
byte currentMaskByte = rawMask[i];
maskedAddressBytes[i] = (byte) (currentAddressByte & currentMaskByte);
}
return InetAddress.getByAddress(maskedAddressBytes);
}
catch (UnknownHostException uhe)
{
logger.debug("Caught UnknownHostException while applying mask "
+ mask + " to IP " + ip, uhe);
return null;
}
}
/**
* Transforms a CIDR formatted mask into a regular network mask
*
* @param cidrMask mask in CIDR format (24, 32, etc.)
* @return a network mask like 255.255.255.0
*/
public static String cidrMaskToNetMask(String cidrMask)
{
if (cidrMask == null)
{
return null;
}
// Get the integer value of the mask
int cidrMaskValue = 0;
try
{
cidrMaskValue = Integer.parseInt(cidrMask);
}
catch (NumberFormatException e)
{
return null;
}
int cidrMaskFull = 0xffffffff << (32 - cidrMaskValue);
int cidrMaskBits1 = cidrMaskFull >> 24 & 0xff;
int cidrMaskBits2 = cidrMaskFull >> 16 & 0xff;
int cidrMaskBits3 = cidrMaskFull >> 8 & 0xff;
int cidrMaskBits4 = cidrMaskFull >> 0 & 0xff;
StringBuffer netMaskBuf = new StringBuffer();
netMaskBuf.append(cidrMaskBits1);
netMaskBuf.append('.');
netMaskBuf.append(cidrMaskBits2);
netMaskBuf.append('.');
netMaskBuf.append(cidrMaskBits3);
netMaskBuf.append('.');
netMaskBuf.append(cidrMaskBits4);
return netMaskBuf.toString();
}
/**
* Tells whether the given IPV4 address is in the given CIDR ip range
*
* @param ip the IP address to compare, in ipv4 format
* @param ipRange the network+mask to test, in CIDR format
* @return true if the given IP is in the given range, false otherwise
*/
public static boolean isInRange(String ip, String ipRange)
{
if (ip == null || ipRange == null)
return false;
// separate network part from mask part
String[] cidrString = ipRange.split("/");
if (cidrString.length == 0)
return false;
String network = cidrString[0];
String cidrMask = "24";
// if there is something after '/', that our mask, otherwise, that's a
// single address
if (cidrString.length > 1)
{
cidrMask = cidrString[1];
}
// Get a regular network mask to apply to the address
String netMask = cidrMaskToNetMask(cidrMask);
// Apply it
InetAddress maskedIP = applyMask(ip, netMask);
InetAddress maskedNetwork = applyMask(network, netMask);
if (maskedIP == null || maskedNetwork == null)
// malformed addresses
return false;
return maskedIP.equals(maskedNetwork);
}
/**
* Tells whether the given IP belongs to the given list of CIDR addresses.
* Null IPs are always denied, null authorizedIPs allow any host (except
* null ones)
*
* @param ip the IP address to test
* @param authorizedIPs a list of CIDR formatted network/mask. Null for
* authorizing any host
* @return true if the given IP is part of one or more given IP ranges
*/
public static boolean isAuthorizedIP(String ip, List<String> authorizedIPs)
{
if (ip == null)
return false;
if (authorizedIPs == null)
{
return true;
}
for (String ipRange : authorizedIPs)
{
if (ipRange != null)
if (isInRange(ip, ipRange))
return true;
}
return false;
}
}