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;