
org.postgresql.core.Utils Maven / Gradle / Ivy
/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2014, PostgreSQL Global Development Group
* Copyright (c) 2004, Open Cloud Limited.
*
*
*-------------------------------------------------------------------------
*/
package org.postgresql.core;
import java.sql.SQLException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.text.ParsePosition;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
/**
* Collection of utilities used by the protocol-level code.
*/
public class Utils {
/**
* Turn a bytearray into a printable form, representing
* each byte in hex.
*
* @param data the bytearray to stringize
* @return a hex-encoded printable representation of data
*/
public static String toHexString(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (int i = 0; i < data.length; ++i)
{
sb.append(Integer.toHexString((data[i] >> 4) & 15));
sb.append(Integer.toHexString(data[i] & 15));
}
return sb.toString();
}
/**
* Keep a local copy of the UTF-8 Charset so we can avoid
* synchronization overhead from looking up the Charset by
* name as String.getBytes(String) requires.
*/
private final static Charset utf8Charset = Charset.forName("UTF-8");
/**
* Encode a string as UTF-8.
*
* @param str the string to encode
* @return the UTF-8 representation of str
*/
public static byte[] encodeUTF8(String str) {
// Previously we just used str.getBytes("UTF-8"), but when
// the JVM is using more than one encoding the lookup cost
// makes that a loser to the below (even in the single thread case).
// When multiple threads are doing Charset lookups, they all get
// blocked and must wait, severely dropping throughput.
//
ByteBuffer buf = utf8Charset.encode(CharBuffer.wrap(str));
byte b[] = new byte[buf.limit()];
buf.get(b, 0, buf.limit());
return b;
}
/**
* Escape the given literal value and append it to the string buffer
* sbuf. If sbuf is null, a new StringBuffer will be
* returned. The argument standardConformingStrings defines whether the
* backend expects standard-conforming string literals or allows backslash
* escape sequences.
*
* @param sbuf the string buffer to append to; or null
* @param value the string value
* @param standardConformingStrings
* @return the sbuf argument; or a new string buffer for sbuf == null
* @throws SQLException if the string contains a \0 character
* @deprecated use {@link #escapeLiteral(StringBuilder, String, boolean)} instead
*/
public static StringBuffer appendEscapedLiteral(StringBuffer sbuf, String value, boolean standardConformingStrings)
throws SQLException
{
if (sbuf == null)
{
sbuf = new StringBuffer(value.length() * 11 / 10); // Add 10% for escaping.
}
doAppendEscapedLiteral(sbuf, value, standardConformingStrings);
return sbuf;
}
/**
* Escape the given literal value and append it to the string builder
* sbuf. If sbuf is null, a new StringBuilder will be
* returned. The argument standardConformingStrings defines whether the
* backend expects standard-conforming string literals or allows backslash
* escape sequences.
*
* @param sbuf the string builder to append to; or null
* @param value the string value
* @param standardConformingStrings
* @return the sbuf argument; or a new string builder for sbuf == null
* @throws SQLException if the string contains a \0 character
*/
public static StringBuilder escapeLiteral(StringBuilder sbuf, String value, boolean standardConformingStrings)
throws SQLException
{
if (sbuf == null)
{
sbuf = new StringBuilder(value.length() * 11 / 10); // Add 10% for escaping.
}
doAppendEscapedLiteral(sbuf, value, standardConformingStrings);
return sbuf;
}
/**
* Common part for {@link #appendEscapedLiteral(StringBuffer, String, boolean)} and {@link #escapeLiteral(StringBuilder, String, boolean)}
* @param sbuf Either StringBuffer or StringBuilder as we do not expect any IOException to be thrown
* @param value
* @param standardConformingStrings
* @throws SQLException
*/
private static void doAppendEscapedLiteral(Appendable sbuf, String value, boolean standardConformingStrings)
throws SQLException
{
try
{
if (standardConformingStrings)
{
// With standard_conforming_strings on, escape only single-quotes.
for (int i = 0; i < value.length(); ++i)
{
char ch = value.charAt(i);
if (ch == '\0')
throw new PSQLException(GT.tr("Zero bytes may not occur in string parameters."), PSQLState.INVALID_PARAMETER_VALUE);
if (ch == '\'')
sbuf.append('\'');
sbuf.append(ch);
}
}
else
{
// With standard_conforming_string off, escape backslashes and
// single-quotes, but still escape single-quotes by doubling, to
// avoid a security hazard if the reported value of
// standard_conforming_strings is incorrect, or an error if
// backslash_quote is off.
for (int i = 0; i < value.length(); ++i)
{
char ch = value.charAt(i);
if (ch == '\0')
throw new PSQLException(GT.tr("Zero bytes may not occur in string parameters."), PSQLState.INVALID_PARAMETER_VALUE);
if (ch == '\\' || ch == '\'')
sbuf.append(ch);
sbuf.append(ch);
}
}
}
catch (IOException e)
{
throw new PSQLException(GT.tr("No IOException expected from StringBuffer or StringBuilder"), PSQLState.UNEXPECTED_ERROR, e);
}
}
/**
* Escape the given identifier value and append it to the string buffer
* sbuf. If sbuf is null, a new
* StringBuffer will be returned. This method is different from
* appendEscapedLiteral in that it includes the quoting required for the
* identifier while appendEscapedLiteral does not.
*
* @param sbuf the string buffer to append to; or null
* @param value the string value
* @return the sbuf argument; or a new string buffer for sbuf == null
* @throws SQLException if the string contains a \0 character
* @deprecated use {@link #escapeIdentifier(StringBuilder, String)} instead
*/
public static StringBuffer appendEscapedIdentifier(StringBuffer sbuf, String value)
throws SQLException
{
if (sbuf == null)
{
sbuf = new StringBuffer(2 + value.length() * 11 / 10); // Add 10% for escaping.
}
doAppendEscapedIdentifier(sbuf, value);
return sbuf;
}
/**
* Escape the given identifier value and append it to the string builder
* sbuf. If sbuf is null, a new
* StringBuilder will be returned. This method is different from
* appendEscapedLiteral in that it includes the quoting required for the
* identifier while {@link #escapeLiteral(StringBuilder, String, boolean)} does not.
*
* @param sbuf the string builder to append to; or null
* @param value the string value
* @return the sbuf argument; or a new string builder for sbuf == null
* @throws SQLException if the string contains a \0 character
*/
public static StringBuilder escapeIdentifier(StringBuilder sbuf, String value)
throws SQLException
{
if (sbuf == null)
{
sbuf = new StringBuilder(2 + value.length() * 11 / 10); // Add 10% for escaping.
}
doAppendEscapedIdentifier(sbuf, value);
return sbuf;
}
/**
* Common part for appendEscapedIdentifier
* @param sbuf Either StringBuffer or StringBuilder as we do not expect any IOException to be thrown.
* @param value
* @throws SQLException
*/
private static void doAppendEscapedIdentifier(Appendable sbuf, String value)
throws SQLException
{
try
{
sbuf.append('"');
for (int i = 0; i < value.length(); ++i)
{
char ch = value.charAt(i);
if (ch == '\0')
throw new PSQLException(GT.tr("Zero bytes may not occur in identifiers."), PSQLState.INVALID_PARAMETER_VALUE);
if (ch == '"')
sbuf.append(ch);
sbuf.append(ch);
}
sbuf.append('"');
}
catch (IOException e)
{
throw new PSQLException(GT.tr("No IOException expected from StringBuffer or StringBuilder"), PSQLState.UNEXPECTED_ERROR, e);
}
}
/**
* Attempt to parse the server version string into an XXYYZZ form version number.
*
* Returns 0 if the version could not be parsed.
*
* Returns minor version 0 if the minor version could not be determined, e.g. devel
* or beta releases.
*
* If a single major part like 90400 is passed, it's assumed to be a pre-parsed
* version and returned verbatim. (Anything equal to or greater than 10000
* is presumed to be this form).
*
* The yy or zz version parts may be larger than 99. A
* NumberFormatException is thrown if a version part is out of range.
*/
public static int parseServerVersionStr(String serverVersion)
throws NumberFormatException
{
int vers;
NumberFormat numformat = NumberFormat.getIntegerInstance();
numformat.setGroupingUsed(false);
ParsePosition parsepos = new ParsePosition(0);
Long parsed;
if (serverVersion == null)
return 0;
/* Get first major version part */
parsed = (Long) numformat.parseObject(serverVersion, parsepos);
if (parsed == null) {
return 0;
}
if (parsed.intValue() >= 10000)
{
/*
* PostgreSQL version 1000? I don't think so. We're seeing a version like
* 90401; return it verbatim, but only if there's nothing else in the version.
* If there is, treat it as a parse error.
*/
if (parsepos.getIndex() == serverVersion.length())
return parsed.intValue();
else
throw new NumberFormatException("First major-version part equal to or greater than 10000 in invalid version string: " + serverVersion);
}
vers = parsed.intValue() * 10000;
/* Did we run out of string? */
if (parsepos.getIndex() == serverVersion.length())
return 0;
/* Skip the . */
if (serverVersion.charAt(parsepos.getIndex()) == '.')
parsepos.setIndex(parsepos.getIndex() + 1);
else
/* Unexpected version format */
return 0;
/*
* Get second major version part. If this isn't purely an integer,
* accept the integer part and return with a minor version of zero,
* so we cope with 8.1devel, etc.
*/
parsed = (Long) numformat.parseObject(serverVersion, parsepos);
if (parsed == null) {
/*
* Failed to parse second part of minor version at all. Half
* a major version is useless, return 0.
*/
return 0;
}
if (parsed.intValue() > 99)
throw new NumberFormatException("Unsupported second part of major version > 99 in invalid version string: " + serverVersion);
vers = vers + parsed.intValue() * 100;
/* Did we run out of string? Return just the major. */
if (parsepos.getIndex() == serverVersion.length())
return vers;
/* Skip the . */
if (serverVersion.charAt(parsepos.getIndex()) == '.')
parsepos.setIndex(parsepos.getIndex() + 1);
else
/* Doesn't look like an x.y.z version, return what we have */
return vers;
/* Try to parse any remainder as a minor version */
parsed = (Long) numformat.parseObject(serverVersion, parsepos);
if (parsed != null) {
if (parsed.intValue() > 99)
throw new NumberFormatException("Unsupported minor version value > 99 in invalid version string: " + serverVersion);
vers = vers + parsed.intValue();
}
return vers;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy