/* Copyright (C) 2002 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.mysql.jdbc; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.lang.ref.SoftReference; import java.net.Socket; import java.security.NoSuchAlgorithmException; import java.sql.SQLException; import java.sql.SQLWarning; import java.util.ArrayList; import java.util.Properties; import java.util.zip.Deflater; import java.util.zip.Inflater; /** * This class is used by Connection for communicating with the MySQL server. * * @author Mark Matthews * @version $Id: MysqlIO.java,v 1.32.2.43 2003/12/24 05:16:24 mmatthew Exp $ * * @see java.sql.Connection */ public class MysqlIO { static final int NULL_LENGTH = ~0; static final int COMP_HEADER_LENGTH = 3; static final int MIN_COMPRESS_LEN = 50; static final int HEADER_LENGTH = 4; private static int maxBufferSize = 65535; private static final int CLIENT_COMPRESS = 32; /* Can use compression protcol */ private static final int CLIENT_CONNECT_WITH_DB = 8; private static final int CLIENT_FOUND_ROWS = 2; private static final int CLIENT_IGNORE_SPACE = 256; /* Ignore spaces before '(' */ private static final int CLIENT_LOCAL_FILES = 128; /* Can use LOAD DATA LOCAL */ /* Found instead of affected rows */ private static final int CLIENT_LONG_FLAG = 4; /* Get all column flags */ private static final int CLIENT_LONG_PASSWORD = 1; /* new more secure passwords */ private static final int CLIENT_PROTOCOL_41 = 512; // for > 4.1.1 private static final int CLIENT_INTERACTIVE = 1024; private static final int CLIENT_SSL = 2048; private static final int CLIENT_RESERVED = 16384; // for 4.1.0 only private static final int CLIENT_SECURE_CONNECTION = 32768; private static final String FALSE_SCRAMBLE = "xxxxxxxx"; /** * We store the platform 'encoding' here, only used to avoid munging * filenames for LOAD DATA LOCAL INFILE... */ private static String jvmPlatformCharset = null; static { OutputStreamWriter outWriter = null; // // Use the I/O system to get the encoding (if possible), to avoid // security restrictions on System.getProperty("file.encoding") in // applets (why is that restricted?) // try { outWriter = new OutputStreamWriter(new ByteArrayOutputStream()); jvmPlatformCharset = outWriter.getEncoding(); } finally { try { outWriter.close(); } catch (IOException ioEx) { // ignore } } } // // Use this when reading in rows to avoid thousands of new() // calls, because the byte arrays just get copied out of the // packet anyway // private Buffer reusablePacket = null; private Buffer sendPacket = null; private Buffer sharedSendPacket = null; /** Data to the server */ //private DataOutputStream _Mysql_Output = null; private BufferedOutputStream mysqlOutput = null; private com.mysql.jdbc.Connection connection; private Deflater deflater = null; private Inflater inflater = null; /** Buffered data from the server */ //private BufferedInputStream _Mysql_Buf_Input = null; /** Buffered data to the server */ //private BufferedOutputStream _Mysql_Buf_Output = null; /** Data from the server */ //private DataInputStream _Mysql_Input = null; private InputStream mysqlInput = null; private RowData streamingData = null; // // For SQL Warnings // private SQLWarning warningChain = null; /** The connection to the server */ private Socket mysqlConnection = null; private SocketFactory socketFactory = null; // // Packet used for 'LOAD DATA LOCAL INFILE' // // We use a SoftReference, so that we don't penalize intermittent // use of this feature // private SoftReference loadFileBufRef; // // Used to send large packets to the server versions 4+ // We use a SoftReference, so that we don't penalize intermittent // use of this feature // private SoftReference splitBufRef; private String host = null; private String seed; private String serverVersion = null; private String socketFactoryClassName = null; private boolean clearStreamBeforeEachQuery = false; private boolean colDecimalNeedsBump = false; // do we need to increment the colDecimal flag? private boolean has41NewNewProt = false; /** Does the server support long column info? */ private boolean hasLongColumnInfo = false; private boolean isInteractiveClient = false; /** * Does the character set of this connection match the character set of the * platform */ private boolean platformDbCharsetMatches = true; private boolean profileSql = false; /** Should we use 4.1 protocol extensions? */ private boolean use41Extensions = false; private boolean useCompression = false; private boolean useNewLargePackets = false; private boolean useNewUpdateCounts = false; // should we use the new larger update counts? private byte packetSequence = 0; private byte protocolVersion = 0; private int clientParam = 0; // changed once we've connected. private int maxAllowedPacket = 1024 * 1024; private int maxThreeBytes = 255 * 255 * 255; private int port = 3306; private int serverMajorVersion = 0; private int serverMinorVersion = 0; private int serverSubMinorVersion = 0; /** * Constructor: Connect to the MySQL server and setup a stream connection. * * @param host the hostname to connect to * @param port the port number that the server is listening on * @param socketFactoryClassName the socket factory to use * @param props the Properties from DriverManager.getConnection() * @param conn the Connection that is creating us * @param socketTimeout the timeout to set for the socket (0 means no * timeout) * * @throws IOException if an IOException occurs during connect. * @throws java.sql.SQLException if a database access error occurs. */ protected MysqlIO(String host, int port, String socketFactoryClassName, Properties props, com.mysql.jdbc.Connection conn, int socketTimeout) throws IOException, java.sql.SQLException { this.connection = conn; this.reusablePacket = new Buffer(this.connection.getNetBufferLength()); this.port = port; this.host = host; this.socketFactoryClassName = socketFactoryClassName; this.socketFactory = createSocketFactory(); this.mysqlConnection = socketFactory.connect(this.host, props); this.clearStreamBeforeEachQuery = this.connection.alwaysClearStream(); if (socketTimeout != 0) { try { this.mysqlConnection.setSoTimeout(socketTimeout); } catch (Exception ex) { /* Ignore if the platform does not support it */ } } this.mysqlConnection = this.socketFactory.beforeHandshake(); this.mysqlInput = new BufferedInputStream(this.mysqlConnection .getInputStream(), 16384); this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection .getOutputStream(), 16384); this.isInteractiveClient = this.connection.isInteractiveClient(); } /** * Should the driver generate SQL statement profiles? * * @param flag should the driver enable profiling? */ protected void setProfileSql(boolean flag) { this.profileSql = flag; } /** * Build a result set. Delegates to buildResultSetWithRows() to build a * JDBC-version-specific ResultSet, given rows as byte data, and field * information. * * @param columnCount the number of columns in the result set * @param maxRows the maximum number of rows to read (-1 means all rows) * @param resultSetType the type of result set (CONCUR_UPDATABLE or * READ_ONLY) * @param streamResults should the result set be read all at once, or * streamed? * @param catalog the database name in use when the result set was created * * @return a result set * * @throws Exception if a database access error occurs */ protected ResultSet getResultSet(long columnCount, int maxRows, int resultSetType, boolean streamResults, String catalog) throws Exception { Buffer packet; // The packet from the server Field[] fields = new Field[(int) columnCount]; // Read in the column information for (int i = 0; i < columnCount; i++) { packet = readPacket(); fields[i] = unpackField(packet, false); } packet = reuseAndReadPacket(this.reusablePacket); RowData rowData = null; if (!streamResults) { ArrayList rows = new ArrayList(); // Now read the data byte[][] rowBytes = nextRow((int) columnCount); int rowCount = 0; if (rowBytes != null) { rows.add(rowBytes); rowCount = 1; } while ((rowBytes != null) && (rowCount < maxRows)) { rowBytes = nextRow((int) columnCount); if (rowBytes != null) { rows.add(rowBytes); rowCount++; } else { if (Driver.TRACE) { Debug.msg(this, "* NULL Row *"); } } } // // Clear any outstanding data left on the wire // when we've artifically limited the number of // rows we retrieve (fix for BUG#1695) // if (rowCount <= maxRows) { clearInputStream(); } if (Driver.TRACE) { Debug.msg(this, "* Fetched " + rows.size() + " rows from server *"); } rowData = new RowDataStatic(rows); reclaimLargeReusablePacket(); } else { rowData = new RowDataDynamic(this, (int) columnCount); this.streamingData = rowData; } return buildResultSetWithRows(catalog, fields, rowData, resultSetType); } /** * Forcibly closes the underlying socket to MySQL. */ protected final void forceClose() { try { if (this.mysqlInput != null) { this.mysqlInput.close(); } } catch (IOException ioEx) { // we can't do anything constructive about this // Let the JVM clean it up later this.mysqlInput = null; } try { if (this.mysqlOutput != null) { this.mysqlOutput.close(); } } catch (IOException ioEx) { // we can't do anything constructive about this // Let the JVM clean it up later this.mysqlOutput = null; } try { if (this.mysqlConnection != null) { this.mysqlConnection.close(); } } catch (IOException ioEx) { // we can't do anything constructive about this // Let the JVM clean it up later this.mysqlConnection = null; } } /** * Does the server send back extra column info? * * @return true if so */ protected boolean hasLongColumnInfo() { return this.hasLongColumnInfo; } /** * Unpacks the Field information from the given packet. Understands pre 4.1 * and post 4.1 server version field packet structures. * * @param packet the packet containing the field information * @param extractDefaultValues should default values be extracted? * * @return the unpacked field */ protected final Field unpackField(Buffer packet, boolean extractDefaultValues) { if (this.use41Extensions) { // we only store the position of the string and // materialize only if needed... if (this.has41NewNewProt) { int catalogNameStart = packet.getPosition() + 1; int catalogNameLength = packet.fastSkipLenString(); } int databaseNameStart = packet.getPosition() + 1; int databaseNameLength = packet.fastSkipLenString(); int tableNameStart = packet.getPosition() + 1; int tableNameLength = packet.fastSkipLenString(); // orgTableName is never used so skip int originalTableNameStart = packet.getPosition() + 1; int originalTableNameLength = packet.fastSkipLenString(); // we only store the position again... int nameStart = packet.getPosition() + 1; int nameLength = packet.fastSkipLenString(); // orgColName is not required so skip... int originalColumnNameStart = packet.getPosition() + 1; int originalColumnNameLength = packet.fastSkipLenString(); packet.readByte(); int charSetNumber = packet.readInt(); int colLength = 0; if (this.has41NewNewProt) { // fixme colLength = (int) packet.readLong(); } else { colLength = packet.readLongInt(); } int colType = packet.readByte() & 0xff; short colFlag = 0; if (this.hasLongColumnInfo) { colFlag = (short) (packet.readInt()); } else { colFlag = (short) (packet.readByte() & 0xff); } int colDecimals = packet.readByte() & 0xff; int defaultValueStart = -1; int defaultValueLength = -1; if (extractDefaultValues) { defaultValueStart = packet.getPosition() + 1; defaultValueLength = packet.fastSkipLenString(); } Field field = new Field(this.connection, packet.getByteBuffer(), databaseNameStart, databaseNameLength, tableNameStart, tableNameLength, originalTableNameStart, originalTableNameLength, nameStart, nameLength, originalColumnNameStart, originalColumnNameLength, colLength, colType, colFlag, colDecimals, defaultValueStart, defaultValueLength, charSetNumber); return field; } else { int tableNameStart = packet.getPosition() + 1; int tableNameLength = packet.fastSkipLenString(); int nameStart = packet.getPosition() + 1; int nameLength = packet.fastSkipLenString(); int colLength = packet.readnBytes(); int colType = packet.readnBytes(); packet.readByte(); // We know it's currently 2 short colFlag = 0; if (this.hasLongColumnInfo) { colFlag = (short) (packet.readInt()); } else { colFlag = (short) (packet.readByte() & 0xff); } int colDecimals = (packet.readByte() & 0xff); if (this.colDecimalNeedsBump) { colDecimals++; } Field field = new Field(this.connection, packet.getBufferSource(), nameStart, nameLength, tableNameStart, tableNameLength, colLength, colType, colFlag, colDecimals); return field; } } /** * Determines if the database charset is the same as the platform charset */ protected void checkForCharsetMismatch() { if (this.connection.useUnicode() && (this.connection.getEncoding() != null)) { String encodingToCheck = jvmPlatformCharset; if (encodingToCheck == null) { encodingToCheck = System.getProperty("file.encoding"); } if (encodingToCheck == null) { this.platformDbCharsetMatches = false; } else { this.platformDbCharsetMatches = encodingToCheck.equals(this.connection .getEncoding()); } } } static int getMaxBuf() { return maxBufferSize; } /** * Get the major version of the MySQL server we are talking to. * * @return DOCUMENT ME! */ final int getServerMajorVersion() { return this.serverMajorVersion; } /** * Get the minor version of the MySQL server we are talking to. * * @return DOCUMENT ME! */ final int getServerMinorVersion() { return this.serverMinorVersion; } /** * Get the sub-minor version of the MySQL server we are talking to. * * @return DOCUMENT ME! */ final int getServerSubMinorVersion() { return this.serverSubMinorVersion; } /** * Get the version string of the server we are talking to * * @return DOCUMENT ME! */ String getServerVersion() { return this.serverVersion; } /** * Initialize communications with the MySQL server. Handles logging on, and * handling initial connection errors. * * @param user DOCUMENT ME! * @param password DOCUMENT ME! * @param database DOCUMENT ME! * * @throws java.sql.SQLException DOCUMENT ME! * @throws SQLException DOCUMENT ME! */ void doHandshake(String user, String password, String database) throws java.sql.SQLException { // Read the first packet Buffer buf = readPacket(); // Get the protocol version this.protocolVersion = buf.readByte(); if (this.protocolVersion == -1) { try { this.mysqlConnection.close(); } catch (Exception e) { ; // ignore } int errno = 2000; errno = buf.readInt(); String serverErrorMessage = buf.readString(); StringBuffer errorBuf = new StringBuffer(" message from server: \""); errorBuf.append(serverErrorMessage); errorBuf.append("\""); String xOpen = SQLError.mysqlToXOpen(errno); throw new SQLException(SQLError.get(xOpen) + ", " + errorBuf.toString(), xOpen, errno); } this.serverVersion = buf.readString(); // Parse the server version into major/minor/subminor int point = this.serverVersion.indexOf("."); if (point != -1) { try { int n = Integer.parseInt(this.serverVersion.substring(0, point)); this.serverMajorVersion = n; } catch (NumberFormatException NFE1) { ; } String remaining = this.serverVersion.substring(point + 1, this.serverVersion.length()); point = remaining.indexOf("."); if (point != -1) { try { int n = Integer.parseInt(remaining.substring(0, point)); this.serverMinorVersion = n; } catch (NumberFormatException nfe) { ; } remaining = remaining.substring(point + 1, remaining.length()); int pos = 0; while (pos < remaining.length()) { if ((remaining.charAt(pos) < '0') || (remaining.charAt(pos) > '9')) { break; } pos++; } try { int n = Integer.parseInt(remaining.substring(0, pos)); this.serverSubMinorVersion = n; } catch (NumberFormatException nfe) { ; } } } if (versionMeetsMinimum(4, 0, 8)) { this.maxThreeBytes = (256 * 256 * 256) - 1; this.useNewLargePackets = true; } else { this.maxThreeBytes = 255 * 255 * 255; this.useNewLargePackets = false; } this.colDecimalNeedsBump = versionMeetsMinimum(3, 23, 0); this.colDecimalNeedsBump = !versionMeetsMinimum(3, 23, 15); // guess? Not noted in changelog this.useNewUpdateCounts = versionMeetsMinimum(3, 22, 5); long threadId = buf.readLong(); seed = buf.readString(); if (Driver.TRACE) { Debug.msg(this, "Protocol Version: " + (int) this.protocolVersion); Debug.msg(this, "Server Version: " + this.serverVersion); Debug.msg(this, "Thread ID: " + threadId); Debug.msg(this, "Crypt Seed: " + seed); } int serverCapabilities = 0; if (buf.getPosition() < buf.getBufLength()) { serverCapabilities = buf.readInt(); } if (versionMeetsMinimum(4, 1, 1)) { int position = buf.getPosition(); /* New protocol with 16 bytes to describe server characteristics */ int serverLanguage = buf.readInt(); // 2 bytes buf.readInt(); buf.setPosition(position + 16); String seedPart2 = buf.readString(); StringBuffer newSeed = new StringBuffer(20); newSeed.append(seed); newSeed.append(seedPart2); this.seed = newSeed.toString(); } if (((serverCapabilities & CLIENT_COMPRESS) != 0) && this.connection.useCompression()) { clientParam |= CLIENT_COMPRESS; } if ((database != null) && (database.length() > 0)) { clientParam |= CLIENT_CONNECT_WITH_DB; } if (((serverCapabilities & CLIENT_SSL) == 0) && this.connection.useSSL()) { this.connection.setUseSSL(false); } if ((serverCapabilities & CLIENT_LONG_FLAG) != 0) { // We understand other column flags, as well clientParam |= CLIENT_LONG_FLAG; this.hasLongColumnInfo = true; } // return FOUND rows clientParam |= CLIENT_FOUND_ROWS; if (this.connection.allowLoadLocalInfile()) { clientParam |= CLIENT_LOCAL_FILES; } if (isInteractiveClient) { clientParam |= CLIENT_INTERACTIVE; } // Authenticate if (this.protocolVersion > 9) { clientParam |= CLIENT_LONG_PASSWORD; // for long passwords } else { clientParam &= ~CLIENT_LONG_PASSWORD; } // // 4.1 has some differences in the protocol // if (versionMeetsMinimum(4, 1, 0)) { if (versionMeetsMinimum(4, 1, 1)) { clientParam |= CLIENT_PROTOCOL_41; this.has41NewNewProt = true; } else { clientParam |= CLIENT_RESERVED; this.has41NewNewProt = false; } this.use41Extensions = true; } int passwordLength = 16; int userLength = 0; int databaseLength = 0; if (user != null) { userLength = user.length(); } if (database != null) { databaseLength = database.length(); } int packLength = (userLength + passwordLength + databaseLength) + 7 + HEADER_LENGTH; Buffer packet = null; if (!connection.useSSL()) { if ((serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) { clientParam |= CLIENT_SECURE_CONNECTION; if (versionMeetsMinimum(4, 1, 1)) { secureAuth411(packLength, serverCapabilities, clientParam, user, password, database); } else { secureAuth(packLength, serverCapabilities, clientParam, user, password, database); } } else { packet = new Buffer(packLength); if ((clientParam & CLIENT_RESERVED) != 0) { if (versionMeetsMinimum(4, 1, 1)) { packet.writeLong(clientParam); packet.writeLong(this.maxThreeBytes); // charset, JDBC will connect as 'latin1', // and use 'SET NAMES' to change to the desired // charset after the connection is established. packet.writeByte((byte) 8); // Set of bytes reserved for future use. packet.writeBytesNoNull(new byte[23]); } else { packet.writeLong(clientParam); packet.writeLong(this.maxThreeBytes); } } else { packet.writeInt((int) clientParam); packet.writeLongInt(this.maxThreeBytes); } // User/Password data packet.writeString(user); if (this.protocolVersion > 9) { packet.writeString(Util.newCrypt(password, this.seed)); } else { packet.writeString(Util.oldCrypt(password, this.seed)); } if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0) && (database != null) && (database.length() > 0)) { packet.writeString(database); } send(packet); } } else { boolean doSecureAuth = false; if ((serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) { clientParam |= CLIENT_SECURE_CONNECTION; doSecureAuth = true; } clientParam |= CLIENT_SSL; packet = new Buffer(packLength); if ((clientParam & CLIENT_RESERVED) != 0) { packet.writeLong(clientParam); } else { packet.writeInt((int) clientParam); } send(packet); javax.net.ssl.SSLSocketFactory sslFact = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory .getDefault(); try { this.mysqlConnection = sslFact.createSocket(this.mysqlConnection, this.host, this.port, true); // need to force TLSv1, or else JSSE tries to do a SSLv2 handshake // which MySQL doesn't understand ((javax.net.ssl.SSLSocket) this.mysqlConnection) .setEnabledProtocols(new String[] { "TLSv1" }); ((javax.net.ssl.SSLSocket) this.mysqlConnection).startHandshake(); this.mysqlInput = new BufferedInputStream(this.mysqlConnection .getInputStream(), 16384); this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection .getOutputStream(), 16384); this.mysqlOutput.flush(); } catch (IOException ioEx) { StringBuffer message = new StringBuffer(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)); message.append(": "); message.append(ioEx.getClass().getName()); message.append(", underlying cause: "); message.append(ioEx.getMessage()); if (!this.connection.useParanoidErrorMessages()) { message.append(Util.stackTraceToString(ioEx)); } throw new java.sql.SQLException(message.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } packet.clear(); if (doSecureAuth) { if (versionMeetsMinimum(4, 1, 1)) { secureAuth411(packLength, serverCapabilities, clientParam, user, password, database); } else { secureAuth(packLength, serverCapabilities, clientParam, user, password, database); } } else { if ((clientParam & CLIENT_RESERVED) != 0) { packet.writeLong(clientParam); packet.writeLong(this.maxThreeBytes); } else { packet.writeInt((int) clientParam); packet.writeLongInt(this.maxThreeBytes); } // User/Password data packet.writeString(user); if (this.protocolVersion > 9) { packet.writeString(Util.newCrypt(password, seed)); } else { packet.writeString(Util.oldCrypt(password, seed)); } if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0) && (database != null) && (database.length() > 0)) { packet.writeString(database); } send(packet); } } // Check for errors, not for 4.1.1 or newer, // as the new auth protocol doesn't work that way // (see secureAuth411() for more details...) if (!versionMeetsMinimum(4, 1, 1)) { checkErrorPacket(); } // // Can't enable compression until after handshake // if (((serverCapabilities & CLIENT_COMPRESS) != 0) && this.connection.useCompression()) { // The following matches with ZLIB's // compress() this.deflater = new Deflater(); this.useCompression = true; this.mysqlInput = new CompressedInputStream(this.mysqlInput); } if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) == 0) && (database != null) && (database.length() > 0)) { try { sendCommand(MysqlDefs.INIT_DB, database, null); } catch (Exception ex) { throw new SQLException(ex.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE); } } } /** * Retrieve one row from the MySQL server. Note: this method is not * thread-safe, but it is only called from methods that are guarded by * synchronizing on this object. * * @param columnCount DOCUMENT ME! * * @return DOCUMENT ME! * * @throws Exception DOCUMENT ME! */ final byte[][] nextRow(int columnCount) throws Exception { // Get the next incoming packet, re-using the packet because // all the data we need gets copied out of it. Buffer rowPacket = checkErrorPacket(); // // Didn't read an error, so re-position to beginning // of packet in order to read result set data // int offset = 0; //if (rowPacket.wasMultiPacket()) { // if (this.useNewLargePackets) { // offset = HEADER_LENGTH; // } else { // offset = HEADER_LENGTH + 1; // } //} rowPacket.setPosition(rowPacket.getPosition() - 1); byte[][] rowData = new byte[columnCount][]; if (!rowPacket.isLastDataPacket()) { for (int i = 0; i < columnCount; i++) { rowData[i] = rowPacket.readLenByteArray(offset); if (Driver.TRACE) { if (rowData[i] == null) { Debug.msg(this, "Field value: NULL"); } else { Debug.msg(this, "Field value: " + rowData[i].toString()); } } } return rowData; } return null; } /** * Log-off of the MySQL server and close the socket. * * @throws SQLException DOCUMENT ME! */ final void quit() throws SQLException { Buffer packet = new Buffer(6); this.packetSequence = -1; packet.writeByte((byte) MysqlDefs.QUIT); send(packet); forceClose(); } /** * Returns the packet used for sending data (used by PreparedStatement) * Guarded by external synchronization on a mutex. * * @return A packet to send data with */ Buffer getSharedSendPacket() { if (this.sharedSendPacket == null) { this.sharedSendPacket = new Buffer(this.connection .getNetBufferLength()); } return this.sharedSendPacket; } void closeStreamer(RowData streamer) throws SQLException { if (this.streamingData == null) { throw new SQLException("Attempt to close streaming result set " + streamer + " when no streaming result set was registered. This is an internal error."); } if (streamer != this.streamingData) { throw new SQLException("Attempt to close streaming result set " + streamer + " that was not registered." + " Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on " + " any active result sets before attempting more queries."); } this.streamingData = null; } /** * Sets the buffer size to max-buf */ void resetMaxBuf() { this.maxAllowedPacket = this.connection.getMaxAllowedPacket(); } /** * Send a command to the MySQL server If data is to be sent with command, * it should be put in ExtraData Raw packets can be sent by setting * QueryPacket to something other than null. * * @param command DOCUMENT ME! * @param extraData DOCUMENT ME! * @param queryPacket DOCUMENT ME! * * @return DOCUMENT ME! * * @throws Exception DOCUMENT ME! * @throws java.sql.SQLException DOCUMENT ME! */ final Buffer sendCommand(int command, String extraData, Buffer queryPacket) throws Exception { checkForOutstandingStreamingData(); try { if (this.clearStreamBeforeEachQuery) { clearInputStream(); } // // PreparedStatements construct their own packets, // for efficiency's sake. // // If this is a generic query, we need to re-use // the sending packet. // if (queryPacket == null) { int packLength = HEADER_LENGTH + COMP_HEADER_LENGTH + 1 + ((extraData != null) ? extraData.length() : 0) + 2; if (this.sendPacket == null) { this.sendPacket = new Buffer(packLength); } this.packetSequence = -1; this.sendPacket.clear(); // Offset different for compression if (this.useCompression) { this.sendPacket.setPosition(this.sendPacket.getPosition() + COMP_HEADER_LENGTH); } this.sendPacket.writeByte((byte) command); if ((command == MysqlDefs.INIT_DB) || (command == MysqlDefs.CREATE_DB) || (command == MysqlDefs.DROP_DB) || (command == MysqlDefs.QUERY)) { this.sendPacket.writeStringNoNull(extraData); } else if (command == MysqlDefs.PROCESS_KILL) { long id = new Long(extraData).longValue(); this.sendPacket.writeLong(id); } else if ((command == MysqlDefs.RELOAD) && (this.protocolVersion > 9)) { Debug.msg(this, "Reload"); //Packet.writeByte(reloadParam); } send(this.sendPacket); } else { this.packetSequence = -1; send(queryPacket); // packet passed by PreparedStatement } } catch (SQLException sqlEx) { // don't wrap SQLExceptions throw sqlEx; } catch (Exception ex) { String underlyingMessage = ex.getMessage(); throw new java.sql.SQLException(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE) + ": " + ex.getClass().getName() + ", " + ((underlyingMessage != null) ? underlyingMessage : "no message given by JVM"), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } return checkErrorPacket(command); } /** * Send a query specified in the String "Query" to the MySQL server. This * method uses the specified character encoding to get the bytes from the * query string. * * @param query DOCUMENT ME! * @param maxRows DOCUMENT ME! * @param characterEncoding DOCUMENT ME! * @param conn DOCUMENT ME! * @param resultSetType DOCUMENT ME! * @param streamResults DOCUMENT ME! * @param catalog DOCUMENT ME! * * @return DOCUMENT ME! * * @throws Exception DOCUMENT ME! */ final ResultSet sqlQuery(String query, int maxRows, String characterEncoding, Connection conn, int resultSetType, boolean streamResults, String catalog) throws Exception { // We don't know exactly how many bytes we're going to get // from the query. Since we're dealing with Unicode, the // max is 2, so pad it (2 * query) + space for headers int packLength = HEADER_LENGTH + 1 + (query.length() * 2) + 2; if (this.sendPacket == null) { this.sendPacket = new Buffer(packLength); } else { this.sendPacket.clear(); } this.sendPacket.writeByte((byte) MysqlDefs.QUERY); if (characterEncoding != null) { SingleByteCharsetConverter converter = this.connection .getCharsetConverter(characterEncoding); if (this.platformDbCharsetMatches) { this.sendPacket.writeStringNoNull(query, characterEncoding, converter); } else { if (StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) { this.sendPacket.writeBytesNoNull(query.getBytes()); } else { this.sendPacket.writeStringNoNull(query, characterEncoding, converter); } } } else { this.sendPacket.writeStringNoNull(query); } return sqlQueryDirect(this.sendPacket, maxRows, conn, resultSetType, streamResults, catalog); } /** * Send a query stored in a packet directly to the server. * * @param queryPacket DOCUMENT ME! * @param maxRows DOCUMENT ME! * @param conn DOCUMENT ME! * @param resultSetType DOCUMENT ME! * @param streamResults DOCUMENT ME! * @param catalog DOCUMENT ME! * * @return DOCUMENT ME! * * @throws Exception DOCUMENT ME! */ final ResultSet sqlQueryDirect(Buffer queryPacket, int maxRows, Connection conn, int resultSetType, boolean streamResults, String catalog) throws Exception { StringBuffer profileMsgBuf = null; // used if profiling long queryStartTime = 0; if (this.profileSql) { profileMsgBuf = new StringBuffer(); queryStartTime = System.currentTimeMillis(); byte[] queryBuf = queryPacket.getByteBuffer(); // Extract the actual query from the network packet String query = new String(queryBuf, 5, (queryPacket.getPosition() - 5)); profileMsgBuf.append("Query\t\""); profileMsgBuf.append(query); profileMsgBuf.append("\"\texecution time:\t"); } // Send query command and sql query string Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket); if (this.profileSql) { long executionTime = System.currentTimeMillis() - queryStartTime; profileMsgBuf.append(executionTime); profileMsgBuf.append("\t"); } resultPacket.setPosition(resultPacket.getPosition() - 1); long columnCount = resultPacket.readFieldLength(); if (Driver.TRACE) { Debug.msg(this, "Column count: " + columnCount); } if (columnCount == 0) { if (this.profileSql) { System.err.println(profileMsgBuf.toString()); } return buildResultSetWithUpdates(resultPacket); } else if (columnCount == Buffer.NULL_LENGTH) { String charEncoding = null; if (this.connection.useUnicode()) { charEncoding = this.connection.getEncoding(); } String fileName = null; if (this.platformDbCharsetMatches) { fileName = ((charEncoding != null) ? resultPacket.readString(charEncoding) : resultPacket.readString()); } else { fileName = resultPacket.readString(); } return sendFileToServer(fileName); } else { long fetchStartTime = 0; if (this.profileSql) { fetchStartTime = System.currentTimeMillis(); } com.mysql.jdbc.ResultSet results = getResultSet(columnCount, maxRows, resultSetType, streamResults, catalog); if (this.profileSql) { long fetchElapsedTime = System.currentTimeMillis() - fetchStartTime; profileMsgBuf.append("result set fetch time:\t"); profileMsgBuf.append(fetchElapsedTime); System.err.println(profileMsgBuf.toString()); } return results; } } /** * Returns the host this IO is connected to * * @return DOCUMENT ME! */ String getHost() { return this.host; } /** * Does the version of the MySQL server we are connected to meet the given * minimums? * * @param major DOCUMENT ME! * @param minor DOCUMENT ME! * @param subminor DOCUMENT ME! * * @return DOCUMENT ME! */ boolean versionMeetsMinimum(int major, int minor, int subminor) { if (getServerMajorVersion() >= major) { if (getServerMajorVersion() == major) { if (getServerMinorVersion() >= minor) { if (getServerMinorVersion() == minor) { return (getServerSubMinorVersion() >= subminor); } else { // newer than major.minor return true; } } else { // older than major.minor return false; } } else { // newer than major return true; } } else { return false; } } private final int readFully(InputStream in, byte[] b, int off, int len) throws IOException { if (len < 0) { throw new IndexOutOfBoundsException(); } int n = 0; while (n < len) { int count = in.read(b, off + n, len - n); if (count < 0) { throw new EOFException(); } n += count; } return n; } /** * Read one packet from the MySQL server * * @return DOCUMENT ME! * * @throws SQLException DOCUMENT ME! * @throws java.sql.SQLException DOCUMENT ME! */ private final Buffer readPacket() throws SQLException { try { int packetLength = mysqlInput.read() + (mysqlInput.read() << 8) + (mysqlInput.read() << 16); // -1 for all values through above assembly sequence if (packetLength == -65793) { forceClose(); throw new IOException("Unexpected end of input stream"); } // we don't look at packet sequence in this case //this.mysqlInput.skip(1); this.mysqlInput.read(); // Read data byte[] buffer = new byte[packetLength + 1]; readFully(this.mysqlInput, buffer, 0, packetLength); buffer[packetLength] = 0; Buffer packet = new Buffer(buffer); return packet; } catch (IOException ioEx) { StringBuffer message = new StringBuffer(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)); message.append(": "); message.append(ioEx.getClass().getName()); message.append(", underlying cause: "); message.append(ioEx.getMessage()); if (!this.connection.useParanoidErrorMessages()) { message.append(Util.stackTraceToString(ioEx)); } throw new java.sql.SQLException(message.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } } private com.mysql.jdbc.ResultSet buildResultSetWithRows(String catalog, com.mysql.jdbc.Field[] fields, RowData rows, int resultSetConcurrency) throws SQLException { switch (resultSetConcurrency) { case java.sql.ResultSet.CONCUR_READ_ONLY: return new com.mysql.jdbc.ResultSet(catalog, fields, rows, this.connection); case java.sql.ResultSet.CONCUR_UPDATABLE: return new com.mysql.jdbc.UpdatableResultSet(catalog, fields, rows, this.connection); default: return new com.mysql.jdbc.ResultSet(catalog, fields, rows, this.connection); } } private com.mysql.jdbc.ResultSet buildResultSetWithUpdates( Buffer resultPacket) throws SQLException { long updateCount = -1; long updateID = -1; String info = null; try { if (this.useNewUpdateCounts) { updateCount = resultPacket.newReadLength(); updateID = resultPacket.newReadLength(); } else { updateCount = (long) resultPacket.readLength(); updateID = (long) resultPacket.readLength(); } if (this.connection.isReadInfoMsgEnabled()) { if (this.use41Extensions) { int serverStatus = resultPacket.readInt(); int warningCount = resultPacket.readInt(); resultPacket.readByte(); // advance pointer } info = resultPacket.readString(); } } catch (Exception ex) { throw new java.sql.SQLException(SQLError.get(SQLError.SQL_STATE_GENERAL_ERROR) + ": " + ex.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR, -1); } if (Driver.TRACE) { Debug.msg(this, "Update Count = " + updateCount); } ResultSet updateRs = new ResultSet(updateCount, updateID); if (info != null) { updateRs.setServerInfo(info); } return updateRs; } /** * Don't hold on to overly-large packets */ private void reclaimLargeReusablePacket() { if ((this.reusablePacket != null) && (this.reusablePacket.getBufLength() > 1048576)) { this.reusablePacket = new Buffer(this.connection.getNetBufferLength()); } } /** * Re-use a packet to read from the MySQL server * * @param reuse DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SQLException DOCUMENT ME! * @throws SQLException DOCUMENT ME! */ private final Buffer reuseAndReadPacket(Buffer reuse) throws SQLException { try { reuse.setWasMultiPacket(false); int packetLength = mysqlInput.read() + (mysqlInput.read() << 8) + (mysqlInput.read() << 16); // -1 for all values through above assembly sequence if (packetLength == -65793) { forceClose(); throw new IOException("Unexpected end of input stream"); } byte multiPacketSeq = (byte) this.mysqlInput.read(); // Set the Buffer to it's original state reuse.setPosition(0); reuse.setSendLength(0); // Do we need to re-alloc the byte buffer? // // Note: We actually check the length of the buffer, // rather than getBufLength(), because getBufLength() is not // necesarily the actual length of the byte array // used as the buffer if (reuse.getByteBuffer().length <= packetLength) { reuse.setByteBuffer(new byte[packetLength + 1]); } // Set the new length reuse.setBufLength(packetLength); // Read the data from the server readFully(this.mysqlInput, reuse.getByteBuffer(), 0, packetLength); boolean isMultiPacket = false; if (packetLength == maxThreeBytes) { reuse.setPosition((int) maxThreeBytes); int packetEndPoint = packetLength; // it's multi-packet isMultiPacket = true; packetLength = mysqlInput.read() + (mysqlInput.read() << 8) + (mysqlInput.read() << 16); // -1 for all values through above assembly sequence if (packetLength == -65793) { forceClose(); throw new IOException("Unexpected end of input stream"); } Buffer multiPacket = new Buffer(packetLength); boolean firstMultiPkt = true; while (true) { if (!firstMultiPkt) { packetLength = mysqlInput.read() + (mysqlInput.read() << 8) + (mysqlInput.read() << 16); // -1 for all values through above assembly sequence if (packetLength == -65793) { forceClose(); throw new IOException( "Unexpected end of input stream"); } } else { firstMultiPkt = false; } if (!this.useNewLargePackets && (packetLength == 1)) { clearInputStream(); break; } else if (packetLength < this.maxThreeBytes) { byte newPacketSeq = (byte) this.mysqlInput.read(); if (newPacketSeq != (multiPacketSeq + 1)) { throw new IOException( "Packets received out of order"); } multiPacketSeq = newPacketSeq; // Set the Buffer to it's original state multiPacket.setPosition(0); multiPacket.setSendLength(0); // Set the new length multiPacket.setBufLength(packetLength); // Read the data from the server byte[] byteBuf = multiPacket.getByteBuffer(); int lengthToWrite = packetLength; int bytesRead = readFully(this.mysqlInput, byteBuf, 0, packetLength); if (bytesRead != lengthToWrite) { throw new SQLException( "Short read from server, expected " + lengthToWrite + " bytes, received only " + bytesRead + ".", SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE); } reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite); packetEndPoint += lengthToWrite; break; // end of multipacket sequence } byte newPacketSeq = (byte) this.mysqlInput.read(); if (newPacketSeq != (multiPacketSeq + 1)) { throw new IOException("Packets received out of order"); } multiPacketSeq = newPacketSeq; // Set the Buffer to it's original state multiPacket.setPosition(0); multiPacket.setSendLength(0); // Set the new length multiPacket.setBufLength(packetLength); // Read the data from the server byte[] byteBuf = multiPacket.getByteBuffer(); int lengthToWrite = packetLength; int bytesRead = readFully(this.mysqlInput, byteBuf, 0, packetLength); if (bytesRead != lengthToWrite) { throw new SQLException( "Short read from server, expected " + lengthToWrite + " bytes, received only " + bytesRead + ".", SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE); } reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite); packetEndPoint += lengthToWrite; } //reuse.writeByte((byte) 0); reuse.setPosition(0); reuse.setWasMultiPacket(true); } if (!isMultiPacket) { reuse.getByteBuffer()[packetLength] = 0; // Null-termination } return reuse; } catch (IOException ioEx) { StringBuffer message = new StringBuffer(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)); message.append(": "); message.append(ioEx.getClass().getName()); message.append(", underlying cause: "); message.append(ioEx.getMessage()); if (!this.connection.useParanoidErrorMessages()) { message.append(Util.stackTraceToString(ioEx)); } throw new java.sql.SQLException(message.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } } /** * Send a packet to the MySQL server * * @param packet DOCUMENT ME! * * @throws SQLException DOCUMENT ME! */ private final void send(Buffer packet) throws SQLException { int l = packet.getPosition(); send(packet, l); // // Don't hold on to large packets // if (packet == this.sharedSendPacket) { reclaimLargeSharedSendPacket(); } } private final void send(Buffer packet, int packetLen) throws SQLException { try { if (packetLen > this.maxAllowedPacket) { throw new PacketTooBigException(packetLen, this.maxAllowedPacket); } if ((serverMajorVersion >= 4) && (packetLen >= maxThreeBytes)) { sendSplitPackets(packet); } else { this.packetSequence++; Buffer packetToSend = packet; packetToSend.setPosition(0); if (this.useCompression) { packetToSend = compressPacket(packet, 0, packetLen, HEADER_LENGTH); packetLen = packetToSend.getPosition(); } else { packetToSend.writeLongInt(packetLen - HEADER_LENGTH); packetToSend.writeByte(this.packetSequence); } this.mysqlOutput.write(packetToSend.getByteBuffer(), 0, packetLen); this.mysqlOutput.flush(); } // // Don't hold on to large packets // if (packet == this.sharedSendPacket) { reclaimLargeSharedSendPacket(); } } catch (IOException ioEx) { StringBuffer message = new StringBuffer(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)); message.append(": "); message.append(ioEx.getClass().getName()); message.append(", underlying cause: "); message.append(ioEx.getMessage()); if (!this.connection.useParanoidErrorMessages()) { message.append(Util.stackTraceToString(ioEx)); } throw new java.sql.SQLException(message.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } } /** * Reads and sends a file to the server for LOAD DATA LOCAL INFILE * * @param fileName the file name to send. * * @return DOCUMENT ME! * * @throws SQLException DOCUMENT ME! */ private final ResultSet sendFileToServer(String fileName) throws SQLException { Buffer filePacket = (loadFileBufRef == null) ? null : (Buffer) (loadFileBufRef .get()); int packetLength = Math.min(this.connection.getMaxAllowedPacket() - (HEADER_LENGTH * 3), alignPacketSize(this.connection.getMaxAllowedPacket() - 16, 4096) - (HEADER_LENGTH * 3)); // // This packet may be _way_ too large to actually allocate, // unforunately, LOAD DATA LOCAL INFILE requires this setup... // try { if (filePacket == null) { filePacket = new Buffer((int) (packetLength + HEADER_LENGTH)); loadFileBufRef = new SoftReference(filePacket); } } catch (OutOfMemoryError oom) { // Attempt to do this, but it might not work... // The server is expecting at least one packet, so we // send an empty 'EOF' packet... this.reusablePacket.clear(); send(this.reusablePacket); throw new SQLException("Unable to allocate packet of size '" + (packetLength + HEADER_LENGTH) + "' for LOAD DATA LOCAL INFILE. Either increase heap space available to your JVM, or adjust the MySQL server variable 'max_allowed_packet'", SQLError.SQL_STATE_MEMORY_ALLOCATION_FAILURE); } filePacket.clear(); send(filePacket, 0); byte[] fileBuf = new byte[packetLength]; BufferedInputStream fileIn = null; try { fileIn = new BufferedInputStream(new FileInputStream(fileName)); int bytesRead = 0; while ((bytesRead = fileIn.read(fileBuf)) != -1) { filePacket.clear(); filePacket.writeBytesNoNull(fileBuf, 0, bytesRead); send(filePacket); } } catch (IOException ioEx) { StringBuffer messageBuf = new StringBuffer("Unable to open file "); if (!this.connection.useParanoidErrorMessages()) { messageBuf.append("'"); if (fileName != null) { messageBuf.append(fileName); } messageBuf.append("'"); } messageBuf.append("for 'LOAD DATA LOCAL INFILE' command."); if (!this.connection.useParanoidErrorMessages()) { messageBuf.append("Due to underlying IOException: "); messageBuf.append(Util.stackTraceToString(ioEx)); } throw new SQLException(messageBuf.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } finally { if (fileIn != null) { try { fileIn.close(); } catch (Exception ex) { throw new SQLException("Unable to close local file during LOAD DATA LOCAL INFILE command", SQLError.SQL_STATE_GENERAL_ERROR); } fileIn = null; } else { // file open failed, but server needs one packet filePacket.clear(); send(filePacket); } } // send empty packet to mark EOF filePacket.clear(); send(filePacket); Buffer resultPacket = checkErrorPacket(); return buildResultSetWithUpdates(resultPacket); } /** * Checks for errors in the reply packet, and if none, returns the reply * packet, ready for reading * * @return DOCUMENT ME! * * @throws SQLException DOCUMENT ME! */ private Buffer checkErrorPacket() throws SQLException { return checkErrorPacket(-1); } /** * Checks for errors in the reply packet, and if none, returns the reply * packet, ready for reading * * @param command the command being issued (if used) * * @return DOCUMENT ME! * * @throws SQLException if an error packet was received * @throws java.sql.SQLException DOCUMENT ME! */ private Buffer checkErrorPacket(int command) throws SQLException { int statusCode = 0; Buffer resultPacket = null; try { // Check return value, if we get a java.io.EOFException, // the server has gone away. We'll pass it on up the // exception chain and let someone higher up decide // what to do (barf, reconnect, etc). resultPacket = reuseAndReadPacket(this.reusablePacket); statusCode = resultPacket.readByte(); } catch (SQLException sqlEx) { // don't wrap SQLExceptions throw sqlEx; } catch (Exception fallThru) { String underlyingMessage = fallThru.getMessage(); throw new java.sql.SQLException(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE) + ": " + fallThru.getClass().getName() + ", " + ((underlyingMessage != null) ? underlyingMessage : "no message given by JVM"), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } // Error handling if (statusCode == (byte) 0xff) { String serverErrorMessage; int errno = 2000; if (this.protocolVersion > 9) { errno = resultPacket.readInt(); String xOpen = null; serverErrorMessage = resultPacket.readString(); if (serverErrorMessage.startsWith("#")) { // we have an SQLState if (serverErrorMessage.length() > 6) { xOpen = serverErrorMessage.substring(1, 6); serverErrorMessage = serverErrorMessage.substring(6); if (xOpen.equals("HY000")) { xOpen = SQLError.mysqlToXOpen(errno); } } else { xOpen = SQLError.mysqlToXOpen(errno); } } else { xOpen = SQLError.mysqlToXOpen(errno); } clearInputStream(); StringBuffer errorBuf = new StringBuffer( " message from server: \""); errorBuf.append(serverErrorMessage); errorBuf.append("\""); throw new SQLException(SQLError.get(xOpen) + ", " + errorBuf.toString(), xOpen, errno); } else { serverErrorMessage = resultPacket.readString(); clearInputStream(); if (serverErrorMessage.indexOf("Unknown column") != -1) { throw new java.sql.SQLException(SQLError.get(SQLError.SQL_STATE_COLUMN_NOT_FOUND) + ", " + serverErrorMessage, SQLError.SQL_STATE_COLUMN_NOT_FOUND, -1); } else { StringBuffer errorBuf = new StringBuffer( " message from server: \""); errorBuf.append(serverErrorMessage); errorBuf.append("\""); throw new java.sql.SQLException(SQLError.get(SQLError.SQL_STATE_GENERAL_ERROR) + ", " + errorBuf.toString(), SQLError.SQL_STATE_GENERAL_ERROR, -1); } } } return resultPacket; } /** * Sends a large packet to the server as a series of smaller packets * * @param packet DOCUMENT ME! * * @throws SQLException DOCUMENT ME! * @throws SQLException DOCUMENT ME! */ private final void sendSplitPackets(Buffer packet) throws SQLException { try { // // Big packets are handled by splitting them in packets of MAX_THREE_BYTES // length. The last packet is always a packet that is < MAX_THREE_BYTES. // (The last packet may even have a length of 0) // // // NB: Guarded by execSQL. If the driver changes architecture, this // will need to be synchronized in some other way // Buffer headerPacket = (splitBufRef == null) ? null : (Buffer) (splitBufRef .get()); // // Store this packet in a soft reference...It can be re-used if not GC'd (so clients // that use it frequently won't have to re-alloc the 16M buffer), but we don't // penalize infrequent users of large packets by keeping 16M allocated all of the time // if (headerPacket == null) { headerPacket = new Buffer((int) (maxThreeBytes + HEADER_LENGTH)); splitBufRef = new SoftReference(headerPacket); } int len = packet.getPosition(); int splitSize = (int) maxThreeBytes; int originalPacketPos = HEADER_LENGTH; byte[] origPacketBytes = packet.getByteBuffer(); byte[] headerPacketBytes = headerPacket.getByteBuffer(); if (Driver.DEBUG) { System.out.println("\n\nSending split packets for packet of " + len + " bytes:\n"); } while (len >= maxThreeBytes) { headerPacket.setPosition(0); headerPacket.writeLongInt(splitSize); this.packetSequence++; headerPacket.writeByte(this.packetSequence); System.arraycopy(origPacketBytes, originalPacketPos, headerPacketBytes, 4, splitSize); this.mysqlOutput.write(headerPacketBytes, 0, splitSize + HEADER_LENGTH); this.mysqlOutput.flush(); if (Driver.DEBUG) { System.out.print(" total packet length (header & data) " + (splitSize + HEADER_LENGTH) + "\nheader: "); headerPacket.dumpHeader(); System.out.println(); System.out.print("last eight bytes: "); headerPacket.dumpNBytes(((splitSize + HEADER_LENGTH) - 8), 8); System.out.println(); } originalPacketPos += splitSize; len -= splitSize; } // // Write last packet // headerPacket.clear(); headerPacket.setPosition(0); headerPacket.writeLongInt(len - HEADER_LENGTH); this.packetSequence++; headerPacket.writeByte(this.packetSequence); if (len != 0) { System.arraycopy(origPacketBytes, originalPacketPos, headerPacketBytes, 4, len - HEADER_LENGTH); } this.mysqlOutput.write(headerPacket.getByteBuffer(), 0, len); this.mysqlOutput.flush(); if (Driver.DEBUG) { System.out.print(" total packet length (header & data) " + len + ",\nheader: "); headerPacket.dumpHeader(); System.out.println(); System.out.print("last packet bytes: "); headerPacket.dumpNBytes(0, len); System.out.println(); } } catch (IOException ioEx) { StringBuffer message = new StringBuffer(SQLError.get(SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE)); message.append(": "); message.append(ioEx.getClass().getName()); message.append(", underlying cause: "); message.append(ioEx.getMessage()); if (!this.connection.useParanoidErrorMessages()) { message.append(Util.stackTraceToString(ioEx)); } throw new java.sql.SQLException(message.toString(), SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0); } } private int alignPacketSize(int a, int l) { return ((((a) + (l)) - 1) & ~((l) - 1)); } private void checkForOutstandingStreamingData() throws SQLException { if (this.streamingData != null) { if (!this.connection.getClobberStreamingResults()) { throw new SQLException("Streaming result set " + this.streamingData + " is still active." + " Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on " + " any active result sets before attempting more queries."); } else { // Close the result set this.streamingData.getOwner().realClose(false); // clear any pending data.... clearInputStream(); } } } private void clearInputStream() throws SQLException { try { int len = this.mysqlInput.available(); while (len > 0) { this.mysqlInput.skip(len); len = this.mysqlInput.available(); } } catch (IOException ioEx) { throw new SQLException("I/O error while clearing input stream of old results", SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE); } } private Buffer compressPacket(Buffer packet, int offset, int packetLen, int headerLength) throws SQLException { packet.writeLongInt(packetLen - headerLength); packet.writeByte((byte) 0); // wrapped packet has 0 packet seq. int lengthToWrite = 0; int compressedLength = 0; byte[] bytesToCompress = packet.getByteBuffer(); byte[] compressedBytes = null; int offsetWrite = 0; if (true /*packetLen < MIN_COMPRESS_LEN*/) { lengthToWrite = packetLen; compressedBytes = packet.getByteBuffer(); compressedLength = 0; offsetWrite = offset; } else { compressedBytes = new byte[bytesToCompress.length * 2]; this.deflater.reset(); this.deflater.setInput(bytesToCompress, offset, packetLen); this.deflater.finish(); int compLen = this.deflater.deflate(compressedBytes); if (compLen > packetLen) { lengthToWrite = packetLen; compressedBytes = packet.getByteBuffer(); compressedLength = 0; offsetWrite = offset; } else { lengthToWrite = compLen; headerLength += COMP_HEADER_LENGTH; compressedLength = packetLen; } } Buffer compressedPacket = new Buffer(packetLen + headerLength); compressedPacket.setPosition(0); compressedPacket.writeLongInt(lengthToWrite); compressedPacket.writeByte(this.packetSequence); compressedPacket.writeLongInt(compressedLength); compressedPacket.writeBytesNoNull(compressedBytes, offsetWrite, lengthToWrite); return compressedPacket; } private SocketFactory createSocketFactory() throws SQLException { try { if (socketFactoryClassName == null) { throw new SQLException("No name specified for socket factory", SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE); } return (SocketFactory) (Class.forName(socketFactoryClassName) .newInstance()); } catch (Exception ex) { throw new SQLException("Could not create socket factory '" + socketFactoryClassName + "' due to underlying exception: " + ex.toString(), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE); } } /** * Ensures that we don't hold on to overly-large send packets */ private void reclaimLargeSharedSendPacket() { if ((this.sharedSendPacket != null) && (this.sharedSendPacket.getBufLength() > 1048576)) { this.sharedSendPacket = new Buffer(this.connection .getNetBufferLength()); } } /** * Secure authentication for 4.1 and newer servers. * * @param packLength * @param serverCapabilities * @param clientParam * @param user * @param password * @param database DOCUMENT ME! * * @throws SQLException */ private void secureAuth(int packLength, int serverCapabilities, long clientParam, String user, String password, String database) throws SQLException { // Passwords can be 16 chars long Buffer packet = new Buffer(packLength); if (this.use41Extensions) { if (versionMeetsMinimum(4, 1, 1)) { packet.writeLong(clientParam); packet.writeLong(this.maxThreeBytes); // charset, JDBC will connect as 'latin1', // and use 'SET NAMES' to change to the desired // charset after the connection is established. packet.writeByte((byte) 8); // Set of bytes reserved for future use. packet.writeBytesNoNull(new byte[23]); } else { packet.writeLong(clientParam); packet.writeLong(this.maxThreeBytes); } } else { packet.writeInt((int) clientParam); packet.writeLongInt(this.maxThreeBytes); } // User/Password data packet.writeString(user); if (password.length() != 0) { /* Prepare false scramble */ packet.writeString(FALSE_SCRAMBLE); } else { /* For empty password*/ packet.writeString(""); } if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0) && (database != null) && (database.length() > 0)) { packet.writeString(database); } send(packet); // // Don't continue stages if password is empty // if (password.length() > 0) { Buffer b = readPacket(); b.setPosition(0); byte[] replyAsBytes = b.getByteBuffer(); if ((replyAsBytes.length == 25) && (replyAsBytes[0] != 0)) { // Old passwords will have '*' at the first byte of hash */ if (replyAsBytes[0] != '*') { try { /* Build full password hash as it is required to decode scramble */ byte[] buff = Security.passwordHashStage1(password); /* Store copy as we'll need it later */ byte[] passwordHash = new byte[buff.length]; System.arraycopy(buff, 0, passwordHash, 0, buff.length); /* Finally hash complete password using hash we got from server */ passwordHash = Security.passwordHashStage2(passwordHash, replyAsBytes); byte[] packetDataAfterSalt = new byte[replyAsBytes.length - 5]; System.arraycopy(replyAsBytes, 4, packetDataAfterSalt, 0, replyAsBytes.length - 5); byte[] mysqlScrambleBuff = new byte[20]; /* Decypt and store scramble 4 = hash for stage2 */ Security.passwordCrypt(packetDataAfterSalt, mysqlScrambleBuff, passwordHash, 20); /* Encode scramble with password. Recycle buffer */ Security.passwordCrypt(mysqlScrambleBuff, buff, buff, 20); Buffer packet2 = new Buffer(25); packet2.writeBytesNoNull(buff); this.packetSequence++; send(packet2, 24); } catch (NoSuchAlgorithmException nse) { throw new SQLException( "Failed to create message digest 'SHA-1' for authentication. " + " You must use a JDK that supports JCE to be able to use secure connection authentication", SQLError.SQL_STATE_GENERAL_ERROR); } } else { try { /* Create password to decode scramble */ byte[] passwordHash = Security.createKeyFromOldPassword(password); /* Decypt and store scramble 4 = hash for stage2 */ byte[] netReadPos4 = new byte[replyAsBytes.length - 5]; System.arraycopy(replyAsBytes, 4, netReadPos4, 0, replyAsBytes.length - 5); byte[] mysqlScrambleBuff = new byte[20]; /* Decypt and store scramble 4 = hash for stage2 */ Security.passwordCrypt(netReadPos4, mysqlScrambleBuff, passwordHash, 20); /* Finally scramble decoded scramble with password */ String scrambledPassword = Util.scramble(new String( mysqlScrambleBuff), password); Buffer packet2 = new Buffer(packLength); packet2.writeString(scrambledPassword); this.packetSequence++; send(packet2, 24); } catch (NoSuchAlgorithmException nse) { throw new SQLException( "Failed to create message digest 'SHA-1' for authentication. " + " You must use a JDK that supports JCE to be able to use secure connection authentication", SQLError.SQL_STATE_GENERAL_ERROR); } } } } } /** * Secure authentication for 4.1.1 and newer servers. * * @param packLength * @param serverCapabilities DOCUMENT ME! * @param clientParam * @param user * @param password * @param database DOCUMENT ME! * * @throws SQLException */ private void secureAuth411(int packLength, int serverCapabilities, long clientParam, String user, String password, String database) throws SQLException { // SERVER: public_seed=create_random_string() // send(public_seed) // // CLIENT: recv(public_seed) // hash_stage1=sha1("password") // hash_stage2=sha1(hash_stage1) // reply=xor(hash_stage1, sha1(public_seed,hash_stage2) // // // this three steps are done in scramble() // // send(reply) // // // SERVER: recv(reply) // hash_stage1=xor(reply, sha1(public_seed,hash_stage2)) // candidate_hash2=sha1(hash_stage1) // check(candidate_hash2==hash_stage2) // Passwords can be 16 chars long Buffer packet = new Buffer(packLength); if (this.use41Extensions) { if (versionMeetsMinimum(4, 1, 1)) { packet.writeLong(this.clientParam); packet.writeLong(this.maxThreeBytes); // charset, JDBC will connect as 'latin1', // and use 'SET NAMES' to change to the desired // charset after the connection is established. packet.writeByte((byte) 8); // Set of bytes reserved for future use. packet.writeBytesNoNull(new byte[23]); } else { packet.writeLong(this.clientParam); packet.writeLong(this.maxThreeBytes); } } else { packet.writeInt((int) this.clientParam); packet.writeLongInt(this.maxThreeBytes); } // User/Password data packet.writeString(user); if (password.length() != 0) { packet.writeByte((byte) 0x14); try { packet.writeBytesNoNull(Security.scramble411(password, this.seed)); } catch (NoSuchAlgorithmException nse) { throw new SQLException( "Failed to create message digest 'SHA-1' for authentication. " + " You must use a JDK that supports JCE to be able to use secure connection authentication", SQLError.SQL_STATE_GENERAL_ERROR); } } else { /* For empty password*/ packet.writeByte((byte) 0); } if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0) && (database != null) && (database.length() > 0)) { packet.writeString(database); } send(packet); byte savePacketSequence = this.packetSequence++; Buffer reply = checkErrorPacket(); if (reply.isLastDataPacket()) { /* By sending this very specific reply server asks us to send scrambled password in old format. The reply contains scramble_323. */ this.packetSequence = ++savePacketSequence; packet.clear(); String seed323 = this.seed.substring(0, 8); packet.writeString(Util.newCrypt(password, seed323)); send(packet); /* Read what server thinks about out new auth message report */ checkErrorPacket(); } } }