org.mariadb.jdbc.internal.util.Utils Maven / Gradle / Ivy
Show all versions of mariadb-java-client Show documentation
/*
*
* MariaDB Client for Java
*
* Copyright (c) 2012-2014 Monty Program Ab.
* Copyright (c) 2015-2019 MariaDB Ab.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to Monty Program Ab [email protected].
*
* This particular MariaDB Client for Java file is work
* derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
* the following copyright and notice provisions:
*
* Copyright (c) 2009-2011, Marcus Eriksson
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* 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.
*
* Neither the name of the driver nor the names of its contributors may not be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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.mariadb.jdbc.internal.util;
import org.mariadb.jdbc.*;
import org.mariadb.jdbc.internal.com.send.parameters.*;
import org.mariadb.jdbc.internal.failover.*;
import org.mariadb.jdbc.internal.failover.impl.*;
import org.mariadb.jdbc.internal.io.socket.*;
import org.mariadb.jdbc.internal.logging.*;
import org.mariadb.jdbc.internal.protocol.*;
import org.mariadb.jdbc.internal.util.pool.*;
import org.mariadb.jdbc.util.*;
import javax.net.*;
import java.io.*;
import java.lang.reflect.Proxy;
import java.lang.reflect.*;
import java.net.*;
import java.security.*;
import java.sql.*;
import java.util.*;
import java.util.concurrent.locks.*;
import java.util.regex.*;
@SuppressWarnings("Annotator")
public class Utils {
private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
private static final Pattern IP_V4 =
Pattern.compile(
"^(([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){1}"
+ "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){2}"
+ "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
private static final Pattern IP_V6 = Pattern.compile("^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$");
private static final Pattern IP_V6_COMPRESSED =
Pattern.compile(
"^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)"
+ "::(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)$");
private static final SocketHandlerFunction socketHandler;
static {
SocketHandlerFunction init;
try {
init = SocketUtility.getSocketHandler();
} catch (Throwable t) {
SocketHandlerFunction defaultSocketHandler =
(options, host) -> Utils.standardSocket(options, host);
init = defaultSocketHandler;
}
socketHandler = init;
}
/**
* Use standard socket implementation.
*
* @param options url options
* @param host host to connect
* @return socket
* @throws IOException in case of error establishing socket.
*/
public static Socket standardSocket(Options options, String host) throws IOException {
SocketFactory socketFactory;
String socketFactoryName = options.socketFactory;
if (socketFactoryName != null) {
try {
@SuppressWarnings("unchecked")
Class extends SocketFactory> socketFactoryClass =
(Class extends SocketFactory>) Class.forName(socketFactoryName);
if (socketFactoryClass != null) {
Constructor extends SocketFactory> constructor = socketFactoryClass.getConstructor();
socketFactory = constructor.newInstance();
return socketFactory.createSocket();
}
} catch (Exception exp) {
throw new IOException(
"Socket factory failed to initialized with option \"socketFactory\" set to \""
+ options.socketFactory
+ "\"",
exp);
}
}
socketFactory = SocketFactory.getDefault();
return socketFactory.createSocket();
}
/**
* Escape String.
*
* @param value value to escape
* @param noBackslashEscapes must backslash be escaped
* @return escaped string.
*/
public static String escapeString(String value, boolean noBackslashEscapes) {
if (!value.contains("'")) {
if (noBackslashEscapes) {
return value;
}
if (!value.contains("\\")) {
return value;
}
}
String escaped = value.replace("'", "''");
if (noBackslashEscapes) {
return escaped;
}
return escaped.replace("\\", "\\\\");
}
/**
* Encrypts a password.
*
* protocol for authentication is like this: 1. Server sends a random array of bytes (the seed)
* 2. client makes a sha1 digest of the password 3. client hashes the output of 2 4. client
* digests the seed 5. client updates the digest with the output from 3 6. an xor of the output of
* 5 and 2 is sent to server 7. server does the same thing and verifies that the scrambled
* passwords match
*
* @param password the password to encrypt
* @param seed the seed to use
* @param passwordCharacterEncoding password character encoding
* @return a scrambled password
* @throws NoSuchAlgorithmException if SHA1 is not available on the platform we are using
* @throws UnsupportedEncodingException if passwordCharacterEncoding is not a valid charset name
*/
public static byte[] encryptPassword(
final String password, final byte[] seed, String passwordCharacterEncoding)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
if (password == null || password.isEmpty()) {
return new byte[0];
}
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
byte[] bytePwd;
if (passwordCharacterEncoding != null && !passwordCharacterEncoding.isEmpty()) {
bytePwd = password.getBytes(passwordCharacterEncoding);
} else {
bytePwd = password.getBytes();
}
final byte[] stage1 = messageDigest.digest(bytePwd);
messageDigest.reset();
final byte[] stage2 = messageDigest.digest(stage1);
messageDigest.reset();
messageDigest.update(seed);
messageDigest.update(stage2);
final byte[] digest = messageDigest.digest();
final byte[] returnBytes = new byte[digest.length];
for (int i = 0; i < digest.length; i++) {
returnBytes[i] = (byte) (stage1[i] ^ digest[i]);
}
return returnBytes;
}
/**
* Copies the original byte array content to a new byte array. The resulting byte array is always
* "length" size. If length is smaller than the original byte array, the resulting byte array is
* truncated. If length is bigger than the original byte array, the resulting byte array is filled
* with zero bytes.
*
* @param orig the original byte array
* @param length how big the resulting byte array will be
* @return the copied byte array
*/
public static byte[] copyWithLength(byte[] orig, int length) {
// No need to initialize with zero bytes, because the bytes are already initialized with that
byte[] result = new byte[length];
int howMuchToCopy = length < orig.length ? length : orig.length;
System.arraycopy(orig, 0, result, 0, howMuchToCopy);
return result;
}
/**
* Copies from original byte array to a new byte array. The resulting byte array is always
* "to-from" size.
*
* @param orig the original byte array
* @param from index of first byte in original byte array which will be copied
* @param to index of last byte in original byte array which will be copied. This can be outside
* of the original byte array
* @return resulting array
*/
public static byte[] copyRange(byte[] orig, int from, int to) {
int length = to - from;
byte[] result = new byte[length];
int howMuchToCopy = orig.length - from < length ? orig.length - from : length;
System.arraycopy(orig, from, result, 0, howMuchToCopy);
return result;
}
/**
* Helper function to replace function parameters in escaped string. 3 functions are handles :
*
*
* - CONVERT(value, type): replacing SQL_XXX types to convertible type, i.e SQL_BIGINT to
* INTEGER
*
- TIMESTAMPDIFF(type, ...): replacing type SQL_TSI_XXX in type with XXX, i.e SQL_TSI_HOUR
* with HOUR
*
- TIMESTAMPADD(type, ...): replacing type SQL_TSI_XXX in type with XXX, i.e SQL_TSI_HOUR
* with HOUR
*
*
* caution: this use MariaDB server conversion: 'SELECT CONVERT('2147483648', INTEGER)' will
* return a BIGINT. MySQL will throw a syntax error.
*
* @param functionString input string
* @param protocol protocol
* @return unescaped string
*/
private static String replaceFunctionParameter(String functionString, Protocol protocol) {
char[] input = functionString.toCharArray();
StringBuilder sb = new StringBuilder();
int index;
for (index = 0; index < input.length; index++) {
if (input[index] != ' ') {
break;
}
}
for (;
((input[index] >= 'a' && index <= 'z') || (input[index] >= 'A' && input[index] <= 'Z'))
&& index < input.length;
index++) {
sb.append(input[index]);
}
String func = sb.toString().toLowerCase(Locale.ROOT);
switch (func) {
case "convert":
// Handle "convert(value, type)" case
// extract last parameter, after the last ','
int lastCommaIndex = functionString.lastIndexOf(',');
int firstParentheses = functionString.indexOf('(');
String value = functionString.substring(firstParentheses + 1, lastCommaIndex);
for (index = lastCommaIndex + 1; index < input.length; index++) {
if (!Character.isWhitespace(input[index])) {
break;
}
}
int endParam = index + 1;
for (; endParam < input.length; endParam++) {
if ((input[endParam] < 'a' || input[endParam] > 'z')
&& (input[endParam] < 'A' || input[endParam] > 'Z')
&& input[endParam] != '_') {
break;
}
}
String typeParam = new String(input, index, endParam - index).toUpperCase(Locale.ROOT);
if (typeParam.startsWith("SQL_")) {
typeParam = typeParam.substring(4);
}
switch (typeParam) {
case "BOOLEAN":
return "1=" + value;
case "BIGINT":
case "SMALLINT":
case "TINYINT":
typeParam = "SIGNED INTEGER";
break;
case "BIT":
typeParam = "UNSIGNED INTEGER";
break;
case "BLOB":
case "VARBINARY":
case "LONGVARBINARY":
case "ROWID":
typeParam = "BINARY";
break;
case "NCHAR":
case "CLOB":
case "NCLOB":
case "DATALINK":
case "VARCHAR":
case "NVARCHAR":
case "LONGVARCHAR":
case "LONGNVARCHAR":
case "SQLXML":
case "LONGNCHAR":
typeParam = "CHAR";
break;
case "DOUBLE":
case "FLOAT":
if (protocol.isServerMariaDb() || protocol.versionGreaterOrEqual(8, 0, 17)) {
typeParam = "DOUBLE";
break;
}
return "0.0+" + value;
case "REAL":
case "NUMERIC":
typeParam = "DECIMAL";
break;
case "TIMESTAMP":
typeParam = "DATETIME";
break;
default:
break;
}
return new String(input, 0, index)
+ typeParam
+ new String(input, endParam, input.length - endParam);
case "timestampdiff":
case "timestampadd":
// Skip to first parameter
for (; index < input.length; index++) {
if (!Character.isWhitespace(input[index]) && input[index] != '(') {
break;
}
}
if (index < input.length - 8) {
String paramPrefix = new String(input, index, 8);
if ("SQL_TSI_".equals(paramPrefix)) {
return new String(input, 0, index)
+ new String(input, index + 8, input.length - (index + 8));
}
}
return functionString;
default:
return functionString;
}
}
private static String resolveEscapes(String escaped, Protocol protocol) throws SQLException {
if (escaped.charAt(0) != '{' || escaped.charAt(escaped.length() - 1) != '}') {
throw new SQLException("unexpected escaped string");
}
int endIndex = escaped.length() - 1;
String escapedLower = escaped.toLowerCase(Locale.ROOT);
if (escaped.startsWith("{fn ")) {
String resolvedParams = replaceFunctionParameter(escaped.substring(4, endIndex), protocol);
return nativeSql(resolvedParams, protocol);
} else if (escapedLower.startsWith("{oj ")) {
// Outer join
// the server supports "oj" in any case, even "oJ"
return nativeSql(escaped.substring(4, endIndex), protocol);
} else if (escaped.startsWith("{d ")) {
// date literal
return escaped.substring(3, endIndex);
} else if (escaped.startsWith("{t ")) {
// time literal
return escaped.substring(3, endIndex);
} else if (escaped.startsWith("{ts ")) {
// timestamp literal
return escaped.substring(4, endIndex);
} else if (escaped.startsWith("{d'")) {
// date literal, no space
return escaped.substring(2, endIndex);
} else if (escaped.startsWith("{t'")) {
// time literal
return escaped.substring(2, endIndex);
} else if (escaped.startsWith("{ts'")) {
// timestamp literal
return escaped.substring(3, endIndex);
} else if (escaped.startsWith("{call ") || escaped.startsWith("{CALL ")) {
// We support uppercase "{CALL" only because Connector/J supports it. It is not in the JDBC
// spec.
return nativeSql(escaped.substring(1, endIndex), protocol);
} else if (escaped.startsWith("{escape ")) {
return escaped.substring(1, endIndex);
} else if (escaped.startsWith("{?")) {
// likely ?=call(...)
return nativeSql(escaped.substring(1, endIndex), protocol);
} else if (escaped.startsWith("{ ") || escaped.startsWith("{\n")) {
// Spaces and newlines before keyword, this is not JDBC compliant, however some it works in
// some drivers,
// so we support it, too
for (int i = 2; i < escaped.length(); i++) {
if (!Character.isWhitespace(escaped.charAt(i))) {
return resolveEscapes("{" + escaped.substring(i), protocol);
}
}
} else if (escaped.startsWith("{\r\n")) {
// Spaces and newlines before keyword, this is not JDBC compliant, however some it works in
// some drivers,
// so we support it, too
for (int i = 3; i < escaped.length(); i++) {
if (!Character.isWhitespace(escaped.charAt(i))) {
return resolveEscapes("{" + escaped.substring(i), protocol);
}
}
}
throw new SQLException("unknown escape sequence " + escaped);
}
/**
* Escape sql String.
*
* @param sql initial sql
* @param protocol protocol
* @return escaped sql string
* @throws SQLException if escape sequence is incorrect.
*/
@SuppressWarnings("ConstantConditions")
public static String nativeSql(String sql, Protocol protocol) throws SQLException {
if (!sql.contains("{")) {
return sql;
}
StringBuilder escapeSequenceBuf = new StringBuilder();
StringBuilder sqlBuffer = new StringBuilder();
char[] charArray = sql.toCharArray();
char lastChar = 0;
boolean inQuote = false;
char quoteChar = 0;
boolean inComment = false;
boolean isSlashSlashComment = false;
int inEscapeSeq = 0;
for (int i = 0; i < charArray.length; i++) {
char car = charArray[i];
if (lastChar == '\\' && !protocol.noBackslashEscapes()) {
sqlBuffer.append(car);
continue;
}
switch (car) {
case '\'':
case '"':
case '`':
if (!inComment) {
if (inQuote) {
if (quoteChar == car) {
inQuote = false;
}
} else {
inQuote = true;
quoteChar = car;
}
}
break;
case '*':
if (!inQuote && !inComment && lastChar == '/') {
inComment = true;
isSlashSlashComment = false;
}
break;
case '/':
case '-':
if (!inQuote) {
if (inComment) {
if (lastChar == '*' && !isSlashSlashComment) {
inComment = false;
} else if (lastChar == car && isSlashSlashComment) {
inComment = false;
}
} else {
if (lastChar == car) {
inComment = true;
isSlashSlashComment = true;
} else if (lastChar == '*') {
inComment = true;
isSlashSlashComment = false;
}
}
}
break;
case '\n':
if (inComment && isSlashSlashComment) {
// slash-slash and dash-dash comments ends with the end of line
inComment = false;
}
break;
case '{':
if (!inQuote && !inComment) {
inEscapeSeq++;
}
break;
case '}':
if (!inQuote && !inComment) {
inEscapeSeq--;
if (inEscapeSeq == 0) {
escapeSequenceBuf.append(car);
sqlBuffer.append(resolveEscapes(escapeSequenceBuf.toString(), protocol));
escapeSequenceBuf.setLength(0);
continue;
}
}
break;
default:
break;
}
lastChar = car;
if (inEscapeSeq > 0) {
escapeSequenceBuf.append(car);
} else {
sqlBuffer.append(car);
}
}
if (inEscapeSeq > 0) {
throw new SQLException(
"Invalid escape sequence , missing closing '}' character in '" + sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* Retrieve protocol corresponding to the failover options. if no failover option, protocol will
* not be proxied. if a failover option is precised, protocol will be proxied so that any
* connection error will be handle directly.
*
* @param urlParser urlParser corresponding to connection url string.
* @param globalInfo global variable information
* @return protocol
* @throws SQLException if any error occur during connection
*/
public static Protocol retrieveProxy(final UrlParser urlParser, final GlobalStateInfo globalInfo)
throws SQLException {
final ReentrantLock lock = new ReentrantLock();
Protocol protocol;
switch (urlParser.getHaMode()) {
case AURORA:
return getProxyLoggingIfNeeded(
urlParser,
(Protocol)
Proxy.newProxyInstance(
AuroraProtocol.class.getClassLoader(),
new Class[] {Protocol.class},
new FailoverProxy(new AuroraListener(urlParser, globalInfo), lock)));
case REPLICATION:
return getProxyLoggingIfNeeded(
urlParser,
(Protocol)
Proxy.newProxyInstance(
MastersSlavesProtocol.class.getClassLoader(),
new Class[] {Protocol.class},
new FailoverProxy(new MastersSlavesListener(urlParser, globalInfo), lock)));
case LOADBALANCE:
case SEQUENTIAL:
return getProxyLoggingIfNeeded(
urlParser,
(Protocol)
Proxy.newProxyInstance(
MasterProtocol.class.getClassLoader(),
new Class[] {Protocol.class},
new FailoverProxy(new MastersFailoverListener(urlParser, globalInfo), lock)));
default:
protocol =
getProxyLoggingIfNeeded(urlParser, new MasterProtocol(urlParser, globalInfo, lock));
protocol.connectWithoutProxy();
return protocol;
}
}
private static Protocol getProxyLoggingIfNeeded(UrlParser urlParser, Protocol protocol) {
if (urlParser.getOptions().profileSql
|| urlParser.getOptions().slowQueryThresholdNanos != null) {
return (Protocol)
Proxy.newProxyInstance(
MasterProtocol.class.getClassLoader(),
new Class[] {Protocol.class},
new ProtocolLoggingProxy(protocol, urlParser.getOptions()));
}
return protocol;
}
/**
* Get timezone from Id. This differ from java implementation : by default, if timezone Id is
* unknown, java return GMT timezone. GMT will be return only if explicitly asked.
*
* @param id timezone id
* @return timezone.
* @throws SQLException if no timezone is found for this Id
*/
public static TimeZone getTimeZone(String id) throws SQLException {
TimeZone tz = TimeZone.getTimeZone(id);
// Validate the timezone ID. JDK maps invalid timezones to GMT
if ("GMT".equals(tz.getID()) && !"GMT".equals(id)) {
throw new SQLException("invalid timezone id '" + id + "'");
}
return tz;
}
/**
* Create socket accordingly to options.
*
* @param options Url options
* @param host hostName ( mandatory only for named pipe)
* @return a nex socket
* @throws IOException if connection error occur
*/
public static Socket createSocket(Options options, String host) throws IOException {
return socketHandler.apply(options, host);
}
/**
* Hexdump.
*
* @param bytes byte arrays
* @return String
*/
public static String hexdump(byte[]... bytes) {
return hexdump(Integer.MAX_VALUE, 0, Integer.MAX_VALUE, bytes);
}
/**
* Hexdump.
*
*
String output example :
*
*
{@code
* 7D 00 00 01 C5 00 00 }...... <- first byte array
* 01 00 00 01 02 33 00 00 02 03 64 65 66 05 74 65 .....3....def.te <- second byte array
* 73 74 6A 0A 74 65 73 74 5F 62 61 74 63 68 0A 74 stj.test_batch.t
* 65 73 74 5F 62 61 74 63 68 02 69 64 02 69 64 0C est_batch.id.id.
* 3F 00 0B 00 00 00 03 03 42 00 00 00 37 00 00 03 ?.......B...7...
* 03 64 65 66 05 74 65 73 74 6A 0A 74 65 73 74 5F .def.testj.test_
* 62 61 74 63 68 0A 74 65 73 74 5F 62 61 74 63 68 batch.test_batch
* 04 74 65 73 74 04 74 65 73 74 0C 21 00 1E 00 00 .test.test.!....
* 00 FD 00 00 00 00 00 05 00 00 04 FE 00 00 22 00 ..............".
* 06 00 00 05 01 31 03 61 61 61 06 00 00 06 01 32 .....1.aaa.....2
* 03 62 62 62 06 00 00 07 01 33 03 63 63 63 06 00 .bbb.....3.ccc..
* 00 08 01 34 03 61 61 61 06 00 00 09 01 35 03 62 ...4.aaa.....5.b
* 62 62 06 00 00 0A 01 36 03 63 63 63 05 00 00 0B bb.....6.ccc....
* FE 00 00 22 00 ...".
* }
*
* @param maxQuerySizeToLog max log size
* @param offset offset of last byte array
* @param length length of last byte array
* @param byteArr byte arrays. if many, only the last may have offset and size limitation others
* will be displayed completely.
* @return String
*/
public static String hexdump(int maxQuerySizeToLog, int offset, int length, byte[]... byteArr) {
switch (byteArr.length) {
case 0:
return "";
case 1:
byte[] bytes = byteArr[0];
if (bytes.length <= offset) {
return "";
}
int dataLength = Math.min(maxQuerySizeToLog, Math.min(bytes.length - offset, length));
StringBuilder outputBuilder = new StringBuilder(dataLength * 5);
outputBuilder.append("\n");
writeHex(bytes, offset, dataLength, outputBuilder);
return outputBuilder.toString();
default:
StringBuilder sb = new StringBuilder();
sb.append("\n");
byte[] arr;
for (int i = 0; i < byteArr.length - 1; i++) {
arr = byteArr[i];
writeHex(arr, 0, arr.length, sb);
}
arr = byteArr[byteArr.length - 1];
int dataLength2 = Math.min(maxQuerySizeToLog, Math.min(arr.length - offset, length));
writeHex(arr, offset, dataLength2, sb);
return sb.toString();
}
}
/**
* Write bytes/hexadecimal value of a byte array to a StringBuilder.
*
* String output example :
*
*
{@code
* 38 00 00 00 03 63 72 65 61 74 65 20 74 61 62 6C 8....create tabl
* 65 20 42 6C 6F 62 54 65 73 74 63 6C 6F 62 74 65 e BlobTestclobte
* 73 74 32 20 28 73 74 72 6D 20 74 65 78 74 29 20 st2 (strm text)
* 43 48 41 52 53 45 54 20 75 74 66 38 CHARSET utf8
* }
*
* @param bytes byte array
* @param offset offset
* @param dataLength byte length to write
* @param outputBuilder string builder
*/
private static void writeHex(
byte[] bytes, int offset, int dataLength, StringBuilder outputBuilder) {
if (bytes == null || bytes.length == 0) {
return;
}
char[] hexaValue = new char[16];
hexaValue[8] = ' ';
int pos = offset;
int posHexa = 0;
while (pos < dataLength + offset) {
int byteValue = bytes[pos] & 0xFF;
outputBuilder
.append(hexArray[byteValue >>> 4])
.append(hexArray[byteValue & 0x0F])
.append(" ");
hexaValue[posHexa++] = (byteValue > 31 && byteValue < 127) ? (char) byteValue : '.';
if (posHexa == 8) {
outputBuilder.append(" ");
}
if (posHexa == 16) {
outputBuilder.append(" ").append(hexaValue).append("\n");
posHexa = 0;
}
pos++;
}
int remaining = posHexa;
if (remaining > 0) {
if (remaining < 8) {
for (; remaining < 8; remaining++) {
outputBuilder.append(" ");
}
outputBuilder.append(" ");
}
for (; remaining < 16; remaining++) {
outputBuilder.append(" ");
}
outputBuilder.append(" ").append(hexaValue, 0, posHexa).append("\n");
}
}
private static String getHex(final byte[] raw) {
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
hex.append(hexArray[(b & 0xF0) >> 4]).append(hexArray[(b & 0x0F)]);
}
return hex.toString();
}
public static String byteArrayToHexString(final byte[] bytes) {
return (bytes != null) ? getHex(bytes) : "";
}
/**
* Convert int value to hexadecimal String.
*
* @param value value to transform
* @return Hexadecimal String value of integer.
*/
public static String intToHexString(final int value) {
final StringBuilder hex = new StringBuilder(8);
int offset = 24;
byte b;
boolean nullEnd = false;
while (offset >= 0) {
b = (byte) (value >> offset);
offset -= 8;
if (b != 0 || nullEnd) {
nullEnd = true;
hex.append(hexArray[(b & 0xF0) >> 4]).append(hexArray[(b & 0x0F)]);
}
}
return hex.toString();
}
/**
* Parse the option "sessionVariable" to ensure having no injection. semi-column not in string
* will be replaced by comma.
*
* @param sessionVariable option value
* @return parsed String
*/
public static String parseSessionVariables(String sessionVariable) {
StringBuilder out = new StringBuilder();
StringBuilder sb = new StringBuilder();
Parse state = Parse.Normal;
boolean iskey = true;
boolean singleQuotes = true;
boolean first = true;
String key = null;
char[] chars = sessionVariable.toCharArray();
for (char car : chars) {
if (state == Parse.Escape) {
sb.append(car);
state = singleQuotes ? Parse.Quote : Parse.String;
continue;
}
switch (car) {
case '"':
if (state == Parse.Normal) {
state = Parse.String;
singleQuotes = false;
} else if (state == Parse.String && !singleQuotes) {
state = Parse.Normal;
}
break;
case '\'':
if (state == Parse.Normal) {
state = Parse.String;
singleQuotes = true;
} else if (state == Parse.String && singleQuotes) {
state = Parse.Normal;
}
break;
case '\\':
if (state == Parse.String) {
state = Parse.Escape;
}
break;
case ';':
case ',':
if (state == Parse.Normal) {
if (!iskey) {
if (!first) {
out.append(",");
}
out.append(key);
out.append(sb.toString());
first = false;
} else {
key = sb.toString().trim();
if (!key.isEmpty()) {
if (!first) {
out.append(",");
}
out.append(key);
first = false;
}
}
iskey = true;
key = null;
sb = new StringBuilder();
continue;
}
break;
case '=':
if (state == Parse.Normal && iskey) {
key = sb.toString().trim();
iskey = false;
sb = new StringBuilder();
}
break;
default:
// nothing
}
sb.append(car);
}
if (!iskey) {
if (!first) {
out.append(",");
}
out.append(key);
out.append(sb.toString());
} else {
String tmpkey = sb.toString().trim();
if (!tmpkey.isEmpty() && !first) {
out.append(",");
}
out.append(tmpkey);
}
return out.toString();
}
public static boolean isIPv4(final String ip) {
return IP_V4.matcher(ip).matches();
}
public static boolean isIPv6(final String ip) {
return IP_V6.matcher(ip).matches() || IP_V6_COMPRESSED.matcher(ip).matches();
}
/**
* Traduce a String value of transaction isolation to corresponding java value.
*
* @param txIsolation String value
* @return java corresponding value (Connection.TRANSACTION_READ_UNCOMMITTED,
* Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION_REPEATABLE_READ or
* Connection.TRANSACTION_SERIALIZABLE)
* @throws SQLException if String value doesn't correspond
* to @@tx_isolation/@@transaction_isolation possible value
*/
public static int transactionFromString(String txIsolation) throws SQLException {
switch (txIsolation) { // tx_isolation
case "READ-UNCOMMITTED":
return Connection.TRANSACTION_READ_UNCOMMITTED;
case "READ-COMMITTED":
return Connection.TRANSACTION_READ_COMMITTED;
case "REPEATABLE-READ":
return Connection.TRANSACTION_REPEATABLE_READ;
case "SERIALIZABLE":
return Connection.TRANSACTION_SERIALIZABLE;
default:
throw new SQLException("unknown transaction isolation level");
}
}
/**
* Validate that file name correspond to send query.
*
* @param sql sql command
* @param parameters sql parameter
* @param fileName server file name
* @return true if correspond
*/
public static boolean validateFileName(
String sql, ParameterHolder[] parameters, String fileName) {
Pattern pattern =
Pattern.compile(
"^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+'"
+ fileName
+ "'",
Pattern.CASE_INSENSITIVE);
if (pattern.matcher(sql).find()) {
return true;
}
if (parameters != null) {
pattern =
Pattern.compile(
"^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+\\?",
Pattern.CASE_INSENSITIVE);
if (pattern.matcher(sql).find() && parameters.length > 0) {
return parameters[0].toString().toLowerCase().equals("'" + fileName.toLowerCase() + "'");
}
}
return false;
}
private enum Parse {
Normal,
String, /* inside string */
Quote,
Escape /* found backslash */
}
}