Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/mysql/jdbc/MysqlIO.java


1   /*
2    Copyright (C) 2002-2004 MySQL AB
3   
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of version 2 of the GNU General Public License as
6    published by the Free Software Foundation.
7    
8   
9    There are special exceptions to the terms and conditions of the GPL 
10   as it is applied to this software. View the full text of the 
11   exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this 
12   software distribution.
13  
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18  
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  
23   */
24  package com.mysql.jdbc;
25  
26  import java.io.BufferedInputStream;
27  import java.io.BufferedOutputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.EOFException;
30  import java.io.FileInputStream;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.io.OutputStreamWriter;
34  import java.lang.ref.SoftReference;
35  import java.net.Socket;
36  import java.security.NoSuchAlgorithmException;
37  import java.sql.SQLException;
38  import java.sql.SQLWarning;
39  import java.util.ArrayList;
40  import java.util.Properties;
41  import java.util.zip.Deflater;
42  import java.util.zip.Inflater;
43  
44  
45  /**
46   * This class is used by Connection for communicating with the MySQL server.
47   *
48   * @author Mark Matthews
49   * @version $Id: MysqlIO.java,v 1.32.2.61 2004/08/27 21:50:12 mmatthew Exp $
50   *
51   * @see java.sql.Connection
52   */
53  public class MysqlIO {
54      static final int NULL_LENGTH = ~0;
55      static final int COMP_HEADER_LENGTH = 3;
56      static final int MIN_COMPRESS_LEN = 50;
57      static final int HEADER_LENGTH = 4;
58      private static int maxBufferSize = 65535;
59      private static final int CLIENT_COMPRESS = 32; /* Can use compression
60      protcol */
61      private static final int CLIENT_CONNECT_WITH_DB = 8;
62      private static final int CLIENT_FOUND_ROWS = 2;
63      private static final int CLIENT_IGNORE_SPACE = 256; /* Ignore spaces
64      before '(' */
65      private static final int CLIENT_LOCAL_FILES = 128; /* Can use LOAD DATA
66      LOCAL */
67  
68      /* Found instead of
69         affected rows */
70      private static final int CLIENT_LONG_FLAG = 4; /* Get all column flags */
71      private static final int CLIENT_LONG_PASSWORD = 1; /* new more secure
72      passwords */
73      private static final int CLIENT_PROTOCOL_41 = 512; // for > 4.1.1
74      private static final int CLIENT_INTERACTIVE = 1024;
75      private static final int CLIENT_SSL = 2048;
76      private static final int CLIENT_RESERVED = 16384; // for 4.1.0 only
77      private static final int CLIENT_SECURE_CONNECTION = 32768;
78      private static final String FALSE_SCRAMBLE = "xxxxxxxx";
79  
80      /**
81       * We store the platform 'encoding' here, only used to avoid munging
82       * filenames for LOAD DATA LOCAL INFILE...
83       */
84      private static String jvmPlatformCharset = null;
85  
86      static {
87          OutputStreamWriter outWriter = null;
88  
89          //
90          // Use the I/O system to get the encoding (if possible), to avoid
91          // security restrictions on System.getProperty("file.encoding") in
92          // applets (why is that restricted?)
93          //
94          try {
95              outWriter = new OutputStreamWriter(new ByteArrayOutputStream());
96              jvmPlatformCharset = outWriter.getEncoding();
97          } finally {
98              try {
99                if (outWriter != null) {
100                 outWriter.close();
101               }
102             } catch (IOException ioEx) {
103                 // ignore
104             }
105         }
106     }
107 
108     //
109     // Use this when reading in rows to avoid thousands of new()
110     // calls, because the byte arrays just get copied out of the
111     // packet anyway
112     //
113     private Buffer reusablePacket = null;
114     private Buffer sendPacket = null;
115     private Buffer sharedSendPacket = null;
116 
117     /** Data to the server */
118 
119     //private DataOutputStream     _Mysql_Output             = null;
120     private BufferedOutputStream mysqlOutput = null;
121     private com.mysql.jdbc.Connection connection;
122     private Deflater deflater = null;
123     private Inflater inflater = null;
124 
125     /** Buffered data from the server */
126 
127     //private BufferedInputStream  _Mysql_Buf_Input          = null;
128 
129     /** Buffered data to the server */
130 
131     //private BufferedOutputStream _Mysql_Buf_Output         = null;
132 
133     /** Data from the server */
134 
135     //private DataInputStream      _Mysql_Input              = null;
136     private InputStream mysqlInput = null;
137     private RowData streamingData = null;
138 
139     //
140     // For SQL Warnings
141     //
142     private SQLWarning warningChain = null;
143 
144     /** The connection to the server */
145     private Socket mysqlConnection = null;
146     private SocketFactory socketFactory = null;
147 
148     //
149     // Packet used for 'LOAD DATA LOCAL INFILE'
150     //
151     // We use a SoftReference, so that we don't penalize intermittent
152     // use of this feature
153     //
154     private SoftReference loadFileBufRef;
155 
156     //
157     // Used to send large packets to the server versions 4+
158     // We use a SoftReference, so that we don't penalize intermittent
159     // use of this feature
160     //
161     private SoftReference splitBufRef;
162     private String host = null;
163     private String seed;
164     private String serverVersion = null;
165     private String socketFactoryClassName = null;
166     private byte[] packetHeaderBuf = new byte[4];
167     private boolean clearStreamBeforeEachQuery = false;
168     private boolean colDecimalNeedsBump = false; // do we need to increment the colDecimal flag?
169     private boolean has41NewNewProt = false;
170 
171     /** Does the server support long column info? */
172     private boolean hasLongColumnInfo = false;
173     private boolean isInteractiveClient = false;
174 
175     /**
176      * Does the character set of this connection match the character set of the
177      * platform
178      */
179     private boolean platformDbCharsetMatches = true;
180     private boolean profileSql = false;
181 
182     /** Should we use 4.1 protocol extensions? */
183     private boolean use41Extensions = false;
184     private boolean useCompression = false;
185     private boolean useNewLargePackets = false;
186     private boolean useNewUpdateCounts = false; // should we use the new larger update counts?
187     private byte packetSequence = 0;
188     private byte protocolVersion = 0;
189     private int clientParam = 0;
190 
191     // changed once we've connected.
192     private int maxAllowedPacket = 1024 * 1024;
193     private int maxThreeBytes = 255 * 255 * 255;
194     private int port = 3306;
195     private int serverCapabilities;
196     private int serverMajorVersion = 0;
197     private int serverMinorVersion = 0;
198     private int serverSubMinorVersion = 0;
199   protected int serverCharsetIndex;
200   private static final int MAX_QUERY_LENGTH_TO_LOG =  4 * 1024; // 4K
201 
202     /**
203      * Constructor:  Connect to the MySQL server and setup a stream connection.
204      *
205      * @param host the hostname to connect to
206      * @param port the port number that the server is listening on
207      * @param socketFactoryClassName the socket factory to use
208      * @param props the Properties from DriverManager.getConnection()
209      * @param conn the Connection that is creating us
210      * @param socketTimeout the timeout to set for the socket (0 means no
211      *        timeout)
212      *
213      * @throws IOException if an IOException occurs during connect.
214      * @throws java.sql.SQLException if a database access error occurs.
215      */
216     protected MysqlIO(String host, int port, String socketFactoryClassName,
217         Properties props, com.mysql.jdbc.Connection conn, int socketTimeout)
218         throws IOException, java.sql.SQLException {
219         this.connection = conn;
220         this.reusablePacket = new Buffer(this.connection.getNetBufferLength());
221         this.port = port;
222         this.host = host;
223         this.socketFactoryClassName = socketFactoryClassName;
224         this.socketFactory = createSocketFactory();
225         this.mysqlConnection = socketFactory.connect(this.host, this.port, props);
226         this.clearStreamBeforeEachQuery = this.connection.alwaysClearStream();
227 
228         if (socketTimeout != 0) {
229             try {
230                 this.mysqlConnection.setSoTimeout(socketTimeout);
231             } catch (Exception ex) {
232                 /* Ignore if the platform does not support it */
233             }
234         }
235 
236         this.mysqlConnection = this.socketFactory.beforeHandshake();
237 
238         if (!this.connection.isUsingUnbufferedInput()) {
239             this.mysqlInput = new BufferedInputStream(this.mysqlConnection
240                     .getInputStream(), 16384);
241         } else {
242             this.mysqlInput = this.mysqlConnection.getInputStream();
243         }
244 
245         this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection
246                 .getOutputStream(), 16384);
247         this.isInteractiveClient = this.connection.isInteractiveClient();
248     }
249 
250     /**
251      * Should the driver generate SQL statement profiles?
252      *
253      * @param flag should the driver enable profiling?
254      */
255     protected void setProfileSql(boolean flag) {
256         this.profileSql = flag;
257     }
258 
259     /**
260      * Build a result set. Delegates to buildResultSetWithRows() to build a
261      * JDBC-version-specific ResultSet, given rows as byte data, and field
262      * information.
263      *
264      * @param columnCount the number of columns in the result set
265      * @param maxRows the maximum number of rows to read (-1 means all rows)
266      * @param resultSetType the type of result set (CONCUR_UPDATABLE or
267      *        READ_ONLY)
268      * @param streamResults should the result set be read all at once, or
269      *        streamed?
270      * @param catalog the database name in use when the result set was created
271      *
272      * @return a result set
273      *
274      * @throws Exception if a database access error occurs
275      */
276     protected ResultSet getResultSet(long columnCount, int maxRows,
277         int resultSetType, boolean streamResults, String catalog)
278         throws Exception {
279         Buffer packet; // The packet from the server
280         Field[] fields = new Field[(int) columnCount];
281 
282         // Read in the column information
283         for (int i = 0; i < columnCount; i++) {
284             packet = readPacket();
285             fields[i] = unpackField(packet, false);
286         }
287 
288         packet = reuseAndReadPacket(this.reusablePacket);
289 
290         RowData rowData = null;
291 
292         if (!streamResults) {
293             ArrayList rows = new ArrayList();
294 
295             // Now read the data
296             byte[][] rowBytes = nextRow((int) columnCount);
297             int rowCount = 0;
298 
299             if (rowBytes != null) {
300                 rows.add(rowBytes);
301                 rowCount = 1;
302             }
303 
304             while ((rowBytes != null) && (rowCount < maxRows)) {
305                 rowBytes = nextRow((int) columnCount);
306 
307                 if (rowBytes != null) {
308                     rows.add(rowBytes);
309                     rowCount++;
310                 } else {
311                     if (Driver.TRACE) {
312                         Debug.msg(this, "* NULL Row *");
313                     }
314                 }
315             }
316 
317             //
318             // Clear any outstanding data left on the wire
319             // when we've artifically limited the number of 
320             // rows we retrieve (fix for BUG#1695)
321             //
322             if (rowCount <= maxRows) {
323                 clearInputStream();
324             }
325 
326             if (Driver.TRACE) {
327                 Debug.msg(this,
328                     "* Fetched " + rows.size() + " rows from server *");
329             }
330 
331             rowData = new RowDataStatic(rows);
332             reclaimLargeReusablePacket();
333         } else {
334             rowData = new RowDataDynamic(this, (int) columnCount);
335             this.streamingData = rowData;
336         }
337 
338         return buildResultSetWithRows(catalog, fields, rowData, resultSetType);
339     }
340 
341     /**
342      * Forcibly closes the underlying socket to MySQL.
343      */
344     protected final void forceClose() {
345         try {
346             if (this.mysqlInput != null) {
347                 this.mysqlInput.close();
348             }
349         } catch (IOException ioEx) {
350             // we can't do anything constructive about this
351             // Let the JVM clean it up later
352             this.mysqlInput = null;
353         }
354 
355         try {
356             if (this.mysqlOutput != null) {
357                 this.mysqlOutput.close();
358             }
359         } catch (IOException ioEx) {
360             // we can't do anything constructive about this
361             // Let the JVM clean it up later
362             this.mysqlOutput = null;
363         }
364 
365         try {
366             if (this.mysqlConnection != null) {
367                 this.mysqlConnection.close();
368             }
369         } catch (IOException ioEx) {
370             // we can't do anything constructive about this
371             // Let the JVM clean it up later
372             this.mysqlConnection = null;
373         }
374     }
375 
376     /**
377      * Re-authenticates as the given user and password
378      *
379      * @param userName DOCUMENT ME!
380      * @param password DOCUMENT ME!
381      *
382      * @throws SQLException DOCUMENT ME!
383      */
384     protected void changeUser(String userName, String password, String database)
385         throws SQLException {
386         this.packetSequence = -1;
387 
388         int passwordLength = 16;
389         int userLength = 0;
390 
391         if (userName != null) {
392             userLength = userName.length();
393         }
394 
395         int packLength = (userLength + passwordLength) + 7 + HEADER_LENGTH;
396 
397         if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
398             Buffer changeUserPacket = new Buffer(packLength + 1);
399             changeUserPacket.writeByte((byte) MysqlDefs.COM_CHANGE_USER);
400 
401             if (versionMeetsMinimum(4, 1, 1)) {
402                 secureAuth411(changeUserPacket, packLength, userName, password,
403                     database, false);
404             } else {
405                 secureAuth(changeUserPacket, packLength, userName, password,
406                     database, false);
407             }
408         } else {
409             // Passwords can be 16 chars long
410             Buffer packet = new Buffer(packLength + 1);
411             packet.writeByte((byte) MysqlDefs.COM_CHANGE_USER);
412 
413             // User/Password data
414             packet.writeString(userName);
415 
416             if (this.protocolVersion > 9) {
417                 packet.writeString(Util.newCrypt(password, this.seed));
418             } else {
419                 packet.writeString(Util.oldCrypt(password, this.seed));
420             }
421 
422             if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
423                     && (database != null) && (database.length() > 0)) {
424                 packet.writeString(database);
425             }
426             
427             send(packet);
428             checkErrorPacket();
429         }
430     }
431 
432     /**
433      * Does the server send back extra column info?
434      *
435      * @return true if so
436      */
437     protected boolean hasLongColumnInfo() {
438         return this.hasLongColumnInfo;
439     }
440 
441     /**
442      * Unpacks the Field information from the given packet. Understands pre 4.1
443      * and post 4.1 server version field packet structures.
444      *
445      * @param packet the packet containing the field information
446      * @param extractDefaultValues should default values be extracted?
447      *
448      * @return the unpacked field
449      */
450     protected final Field unpackField(Buffer packet,
451         boolean extractDefaultValues) throws SQLException {
452         if (this.use41Extensions) {
453             // we only store the position of the string and
454             // materialize only if needed...
455             if (this.has41NewNewProt) {
456                 int catalogNameStart = packet.getPosition() + 1;
457                 int catalogNameLength = packet.fastSkipLenString();
458             }
459 
460             int databaseNameStart = packet.getPosition() + 1;
461             int databaseNameLength = packet.fastSkipLenString();
462 
463             int tableNameStart = packet.getPosition() + 1;
464             int tableNameLength = packet.fastSkipLenString();
465 
466             // orgTableName is never used so skip
467             int originalTableNameStart = packet.getPosition() + 1;
468             int originalTableNameLength = packet.fastSkipLenString();
469 
470             // we only store the position again...
471             int nameStart = packet.getPosition() + 1;
472             int nameLength = packet.fastSkipLenString();
473 
474             // orgColName is not required so skip...
475             int originalColumnNameStart = packet.getPosition() + 1;
476             int originalColumnNameLength = packet.fastSkipLenString();
477 
478             packet.readByte();
479 
480             int charSetNumber = packet.readInt();
481 
482             long colLength = 0;
483 
484             if (this.has41NewNewProt) {
485                 colLength = packet.readLong();
486             } else {
487                 colLength = packet.readLongInt();
488             }
489 
490             int colType = packet.readByte() & 0xff;
491 
492             short colFlag = 0;
493 
494             if (this.hasLongColumnInfo) {
495                 colFlag = (short) (packet.readInt());
496             } else {
497                 colFlag = (short) (packet.readByte() & 0xff);
498             }
499 
500             int colDecimals = packet.readByte() & 0xff;
501 
502             int defaultValueStart = -1;
503             int defaultValueLength = -1;
504 
505             if (extractDefaultValues) {
506                 defaultValueStart = packet.getPosition() + 1;
507                 defaultValueLength = packet.fastSkipLenString();
508             }
509 
510             Field field = new Field(this.connection, packet.getByteBuffer(),
511                     databaseNameStart, databaseNameLength, tableNameStart,
512                     tableNameLength, originalTableNameStart,
513                     originalTableNameLength, nameStart, nameLength,
514                     originalColumnNameStart, originalColumnNameLength,
515                     colLength, colType, colFlag, colDecimals,
516                     defaultValueStart, defaultValueLength, charSetNumber);
517 
518             return field;
519         } else {
520             int tableNameStart = packet.getPosition() + 1;
521             int tableNameLength = packet.fastSkipLenString();
522             int nameStart = packet.getPosition() + 1;
523             int nameLength = packet.fastSkipLenString();
524             int colLength = packet.readnBytes();
525             int colType = packet.readnBytes();
526             packet.readByte(); // We know it's currently 2
527 
528             short colFlag = 0;
529 
530             if (this.hasLongColumnInfo) {
531                 colFlag = (short) (packet.readInt());
532             } else {
533                 colFlag = (short) (packet.readByte() & 0xff);
534             }
535 
536             int colDecimals = (packet.readByte() & 0xff);
537 
538             if (this.colDecimalNeedsBump) {
539                 colDecimals++;
540             }
541 
542             Field field = new Field(this.connection, packet.getBufferSource(),
543                     nameStart, nameLength, tableNameStart, tableNameLength,
544                     colLength, colType, colFlag, colDecimals);
545 
546             return field;
547         }
548     }
549 
550     /**
551      * Determines if the database charset is the same as the platform charset
552      */
553     protected void checkForCharsetMismatch() {
554         if (this.connection.useUnicode()
555                 && (this.connection.getEncoding() != null)) {
556             String encodingToCheck = jvmPlatformCharset;
557 
558             if (encodingToCheck == null) {
559                 encodingToCheck = System.getProperty("file.encoding");
560             }
561 
562             if (encodingToCheck == null) {
563                 this.platformDbCharsetMatches = false;
564             } else {
565                 this.platformDbCharsetMatches = encodingToCheck.equals(this.connection
566                         .getEncoding());
567             }
568         }
569     }
570 
571     static int getMaxBuf() {
572         return maxBufferSize;
573     }
574 
575     /**
576      * Get the major version of the MySQL server we are talking to.
577      *
578      * @return DOCUMENT ME!
579      */
580     final int getServerMajorVersion() {
581         return this.serverMajorVersion;
582     }
583 
584     /**
585      * Get the minor version of the MySQL server we are talking to.
586      *
587      * @return DOCUMENT ME!
588      */
589     final int getServerMinorVersion() {
590         return this.serverMinorVersion;
591     }
592 
593     /**
594      * Get the sub-minor version of the MySQL server we are talking to.
595      *
596      * @return DOCUMENT ME!
597      */
598     final int getServerSubMinorVersion() {
599         return this.serverSubMinorVersion;
600     }
601 
602     /**
603      * Get the version string of the server we are talking to
604      *
605      * @return DOCUMENT ME!
606      */
607     String getServerVersion() {
608         return this.serverVersion;
609     }
610 
611     /**
612      * Initialize communications with the MySQL server. Handles logging on, and
613      * handling initial connection errors.
614      *
615      * @param user DOCUMENT ME!
616      * @param password DOCUMENT ME!
617      * @param database DOCUMENT ME!
618      *
619      * @throws java.sql.SQLException DOCUMENT ME!
620      * @throws SQLException DOCUMENT ME!
621      */
622     void doHandshake(String user, String password, String database)
623         throws java.sql.SQLException {
624         // Read the first packet
625         Buffer buf = readPacket();
626 
627         // Get the protocol version
628         this.protocolVersion = buf.readByte();
629 
630         if (this.protocolVersion == -1) {
631             try {
632                 this.mysqlConnection.close();
633             } catch (Exception e) {
634                 ; // ignore
635             }
636 
637             int errno = 2000;
638 
639             errno = buf.readInt();
640 
641             String serverErrorMessage = buf.readString();
642 
643             StringBuffer errorBuf = new StringBuffer(" message from server: \"");
644             errorBuf.append(serverErrorMessage);
645             errorBuf.append("\"");
646 
647             String xOpen = SQLError.mysqlToXOpen(errno);
648 
649             throw new SQLException(SQLError.get(xOpen) + ", "
650                 + errorBuf.toString(), xOpen, errno);
651         }
652 
653         this.serverVersion = buf.readString();
654 
655         // Parse the server version into major/minor/subminor
656         int point = this.serverVersion.indexOf(".");
657 
658         if (point != -1) {
659             try {
660                 int n = Integer.parseInt(this.serverVersion.substring(0, point));
661                 this.serverMajorVersion = n;
662             } catch (NumberFormatException NFE1) {
663                 ;
664             }
665 
666             String remaining = this.serverVersion.substring(point + 1,
667                     this.serverVersion.length());
668             point = remaining.indexOf(".");
669 
670             if (point != -1) {
671                 try {
672                     int n = Integer.parseInt(remaining.substring(0, point));
673                     this.serverMinorVersion = n;
674                 } catch (NumberFormatException nfe) {
675                     ;
676                 }
677 
678                 remaining = remaining.substring(point + 1, remaining.length());
679 
680                 int pos = 0;
681 
682                 while (pos < remaining.length()) {
683                     if ((remaining.charAt(pos) < '0')
684                             || (remaining.charAt(pos) > '9')) {
685                         break;
686                     }
687 
688                     pos++;
689                 }
690 
691                 try {
692                     int n = Integer.parseInt(remaining.substring(0, pos));
693                     this.serverSubMinorVersion = n;
694                 } catch (NumberFormatException nfe) {
695                     ;
696                 }
697             }
698         }
699 
700         if (versionMeetsMinimum(4, 0, 8)) {
701             this.maxThreeBytes = (256 * 256 * 256) - 1;
702             this.useNewLargePackets = true;
703         } else {
704             this.maxThreeBytes = 255 * 255 * 255;
705             this.useNewLargePackets = false;
706         }
707 
708         this.colDecimalNeedsBump = versionMeetsMinimum(3, 23, 0);
709         this.colDecimalNeedsBump = !versionMeetsMinimum(3, 23, 15); // guess? Not noted in changelog
710         this.useNewUpdateCounts = versionMeetsMinimum(3, 22, 5);
711 
712         long threadId = buf.readLong();
713         seed = buf.readString();
714 
715         if (Driver.TRACE) {
716             Debug.msg(this, "Protocol Version: " + (int) this.protocolVersion);
717             Debug.msg(this, "Server Version: " + this.serverVersion);
718             Debug.msg(this, "Thread ID: " + threadId);
719             Debug.msg(this, "Crypt Seed: " + seed);
720         }
721 
722         this.serverCapabilities = 0;
723 
724         if (buf.getPosition() < buf.getBufLength()) {
725             serverCapabilities = buf.readInt();
726         }
727 
728         if (versionMeetsMinimum(4, 1, 1)) {
729             int position = buf.getPosition();
730             
731             this.serverCharsetIndex = buf.readByte() & 0xff;
732       //not used this.serverStatus = buf.readInt();
733       
734             buf.setPosition(position + 16);
735 
736             String seedPart2 = buf.readString();
737             StringBuffer newSeed = new StringBuffer(20);
738             newSeed.append(seed);
739             newSeed.append(seedPart2);
740             this.seed = newSeed.toString();
741         }
742 
743         if (((serverCapabilities & CLIENT_COMPRESS) != 0)
744                 && this.connection.useCompression()) {
745             clientParam |= CLIENT_COMPRESS;
746         }
747 
748         if ((database != null) && (database.length() > 0)) {
749             clientParam |= CLIENT_CONNECT_WITH_DB;
750         }
751 
752         if (((serverCapabilities & CLIENT_SSL) == 0)
753                 && this.connection.useSSL()) {
754             this.connection.setUseSSL(false);
755         }
756 
757         if ((serverCapabilities & CLIENT_LONG_FLAG) != 0) {
758             // We understand other column flags, as well
759             clientParam |= CLIENT_LONG_FLAG;
760             this.hasLongColumnInfo = true;
761         }
762 
763         // return FOUND rows
764         clientParam |= CLIENT_FOUND_ROWS;
765 
766         if (this.connection.allowLoadLocalInfile()) {
767             clientParam |= CLIENT_LOCAL_FILES;
768         }
769 
770         if (isInteractiveClient) {
771             clientParam |= CLIENT_INTERACTIVE;
772         }
773 
774         // Authenticate
775         if (this.protocolVersion > 9) {
776             clientParam |= CLIENT_LONG_PASSWORD; // for long passwords
777         } else {
778             clientParam &= ~CLIENT_LONG_PASSWORD;
779         }
780 
781         //
782         // 4.1 has some differences in the protocol
783         //
784         if (versionMeetsMinimum(4, 1, 0)) {
785             if (versionMeetsMinimum(4, 1, 1)) {
786                 clientParam |= CLIENT_PROTOCOL_41;
787                 this.has41NewNewProt = true;
788             } else {
789                 clientParam |= CLIENT_RESERVED;
790                 this.has41NewNewProt = false;
791             }
792 
793             this.use41Extensions = true;
794         }
795 
796         int passwordLength = 16;
797         int userLength = 0;
798         int databaseLength = 0;
799 
800         if (user != null) {
801             userLength = user.length();
802         }
803 
804         if (database != null) {
805             databaseLength = database.length();
806         }
807 
808         int packLength = (userLength + passwordLength + databaseLength) + 7
809             + HEADER_LENGTH;
810         Buffer packet = null;
811 
812         if (!connection.useSSL()) {
813             if ((serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
814                 clientParam |= CLIENT_SECURE_CONNECTION;
815 
816                 if (versionMeetsMinimum(4, 1, 1)) {
817                     secureAuth411(null, packLength, user, password, database,
818                         true);
819                 } else {
820                     secureAuth(null, packLength, user, password, database, true);
821                 }
822             } else {
823                 packet = new Buffer(packLength);
824 
825                 if ((clientParam & CLIENT_RESERVED) != 0) {
826                     if (versionMeetsMinimum(4, 1, 1)) {
827                         packet.writeLong(clientParam);
828                         packet.writeLong(this.maxThreeBytes);
829 
830                         // charset, JDBC will connect as 'latin1',
831                         // and use 'SET NAMES' to change to the desired
832                         // charset after the connection is established.
833                         packet.writeByte((byte) 8);
834 
835                         // Set of bytes reserved for future use.
836                         packet.writeBytesNoNull(new byte[23]);
837                     } else {
838                         packet.writeLong(clientParam);
839                         packet.writeLong(this.maxThreeBytes);
840                     }
841                 } else {
842                     packet.writeInt((int) clientParam);
843                     packet.writeLongInt(this.maxThreeBytes);
844                 }
845 
846                 // User/Password data
847                 packet.writeString(user);
848 
849                 if (this.protocolVersion > 9) {
850                     packet.writeString(Util.newCrypt(password, this.seed));
851                 } else {
852                     packet.writeString(Util.oldCrypt(password, this.seed));
853                 }
854 
855                 if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
856                         && (database != null) && (database.length() > 0)) {
857                     packet.writeString(database);
858                 }
859 
860                 send(packet);
861             }
862         } else {
863             boolean doSecureAuth = false;
864 
865             if ((serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) {
866                 clientParam |= CLIENT_SECURE_CONNECTION;
867                 doSecureAuth = true;
868             }
869 
870             clientParam |= CLIENT_SSL;
871             packet = new Buffer(packLength);
872 
873             if ((clientParam & CLIENT_RESERVED) != 0) {
874                 packet.writeLong(clientParam);
875             } else {
876                 packet.writeInt((int) clientParam);
877             }
878 
879             send(packet);
880 
881             javax.net.ssl.SSLSocketFactory sslFact = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory
882                 .getDefault();
883 
884             try {
885                 this.mysqlConnection = sslFact.createSocket(this.mysqlConnection,
886                         this.host, this.port, true);
887 
888                 // need to force TLSv1, or else JSSE tries to do a SSLv2 handshake
889                 // which MySQL doesn't understand
890                 ((javax.net.ssl.SSLSocket) this.mysqlConnection)
891                 .setEnabledProtocols(new String[] { "TLSv1" });
892                 ((javax.net.ssl.SSLSocket) this.mysqlConnection).startHandshake();
893                 this.mysqlInput = new BufferedInputStream(this.mysqlConnection
894                         .getInputStream(), 16384);
895                 this.mysqlOutput = new BufferedOutputStream(this.mysqlConnection
896                         .getOutputStream(), 16384);
897                 this.mysqlOutput.flush();
898             } catch (IOException ioEx) {
899                 StringBuffer message = new StringBuffer(SQLError.get(
900                             SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE));
901                 message.append(": ");
902                 message.append(ioEx.getClass().getName());
903                 message.append(", underlying cause: ");
904                 message.append(ioEx.getMessage());
905 
906                 if (!this.connection.useParanoidErrorMessages()) {
907                     message.append(Util.stackTraceToString(ioEx));
908                 }
909 
910                 throw new java.sql.SQLException(message.toString(),
911                     SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
912             }
913 
914             packet.clear();
915 
916             if (doSecureAuth) {
917                 if (versionMeetsMinimum(4, 1, 1)) {
918                     secureAuth411(null, packLength, user, password, database,
919                         true);
920                 } else {
921                     secureAuth(null, packLength, user, password, database, true);
922                 }
923             } else {
924                 if ((clientParam & CLIENT_RESERVED) != 0) {
925                     packet.writeLong(clientParam);
926                     packet.writeLong(this.maxThreeBytes);
927                 } else {
928                     packet.writeInt((int) clientParam);
929                     packet.writeLongInt(this.maxThreeBytes);
930                 }
931 
932                 // User/Password data
933                 packet.writeString(user);
934 
935                 if (this.protocolVersion > 9) {
936                     packet.writeString(Util.newCrypt(password, seed));
937                 } else {
938                     packet.writeString(Util.oldCrypt(password, seed));
939                 }
940 
941                 if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
942                         && (database != null) && (database.length() > 0)) {
943                     packet.writeString(database);
944                 }
945 
946                 send(packet);
947             }
948         }
949 
950         // Check for errors, not for 4.1.1 or newer,
951         // as the new auth protocol doesn't work that way
952         // (see secureAuth411() for more details...)
953         if (!versionMeetsMinimum(4, 1, 1)) {
954             checkErrorPacket();
955         }
956 
957         //
958         // Can't enable compression until after handshake
959         //
960         if (((serverCapabilities & CLIENT_COMPRESS) != 0)
961                 && this.connection.useCompression()) {
962             // The following matches with ZLIB's
963             // compress()
964             this.deflater = new Deflater();
965             this.useCompression = true;
966             this.mysqlInput = new CompressedInputStream(this.mysqlInput);
967         }
968 
969         if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) == 0)
970                 && (database != null) && (database.length() > 0)) {
971             try {
972                 sendCommand(MysqlDefs.INIT_DB, database, null);
973             } catch (Exception ex) {
974                 throw new SQLException(ex.toString()
975                     + (this.connection.useParanoidErrorMessages() ? ""
976                                                                   : Util
977                     .stackTraceToString(ex)),
978                     SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE);
979             }
980         }
981     }
982 
983     /**
984      * Retrieve one row from the MySQL server. Note: this method is not
985      * thread-safe, but it is only called from methods that are guarded by
986      * synchronizing on this object.
987      *
988      * @param columnCount DOCUMENT ME!
989      *
990      * @return DOCUMENT ME!
991      *
992      * @throws Exception DOCUMENT ME!
993      */
994     final byte[][] nextRow(int columnCount) throws Exception {
995         // Get the next incoming packet, re-using the packet because
996         // all the data we need gets copied out of it.
997         Buffer rowPacket = checkErrorPacket();
998 
999         //
1000        // Didn't read an error, so re-position to beginning
1001        // of packet in order to read result set data
1002        //
1003        int offset = 0;
1004
1005        //if (rowPacket.wasMultiPacket()) {
1006        //    if (this.useNewLargePackets) {
1007        //        offset = HEADER_LENGTH;
1008        //    } else {
1009        //        offset = HEADER_LENGTH + 1;
1010        //   }
1011        //}
1012        rowPacket.setPosition(rowPacket.getPosition() - 1);
1013
1014        byte[][] rowData = new byte[columnCount][];
1015
1016        if (!rowPacket.isLastDataPacket()) {
1017            for (int i = 0; i < columnCount; i++) {
1018                rowData[i] = rowPacket.readLenByteArray(offset);
1019
1020                if (Driver.TRACE) {
1021                    if (rowData[i] == null) {
1022                        Debug.msg(this, "Field value: NULL");
1023                    } else {
1024                        Debug.msg(this, "Field value: " + rowData[i].toString());
1025                    }
1026                }
1027            }
1028
1029            return rowData;
1030        }
1031
1032        return null;
1033    }
1034
1035    /**
1036     * Log-off of the MySQL server and close the socket.
1037     *
1038     * @throws SQLException DOCUMENT ME!
1039     */
1040    final void quit() throws SQLException {
1041        Buffer packet = new Buffer(6);
1042        this.packetSequence = -1;
1043        packet.writeByte((byte) MysqlDefs.QUIT);
1044        send(packet);
1045        forceClose();
1046    }
1047
1048    /**
1049     * Returns the packet used for sending data (used by PreparedStatement)
1050     * Guarded by external synchronization on a mutex.
1051     *
1052     * @return A packet to send data with
1053     */
1054    Buffer getSharedSendPacket() {
1055        if (this.sharedSendPacket == null) {
1056            this.sharedSendPacket = new Buffer(this.connection
1057                    .getNetBufferLength());
1058        }
1059
1060        return this.sharedSendPacket;
1061    }
1062
1063    void closeStreamer(RowData streamer) throws SQLException {
1064        if (this.streamingData == null) {
1065            throw new SQLException("Attempt to close streaming result set "
1066                + streamer
1067                + " when no streaming  result set was registered. This is an internal error.");
1068        }
1069
1070        if (streamer != this.streamingData) {
1071            throw new SQLException("Attempt to close streaming result set "
1072                + streamer + " that was not registered."
1073                + " Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on "
1074                + " any active result sets before attempting more queries.");
1075        }
1076
1077        this.streamingData = null;
1078    }
1079
1080    /**
1081     * Sets the buffer size to max-buf
1082     */
1083    void resetMaxBuf() {
1084        this.maxAllowedPacket = this.connection.getMaxAllowedPacket();
1085    }
1086
1087    /**
1088     * Send a command to the MySQL server If data is to be sent with command,
1089     * it should be put in ExtraData Raw packets can be sent by setting
1090     * QueryPacket to something other than null.
1091     *
1092     * @param command DOCUMENT ME!
1093     * @param extraData DOCUMENT ME!
1094     * @param queryPacket DOCUMENT ME!
1095     *
1096     * @return DOCUMENT ME!
1097     *
1098     * @throws Exception DOCUMENT ME!
1099     * @throws java.sql.SQLException DOCUMENT ME!
1100     */
1101    final Buffer sendCommand(int command, String extraData, Buffer queryPacket)
1102        throws Exception {
1103        checkForOutstandingStreamingData();
1104
1105        try {
1106            if (this.clearStreamBeforeEachQuery) {
1107                clearInputStream();
1108            }
1109
1110            //
1111            // PreparedStatements construct their own packets,
1112            // for efficiency's sake.
1113            //
1114            // If this is a generic query, we need to re-use
1115            // the sending packet.
1116            //
1117            if (queryPacket == null) {
1118                int packLength = HEADER_LENGTH + COMP_HEADER_LENGTH + 1
1119                    + ((extraData != null) ? extraData.length() : 0) + 2;
1120
1121                if (this.sendPacket == null) {
1122                    this.sendPacket = new Buffer(packLength);
1123                }
1124
1125                this.packetSequence = -1;
1126                this.sendPacket.clear();
1127
1128                this.sendPacket.writeByte((byte) command);
1129
1130                if ((command == MysqlDefs.INIT_DB)
1131                        || (command == MysqlDefs.CREATE_DB)
1132                        || (command == MysqlDefs.DROP_DB)
1133                        || (command == MysqlDefs.QUERY)) {
1134                    this.sendPacket.writeStringNoNull(extraData);
1135                } else if (command == MysqlDefs.PROCESS_KILL) {
1136                    long id = new Long(extraData).longValue();
1137                    this.sendPacket.writeLong(id);
1138                } else if ((command == MysqlDefs.RELOAD)
1139                        && (this.protocolVersion > 9)) {
1140                    Debug.msg(this, "Reload");
1141
1142                    //Packet.writeByte(reloadParam);
1143                }
1144
1145                send(this.sendPacket);
1146            } else {
1147                this.packetSequence = -1;
1148                send(queryPacket); // packet passed by PreparedStatement
1149            }
1150        } catch (SQLException sqlEx) {
1151            // don't wrap SQLExceptions
1152            throw sqlEx;
1153        } catch (Exception ex) {
1154            String underlyingMessage = ex.getMessage();
1155
1156            throw new java.sql.SQLException(SQLError.get(
1157                    SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE) + ": "
1158                + ex.getClass().getName() + ", "
1159                + ((underlyingMessage != null) ? underlyingMessage
1160                                               : "no message given by JVM")
1161                + (this.connection.useParanoidErrorMessages() ? ""
1162                                                              : Util
1163                .stackTraceToString(ex)),
1164                SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
1165        }
1166
1167        return checkErrorPacket(command);
1168    }
1169
1170    /**
1171     * Send a query specified in the String "Query" to the MySQL server. This
1172     * method uses the specified character encoding to get the bytes from the
1173     * query string.
1174     *
1175     * @param query DOCUMENT ME!
1176     * @param maxRows DOCUMENT ME!
1177     * @param characterEncoding DOCUMENT ME!
1178     * @param conn DOCUMENT ME!
1179     * @param resultSetType DOCUMENT ME!
1180     * @param streamResults DOCUMENT ME!
1181     * @param catalog DOCUMENT ME!
1182     *
1183     * @return DOCUMENT ME!
1184     *
1185     * @throws Exception DOCUMENT ME!
1186     */
1187    final ResultSet sqlQuery(String query, int maxRows,
1188        String characterEncoding, Connection conn, int resultSetType,
1189        boolean streamResults, String catalog) throws Exception {
1190        // We don't know exactly how many bytes we're going to get
1191        // from the query. Since we're dealing with Unicode, the
1192        // max is 2, so pad it (2 * query) + space for headers
1193        int packLength = HEADER_LENGTH + 1 + (query.length() * 2) + 2;
1194
1195        if (this.sendPacket == null) {
1196            this.sendPacket = new Buffer(packLength);
1197        } else {
1198            this.sendPacket.clear();
1199        }
1200
1201        this.sendPacket.writeByte((byte) MysqlDefs.QUERY);
1202
1203        if (characterEncoding != null) {
1204            SingleByteCharsetConverter converter = this.connection
1205                .getCharsetConverter(characterEncoding);
1206
1207            if (this.platformDbCharsetMatches) {
1208                this.sendPacket.writeStringNoNull(query, characterEncoding,
1209                    this.connection.getServerCharacterEncoding(), converter, 
1210          this.connection.parserKnowsUnicode());
1211            } else {
1212                if (StringUtils.startsWithIgnoreCaseAndWs(query, "LOAD DATA")) {
1213                    this.sendPacket.writeBytesNoNull(query.getBytes());
1214                } else {
1215                    this.sendPacket.writeStringNoNull(query, characterEncoding,
1216                        this.connection.getServerCharacterEncoding(), converter, 
1217              this.connection.parserKnowsUnicode());
1218                }
1219            }
1220        } else {
1221            this.sendPacket.writeStringNoNull(query);
1222        }
1223
1224        return sqlQueryDirect(this.sendPacket, maxRows, conn, resultSetType,
1225            streamResults, catalog);
1226    }
1227
1228    /**
1229     * Send a query stored in a packet directly to the server.
1230     *
1231     * @param queryPacket DOCUMENT ME!
1232     * @param maxRows DOCUMENT ME!
1233     * @param conn DOCUMENT ME!
1234     * @param resultSetType DOCUMENT ME!
1235     * @param streamResults DOCUMENT ME!
1236     * @param catalog DOCUMENT ME!
1237     *
1238     * @return DOCUMENT ME!
1239     *
1240     * @throws Exception DOCUMENT ME!
1241     */
1242    final ResultSet sqlQueryDirect(Buffer queryPacket, int maxRows,
1243        Connection conn, int resultSetType, boolean streamResults,
1244        String catalog) throws Exception {
1245        StringBuffer profileMsgBuf = null; // used if profiling
1246        long queryStartTime = 0;
1247
1248        if (this.profileSql) {
1249            profileMsgBuf = new StringBuffer();
1250            queryStartTime = System.currentTimeMillis();
1251
1252            byte[] queryBuf = queryPacket.getByteBuffer();
1253
1254            int queryLength = queryPacket.getPosition();
1255            
1256            boolean queryTruncated = false;
1257            
1258            if (queryLength > MAX_QUERY_LENGTH_TO_LOG) {
1259              queryLength = MAX_QUERY_LENGTH_TO_LOG;
1260              
1261              queryTruncated = true;
1262            }
1263            
1264            // Extract the actual query from the network packet
1265            String query = new String(queryBuf, 5,
1266                    (queryLength - 5));
1267            profileMsgBuf.append("Query\t\"");
1268            profileMsgBuf.append(query);
1269            
1270            if (queryTruncated) {
1271              profileMsgBuf.append(" ... (long query truncated)");
1272            }
1273            
1274            profileMsgBuf.append("\"\texecution time:\t");
1275        }
1276
1277        // Send query command and sql query string
1278        Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket);
1279
1280        if (this.profileSql) {
1281            long executionTime = System.currentTimeMillis() - queryStartTime;
1282            profileMsgBuf.append(executionTime);
1283            profileMsgBuf.append("\t");
1284        }
1285
1286        resultPacket.setPosition(resultPacket.getPosition() - 1);
1287
1288        long columnCount = resultPacket.readFieldLength();
1289
1290        if (Driver.TRACE) {
1291            Debug.msg(this, "Column count: " + columnCount);
1292        }
1293
1294        if (columnCount == 0) {
1295            if (this.profileSql) {
1296                System.err.println(profileMsgBuf.toString());
1297            }
1298
1299            return buildResultSetWithUpdates(resultPacket);
1300        } else if (columnCount == Buffer.NULL_LENGTH) {
1301            String charEncoding = null;
1302
1303            if (this.connection.useUnicode()) {
1304                charEncoding = this.connection.getEncoding();
1305            }
1306
1307            String fileName = null;
1308
1309            if (this.platformDbCharsetMatches) {
1310                fileName = ((charEncoding != null)
1311                    ? resultPacket.readString(charEncoding)
1312                    : resultPacket.readString());
1313            } else {
1314                fileName = resultPacket.readString();
1315            }
1316
1317            return sendFileToServer(fileName);
1318        } else {
1319            long fetchStartTime = 0;
1320
1321            if (this.profileSql) {
1322                fetchStartTime = System.currentTimeMillis();
1323            }
1324
1325            com.mysql.jdbc.ResultSet results = getResultSet(columnCount,
1326                    maxRows, resultSetType, streamResults, catalog);
1327
1328            if (this.profileSql) {
1329                long fetchElapsedTime = System.currentTimeMillis()
1330                    - fetchStartTime;
1331                profileMsgBuf.append("result set fetch time:\t");
1332                profileMsgBuf.append(fetchElapsedTime);
1333                System.err.println(profileMsgBuf.toString());
1334            }
1335
1336            return results;
1337        }
1338    }
1339
1340    /**
1341     * Returns the host this IO is connected to
1342     *
1343     * @return DOCUMENT ME!
1344     */
1345    String getHost() {
1346        return this.host;
1347    }
1348
1349    /**
1350     * Does the version of the MySQL server we are connected to meet the given
1351     * minimums?
1352     *
1353     * @param major DOCUMENT ME!
1354     * @param minor DOCUMENT ME!
1355     * @param subminor DOCUMENT ME!
1356     *
1357     * @return DOCUMENT ME!
1358     */
1359    boolean versionMeetsMinimum(int major, int minor, int subminor) {
1360        if (getServerMajorVersion() >= major) {
1361            if (getServerMajorVersion() == major) {
1362                if (getServerMinorVersion() >= minor) {
1363                    if (getServerMinorVersion() == minor) {
1364                        return (getServerSubMinorVersion() >= subminor);
1365                    } else {
1366                        // newer than major.minor
1367                        return true;