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;
1368 }
1369 } else {
1370 // older than major.minor
1371 return false;
1372 }
1373 } else {
1374 // newer than major
1375 return true;
1376 }
1377 } else {
1378 return false;
1379 }
1380 }
1381
1382 private final int readFully(InputStream in, byte[] b, int off, int len)
1383 throws IOException {
1384 if (len < 0) {
1385 throw new IndexOutOfBoundsException();
1386 }
1387
1388 int n = 0;
1389
1390 while (n < len) {
1391 int count = in.read(b, off + n, len - n);
1392
1393 if (count < 0) {
1394 throw new EOFException();
1395 }
1396
1397 n += count;
1398 }
1399
1400 return n;
1401 }
1402
1403 /**
1404 * Read one packet from the MySQL server
1405 *
1406 * @return DOCUMENT ME!
1407 *
1408 * @throws SQLException DOCUMENT ME!
1409 * @throws java.sql.SQLException DOCUMENT ME!
1410 */
1411 private final Buffer readPacket() throws SQLException {
1412 try {
1413 int lengthRead = readFully(mysqlInput, this.packetHeaderBuf, 0, 4);
1414
1415 if (lengthRead < 4) {
1416 forceClose();
1417 throw new IOException("Unexpected end of input stream");
1418 }
1419
1420 int packetLength = ((int) (this.packetHeaderBuf[0] & 0xff))
1421 + (((int) (this.packetHeaderBuf[1] & 0xff)) << 8)
1422 + (((int) (this.packetHeaderBuf[2] & 0xff)) << 16);
1423
1424 byte multiPacketSeq = this.packetHeaderBuf[3];
1425
1426 // Read data
1427 byte[] buffer = new byte[packetLength + 1];
1428 readFully(this.mysqlInput, buffer, 0, packetLength);
1429 buffer[packetLength] = 0;
1430
1431 Buffer packet = new Buffer(buffer);
1432
1433 return packet;
1434 } catch (IOException ioEx) {
1435 StringBuffer message = new StringBuffer(SQLError.get(
1436 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE));
1437 message.append(": ");
1438 message.append(ioEx.getClass().getName());
1439 message.append(", underlying cause: ");
1440 message.append(ioEx.getMessage());
1441
1442 if (!this.connection.useParanoidErrorMessages()) {
1443 message.append(Util.stackTraceToString(ioEx));
1444 }
1445
1446 throw new java.sql.SQLException(message.toString(),
1447 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
1448 }
1449 }
1450
1451 private com.mysql.jdbc.ResultSet buildResultSetWithRows(String catalog,
1452 com.mysql.jdbc.Field[] fields, RowData rows, int resultSetConcurrency)
1453 throws SQLException {
1454 switch (resultSetConcurrency) {
1455 case java.sql.ResultSet.CONCUR_READ_ONLY:
1456 return new com.mysql.jdbc.ResultSet(catalog, fields, rows,
1457 this.connection);
1458
1459 case java.sql.ResultSet.CONCUR_UPDATABLE:
1460 return new com.mysql.jdbc.UpdatableResultSet(catalog, fields, rows,
1461 this.connection);
1462
1463 default:
1464 return new com.mysql.jdbc.ResultSet(catalog, fields, rows,
1465 this.connection);
1466 }
1467 }
1468
1469 private com.mysql.jdbc.ResultSet buildResultSetWithUpdates(
1470 Buffer resultPacket) throws SQLException {
1471 long updateCount = -1;
1472 long updateID = -1;
1473 String info = null;
1474
1475 try {
1476 if (this.useNewUpdateCounts) {
1477 updateCount = resultPacket.newReadLength();
1478 updateID = resultPacket.newReadLength();
1479 } else {
1480 updateCount = (long) resultPacket.readLength();
1481 updateID = (long) resultPacket.readLength();
1482 }
1483
1484 if (this.connection.isReadInfoMsgEnabled()) {
1485 if (this.use41Extensions) {
1486 int serverStatus = resultPacket.readInt();
1487 int warningCount = resultPacket.readInt();
1488
1489 resultPacket.readByte(); // advance pointer
1490 }
1491
1492 info = resultPacket.readString();
1493 }
1494 } catch (Exception ex) {
1495 throw new java.sql.SQLException(SQLError.get(
1496 SQLError.SQL_STATE_GENERAL_ERROR) + ": "
1497 + ex.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR, -1);
1498 }
1499
1500 if (Driver.TRACE) {
1501 Debug.msg(this, "Update Count = " + updateCount);
1502 }
1503
1504 ResultSet updateRs = new ResultSet(updateCount, updateID);
1505
1506 if (info != null) {
1507 updateRs.setServerInfo(info);
1508 }
1509
1510 return updateRs;
1511 }
1512
1513 /**
1514 * Don't hold on to overly-large packets
1515 */
1516 private void reclaimLargeReusablePacket() {
1517 if ((this.reusablePacket != null)
1518 && (this.reusablePacket.getBufLength() > 1048576)) {
1519 this.reusablePacket = new Buffer(this.connection.getNetBufferLength());
1520 }
1521 }
1522
1523 /**
1524 * Re-use a packet to read from the MySQL server
1525 *
1526 * @param reuse DOCUMENT ME!
1527 *
1528 * @return DOCUMENT ME!
1529 *
1530 * @throws SQLException DOCUMENT ME!
1531 * @throws SQLException DOCUMENT ME!
1532 */
1533 private final Buffer reuseAndReadPacket(Buffer reuse)
1534 throws SQLException {
1535 try {
1536 reuse.setWasMultiPacket(false);
1537
1538 int lengthRead = readFully(mysqlInput, this.packetHeaderBuf, 0, 4);
1539
1540 if (lengthRead < 4) {
1541 forceClose();
1542 throw new IOException("Unexpected end of input stream");
1543 }
1544
1545 int packetLength = ((int) (this.packetHeaderBuf[0] & 0xff))
1546 + (((int) (this.packetHeaderBuf[1] & 0xff)) << 8)
1547 + (((int) (this.packetHeaderBuf[2] & 0xff)) << 16);
1548
1549 byte multiPacketSeq = this.packetHeaderBuf[3];
1550
1551 //byte multiPacketSeq = (byte) this.mysqlInput.read();
1552 // Set the Buffer to it's original state
1553 reuse.setPosition(0);
1554 reuse.setSendLength(0);
1555
1556 // Do we need to re-alloc the byte buffer?
1557 //
1558 // Note: We actually check the length of the buffer,
1559 // rather than getBufLength(), because getBufLength() is not
1560 // necesarily the actual length of the byte array
1561 // used as the buffer
1562 if (reuse.getByteBuffer().length <= packetLength) {
1563 reuse.setByteBuffer(new byte[packetLength + 1]);
1564 }
1565
1566 // Set the new length
1567 reuse.setBufLength(packetLength);
1568
1569 // Read the data from the server
1570 readFully(this.mysqlInput, reuse.getByteBuffer(), 0, packetLength);
1571
1572 boolean isMultiPacket = false;
1573
1574 if (packetLength == maxThreeBytes) {
1575 reuse.setPosition((int) maxThreeBytes);
1576
1577 int packetEndPoint = packetLength;
1578
1579 // it's multi-packet
1580 isMultiPacket = true;
1581
1582 lengthRead = readFully(mysqlInput, this.packetHeaderBuf, 0, 4);
1583
1584 if (lengthRead < 4) {
1585 forceClose();
1586 throw new IOException("Unexpected end of input stream");
1587 }
1588
1589 packetLength = ((int) (this.packetHeaderBuf[0] & 0xff))
1590 + (((int) (this.packetHeaderBuf[1] & 0xff)) << 8)
1591 + (((int) (this.packetHeaderBuf[2] & 0xff)) << 16);
1592
1593 Buffer multiPacket = new Buffer(packetLength);
1594 boolean firstMultiPkt = true;
1595
1596 while (true) {
1597 if (!firstMultiPkt) {
1598 lengthRead = readFully(mysqlInput,
1599 this.packetHeaderBuf, 0, 4);
1600
1601 if (lengthRead < 4) {
1602 forceClose();
1603 throw new IOException(
1604 "Unexpected end of input stream");
1605 }
1606
1607 packetLength = ((int) (this.packetHeaderBuf[0] & 0xff))
1608 + (((int) (this.packetHeaderBuf[1] & 0xff)) << 8)
1609 + (((int) (this.packetHeaderBuf[2] & 0xff)) << 16);
1610 } else {
1611 firstMultiPkt = false;
1612 }
1613
1614 if (!this.useNewLargePackets && (packetLength == 1)) {
1615 clearInputStream();
1616
1617 break;
1618 } else if (packetLength < this.maxThreeBytes) {
1619 byte newPacketSeq = this.packetHeaderBuf[3];
1620
1621 if (newPacketSeq != (multiPacketSeq + 1)) {
1622 throw new IOException(
1623 "Packets received out of order");
1624 }
1625
1626 multiPacketSeq = newPacketSeq;
1627
1628 // Set the Buffer to it's original state
1629 multiPacket.setPosition(0);
1630 multiPacket.setSendLength(0);
1631
1632 // Set the new length
1633 multiPacket.setBufLength(packetLength);
1634
1635 // Read the data from the server
1636 byte[] byteBuf = multiPacket.getByteBuffer();
1637 int lengthToWrite = packetLength;
1638
1639 int bytesRead = readFully(this.mysqlInput, byteBuf, 0,
1640 packetLength);
1641
1642 if (bytesRead != lengthToWrite) {
1643 throw new SQLException(
1644 "Short read from server, expected "
1645 + lengthToWrite + " bytes, received only "
1646 + bytesRead + ".",
1647 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE);
1648 }
1649
1650 reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
1651
1652 packetEndPoint += lengthToWrite;
1653
1654 break; // end of multipacket sequence
1655 }
1656
1657 byte newPacketSeq = this.packetHeaderBuf[3];
1658
1659 if (newPacketSeq != (multiPacketSeq + 1)) {
1660 throw new IOException("Packets received out of order");
1661 }
1662
1663 multiPacketSeq = newPacketSeq;
1664
1665 // Set the Buffer to it's original state
1666 multiPacket.setPosition(0);
1667 multiPacket.setSendLength(0);
1668
1669 // Set the new length
1670 multiPacket.setBufLength(packetLength);
1671
1672 // Read the data from the server
1673 byte[] byteBuf = multiPacket.getByteBuffer();
1674 int lengthToWrite = packetLength;
1675
1676 int bytesRead = readFully(this.mysqlInput, byteBuf, 0,
1677 packetLength);
1678
1679 if (bytesRead != lengthToWrite) {
1680 throw new SQLException(
1681 "Short read from server, expected " + lengthToWrite
1682 + " bytes, received only " + bytesRead + ".",
1683 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE);
1684 }
1685
1686 reuse.writeBytesNoNull(byteBuf, 0, lengthToWrite);
1687
1688 packetEndPoint += lengthToWrite;
1689 }
1690
1691 //reuse.writeByte((byte) 0);
1692 reuse.setPosition(0);
1693 reuse.setWasMultiPacket(true);
1694 }
1695
1696 if (!isMultiPacket) {
1697 reuse.getByteBuffer()[packetLength] = 0; // Null-termination
1698 }
1699
1700 return reuse;
1701 } catch (IOException ioEx) {
1702 StringBuffer message = new StringBuffer(SQLError.get(
1703 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE));
1704 message.append(": ");
1705 message.append(ioEx.getClass().getName());
1706 message.append(", underlying cause: ");
1707 message.append(ioEx.getMessage());
1708
1709 if (!this.connection.useParanoidErrorMessages()) {
1710 message.append(Util.stackTraceToString(ioEx));
1711 }
1712
1713 throw new java.sql.SQLException(message.toString(),
1714 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
1715 }
1716 }
1717
1718 /**
1719 * Send a packet to the MySQL server
1720 *
1721 * @param packet DOCUMENT ME!
1722 *
1723 * @throws SQLException DOCUMENT ME!
1724 */
1725 private final void send(Buffer packet) throws SQLException {
1726 int l = packet.getPosition();
1727 send(packet, l);
1728
1729 //
1730 // Don't hold on to large packets
1731 //
1732 if (packet == this.sharedSendPacket) {
1733 reclaimLargeSharedSendPacket();
1734 }
1735 }
1736
1737 private final void send(Buffer packet, int packetLen)
1738 throws SQLException {
1739 try {
1740 if (packetLen > this.maxAllowedPacket) {
1741 throw new PacketTooBigException(packetLen, this.maxAllowedPacket);
1742 }
1743
1744 if ((serverMajorVersion >= 4) && (packetLen >= maxThreeBytes)) {
1745 sendSplitPackets(packet);
1746 } else {
1747 this.packetSequence++;
1748
1749 Buffer packetToSend = packet;
1750
1751 packetToSend.setPosition(0);
1752
1753 if (this.useCompression) {
1754 packetToSend = compressPacket(packet, 0, packetLen,
1755 HEADER_LENGTH);
1756 packetLen = packetToSend.getPosition();
1757 } else {
1758 packetToSend.writeLongInt(packetLen - HEADER_LENGTH);
1759 packetToSend.writeByte(this.packetSequence);
1760 }
1761
1762 this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
1763 packetLen);
1764 this.mysqlOutput.flush();
1765 }
1766
1767 //
1768 // Don't hold on to large packets
1769 //
1770 if (packet == this.sharedSendPacket) {
1771 reclaimLargeSharedSendPacket();
1772 }
1773 } catch (IOException ioEx) {
1774 StringBuffer message = new StringBuffer(SQLError.get(
1775 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE));
1776 message.append(": ");
1777 message.append(ioEx.getClass().getName());
1778 message.append(", underlying cause: ");
1779 message.append(ioEx.getMessage());
1780
1781 if (!this.connection.useParanoidErrorMessages()) {
1782 message.append(Util.stackTraceToString(ioEx));
1783 }
1784
1785 throw new java.sql.SQLException(message.toString(),
1786 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
1787 }
1788 }
1789
1790 /**
1791 * Reads and sends a file to the server for LOAD DATA LOCAL INFILE
1792 *
1793 * @param fileName the file name to send.
1794 *
1795 * @return DOCUMENT ME!
1796 *
1797 * @throws SQLException DOCUMENT ME!
1798 */
1799 private final ResultSet sendFileToServer(String fileName)
1800 throws SQLException {
1801 Buffer filePacket = (loadFileBufRef == null) ? null
1802 : (Buffer) (loadFileBufRef
1803 .get());
1804
1805 int packetLength = Math.min(this.connection.getMaxAllowedPacket()
1806 - (HEADER_LENGTH * 3),
1807 alignPacketSize(this.connection.getMaxAllowedPacket() - 16, 4096)
1808 - (HEADER_LENGTH * 3));
1809
1810 //
1811 // This packet may be _way_ too large to actually allocate,
1812 // unforunately, LOAD DATA LOCAL INFILE requires this setup...
1813 //
1814 try {
1815 if (filePacket == null) {
1816 filePacket = new Buffer((int) (packetLength + HEADER_LENGTH));
1817 loadFileBufRef = new SoftReference(filePacket);
1818 }
1819 } catch (OutOfMemoryError oom) {
1820 // Attempt to do this, but it might not work...
1821 // The server is expecting at least one packet, so we
1822 // send an empty 'EOF' packet...
1823 this.reusablePacket.clear();
1824 send(this.reusablePacket);
1825
1826 throw new SQLException("Unable to allocate packet of size '"
1827 + (packetLength + HEADER_LENGTH)
1828 + "' for LOAD DATA LOCAL INFILE. Either increase heap space available to your JVM, or adjust the MySQL server variable 'max_allowed_packet'",
1829 SQLError.SQL_STATE_MEMORY_ALLOCATION_FAILURE);
1830 }
1831
1832 filePacket.clear();
1833 send(filePacket, 0);
1834
1835 byte[] fileBuf = new byte[packetLength];
1836
1837 BufferedInputStream fileIn = null;
1838
1839 try {
1840 fileIn = new BufferedInputStream(new FileInputStream(fileName));
1841
1842 int bytesRead = 0;
1843
1844 while ((bytesRead = fileIn.read(fileBuf)) != -1) {
1845 filePacket.clear();
1846 filePacket.writeBytesNoNull(fileBuf, 0, bytesRead);
1847 send(filePacket);
1848 }
1849 } catch (IOException ioEx) {
1850 StringBuffer messageBuf = new StringBuffer("Unable to open file ");
1851
1852 if (!this.connection.useParanoidErrorMessages()) {
1853 messageBuf.append("'");
1854
1855 if (fileName != null) {
1856 messageBuf.append(fileName);
1857 }
1858
1859 messageBuf.append("'");
1860 }
1861
1862 messageBuf.append("for 'LOAD DATA LOCAL INFILE' command.");
1863
1864 if (!this.connection.useParanoidErrorMessages()) {
1865 messageBuf.append("Due to underlying IOException: ");
1866 messageBuf.append(Util.stackTraceToString(ioEx));
1867 }
1868
1869 throw new SQLException(messageBuf.toString(),
1870 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1871 } finally {
1872 if (fileIn != null) {
1873 try {
1874 fileIn.close();
1875 } catch (Exception ex) {
1876 throw new SQLException("Unable to close local file during LOAD DATA LOCAL INFILE command",
1877 SQLError.SQL_STATE_GENERAL_ERROR);
1878 }
1879
1880 fileIn = null;
1881 } else {
1882 // file open failed, but server needs one packet
1883 filePacket.clear();
1884 send(filePacket);
1885 }
1886 }
1887
1888 // send empty packet to mark EOF
1889 filePacket.clear();
1890 send(filePacket);
1891
1892 Buffer resultPacket = checkErrorPacket();
1893
1894 return buildResultSetWithUpdates(resultPacket);
1895 }
1896
1897 /**
1898 * Checks for errors in the reply packet, and if none, returns the reply
1899 * packet, ready for reading
1900 *
1901 * @return DOCUMENT ME!
1902 *
1903 * @throws SQLException DOCUMENT ME!
1904 */
1905 private Buffer checkErrorPacket() throws SQLException {
1906 return checkErrorPacket(-1);
1907 }
1908
1909 /**
1910 * Checks for errors in the reply packet, and if none, returns the reply
1911 * packet, ready for reading
1912 *
1913 * @param command the command being issued (if used)
1914 *
1915 * @return DOCUMENT ME!
1916 *
1917 * @throws SQLException if an error packet was received
1918 * @throws java.sql.SQLException DOCUMENT ME!
1919 */
1920 private Buffer checkErrorPacket(int command) throws SQLException {
1921 int statusCode = 0;
1922 Buffer resultPacket = null;
1923
1924 try {
1925 // Check return value, if we get a java.io.EOFException,
1926 // the server has gone away. We'll pass it on up the
1927 // exception chain and let someone higher up decide
1928 // what to do (barf, reconnect, etc).
1929 resultPacket = reuseAndReadPacket(this.reusablePacket);
1930 statusCode = resultPacket.readByte();
1931 } catch (SQLException sqlEx) {
1932 // don't wrap SQLExceptions
1933 throw sqlEx;
1934 } catch (Exception fallThru) {
1935 String underlyingMessage = fallThru.getMessage();
1936
1937 throw new java.sql.SQLException(SQLError.get(
1938 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE) + ": "
1939 + fallThru.getClass().getName() + ", "
1940 + ((underlyingMessage != null) ? underlyingMessage
1941 : "no message given by JVM")
1942 + (this.connection.useParanoidErrorMessages() ? ""
1943 : Util
1944 .stackTraceToString(fallThru)),
1945 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
1946 }
1947
1948 // Error handling
1949 if (statusCode == (byte) 0xff) {
1950 String serverErrorMessage;
1951 int errno = 2000;
1952
1953 if (this.protocolVersion > 9) {
1954 errno = resultPacket.readInt();
1955
1956 String xOpen = null;
1957
1958 serverErrorMessage = resultPacket.readString();
1959
1960 if (serverErrorMessage.startsWith("#")) {
1961 // we have an SQLState
1962 if (serverErrorMessage.length() > 6) {
1963 xOpen = serverErrorMessage.substring(1, 6);
1964 serverErrorMessage = serverErrorMessage.substring(6);
1965
1966 if (xOpen.equals("HY000")) {
1967 xOpen = SQLError.mysqlToXOpen(errno);
1968 }
1969 } else {
1970 xOpen = SQLError.mysqlToXOpen(errno);
1971 }
1972 } else {
1973 xOpen = SQLError.mysqlToXOpen(errno);
1974 }
1975
1976 clearInputStream();
1977
1978 StringBuffer errorBuf = new StringBuffer();
1979
1980 String xOpenErrorMessage = SQLError.get(xOpen);
1981
1982 if (!this.connection.getUseOnlyServerErrorMessages()) {
1983 if (xOpenErrorMessage != null) {
1984 errorBuf.append(xOpenErrorMessage);
1985 errorBuf.append(" message from server: \"");
1986 }
1987 }
1988
1989 errorBuf.append(serverErrorMessage);
1990
1991 if (!this.connection.getUseOnlyServerErrorMessages()) {
1992 if (xOpenErrorMessage != null) {
1993 errorBuf.append("\"");
1994 }
1995 }
1996
1997 throw new SQLException(errorBuf.toString(), xOpen, errno);
1998 } else {
1999 serverErrorMessage = resultPacket.readString();
2000 clearInputStream();
2001
2002 if (serverErrorMessage.indexOf("Unknown column") != -1) {
2003
2004 StringBuffer errorBuf = new StringBuffer();
2005
2006 String xOpenErrorMessage = SQLError.get(SQLError.SQL_STATE_COLUMN_NOT_FOUND);
2007
2008 if (!this.connection.getUseOnlyServerErrorMessages()) {
2009 if (xOpenErrorMessage != null) {
2010 errorBuf.append(xOpenErrorMessage);
2011 errorBuf.append(" message from server: \"");
2012 }
2013 }
2014
2015 errorBuf.append(serverErrorMessage);
2016
2017 if (!this.connection.getUseOnlyServerErrorMessages()) {
2018 if (xOpenErrorMessage != null) {
2019 errorBuf.append("\"");
2020 }
2021 }
2022
2023 throw new java.sql.SQLException(errorBuf.toString(),
2024 SQLError.SQL_STATE_COLUMN_NOT_FOUND, -1);
2025 } else {
2026 StringBuffer errorBuf = new StringBuffer();
2027
2028 String xOpenErrorMessage = SQLError.get(SQLError.SQL_STATE_GENERAL_ERROR);
2029
2030 if (!this.connection.getUseOnlyServerErrorMessages()) {
2031 if (xOpenErrorMessage != null) {
2032 errorBuf.append(xOpenErrorMessage);
2033 errorBuf.append(" message from server: \"");
2034 }
2035 }
2036
2037 errorBuf.append(serverErrorMessage);
2038
2039 if (!this.connection.getUseOnlyServerErrorMessages()) {
2040 if (xOpenErrorMessage != null) {
2041 errorBuf.append("\"");
2042 }
2043 }
2044
2045 throw new java.sql.SQLException(errorBuf.toString(),
2046 SQLError.SQL_STATE_GENERAL_ERROR, -1);
2047 }
2048 }
2049 }
2050
2051 return resultPacket;
2052 }
2053
2054 /**
2055 * Sends a large packet to the server as a series of smaller packets
2056 *
2057 * @param packet DOCUMENT ME!
2058 *
2059 * @throws SQLException DOCUMENT ME!
2060 * @throws SQLException DOCUMENT ME!
2061 */
2062 private final void sendSplitPackets(Buffer packet)
2063 throws SQLException {
2064 try {
2065 //
2066 // Big packets are handled by splitting them in packets of MAX_THREE_BYTES
2067 // length. The last packet is always a packet that is < MAX_THREE_BYTES.
2068 // (The last packet may even have a length of 0)
2069 //
2070 //
2071 // NB: Guarded by execSQL. If the driver changes architecture, this
2072 // will need to be synchronized in some other way
2073 //
2074 Buffer headerPacket = (splitBufRef == null) ? null
2075 : (Buffer) (splitBufRef
2076 .get());
2077
2078 //
2079 // Store this packet in a soft reference...It can be re-used if not GC'd (so clients
2080 // that use it frequently won't have to re-alloc the 16M buffer), but we don't
2081 // penalize infrequent users of large packets by keeping 16M allocated all of the time
2082 //
2083 if (headerPacket == null) {
2084 headerPacket = new Buffer((int) (maxThreeBytes + HEADER_LENGTH));
2085 splitBufRef = new SoftReference(headerPacket);
2086 }
2087
2088 int len = packet.getPosition();
2089 int splitSize = (int) maxThreeBytes;
2090 int originalPacketPos = HEADER_LENGTH;
2091 byte[] origPacketBytes = packet.getByteBuffer();
2092 byte[] headerPacketBytes = headerPacket.getByteBuffer();
2093
2094 if (Driver.DEBUG) {
2095 System.out.println("\n\nSending split packets for packet of "
2096 + len + " bytes:\n");
2097 }
2098
2099 while (len >= maxThreeBytes) {
2100 headerPacket.setPosition(0);
2101 headerPacket.writeLongInt(splitSize);
2102 this.packetSequence++;
2103 headerPacket.writeByte(this.packetSequence);
2104 System.arraycopy(origPacketBytes, originalPacketPos,
2105 headerPacketBytes, 4, splitSize);
2106 this.mysqlOutput.write(headerPacketBytes, 0,
2107 splitSize + HEADER_LENGTH);
2108 this.mysqlOutput.flush();
2109
2110 if (Driver.DEBUG) {
2111 System.out.print(" total packet length (header & data) "
2112 + (splitSize + HEADER_LENGTH) + "\nheader: ");
2113 headerPacket.dumpHeader();
2114 System.out.println();
2115 System.out.print("last eight bytes: ");
2116 headerPacket.dumpNBytes(((splitSize + HEADER_LENGTH) - 8), 8);
2117 System.out.println();
2118 }
2119
2120 originalPacketPos += splitSize;
2121 len -= splitSize;
2122 }
2123
2124 //
2125 // Write last packet
2126 //
2127 headerPacket.clear();
2128 headerPacket.setPosition(0);
2129 headerPacket.writeLongInt(len - HEADER_LENGTH);
2130 this.packetSequence++;
2131 headerPacket.writeByte(this.packetSequence);
2132
2133 if (len != 0) {
2134 System.arraycopy(origPacketBytes, originalPacketPos,
2135 headerPacketBytes, 4, len - HEADER_LENGTH);
2136 }
2137
2138 this.mysqlOutput.write(headerPacket.getByteBuffer(), 0, len);
2139 this.mysqlOutput.flush();
2140
2141 if (Driver.DEBUG) {
2142 System.out.print(" total packet length (header & data) " + len
2143 + ",\nheader: ");
2144 headerPacket.dumpHeader();
2145 System.out.println();
2146 System.out.print("last packet bytes: ");
2147 headerPacket.dumpNBytes(0, len);
2148 System.out.println();
2149 }
2150 } catch (IOException ioEx) {
2151 StringBuffer message = new StringBuffer(SQLError.get(
2152 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE));
2153 message.append(": ");
2154 message.append(ioEx.getClass().getName());
2155 message.append(", underlying cause: ");
2156 message.append(ioEx.getMessage());
2157
2158 if (!this.connection.useParanoidErrorMessages()) {
2159 message.append(Util.stackTraceToString(ioEx));
2160 }
2161
2162 throw new java.sql.SQLException(message.toString(),
2163 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0);
2164 }
2165 }
2166
2167 private int alignPacketSize(int a, int l) {
2168 return ((((a) + (l)) - 1) & ~((l) - 1));
2169 }
2170
2171 private void checkForOutstandingStreamingData() throws SQLException {
2172 if (this.streamingData != null) {
2173 if (!this.connection.getClobberStreamingResults()) {
2174 throw new SQLException("Streaming result set "
2175 + this.streamingData + " is still active."
2176 + " Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on "
2177 + " any active result sets before attempting more queries.");
2178 } else {
2179 // Close the result set
2180 this.streamingData.getOwner().realClose(false);
2181
2182 // clear any pending data....
2183 clearInputStream();
2184 }
2185 }
2186 }
2187
2188 private void clearInputStream() throws SQLException {
2189 try {
2190 int len = this.mysqlInput.available();
2191
2192 while (len > 0) {
2193 this.mysqlInput.skip(len);
2194 len = this.mysqlInput.available();
2195 }
2196 } catch (IOException ioEx) {
2197 throw new SQLException("I/O error while clearing input stream of old results",
2198 SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE);
2199 }
2200 }
2201
2202 private Buffer compressPacket(Buffer packet, int offset, int packetLen,
2203 int headerLength) throws SQLException {
2204 packet.writeLongInt(packetLen - headerLength);
2205 packet.writeByte((byte) 0); // wrapped packet has 0 packet seq.
2206
2207 int lengthToWrite = 0;
2208 int compressedLength = 0;
2209 byte[] bytesToCompress = packet.getByteBuffer();
2210 byte[] compressedBytes = null;
2211 int offsetWrite = 0;
2212
2213 if (true /*packetLen < MIN_COMPRESS_LEN*/) {
2214 lengthToWrite = packetLen;
2215 compressedBytes = packet.getByteBuffer();
2216 compressedLength = 0;
2217 offsetWrite = offset;
2218 } else {
2219 compressedBytes = new byte[bytesToCompress.length * 2];
2220
2221 this.deflater.reset();
2222 this.deflater.setInput(bytesToCompress, offset, packetLen);
2223 this.deflater.finish();
2224
2225 int compLen = this.deflater.deflate(compressedBytes);
2226
2227 if (compLen > packetLen) {
2228 lengthToWrite = packetLen;
2229 compressedBytes = packet.getByteBuffer();
2230 compressedLength = 0;
2231 offsetWrite = offset;
2232 } else {
2233 lengthToWrite = compLen;
2234 headerLength += COMP_HEADER_LENGTH;
2235 compressedLength = packetLen;
2236 }
2237 }
2238
2239 Buffer compressedPacket = new Buffer(packetLen + headerLength);
2240
2241 compressedPacket.setPosition(0);
2242 compressedPacket.writeLongInt(lengthToWrite);
2243 compressedPacket.writeByte(this.packetSequence);
2244 compressedPacket.writeLongInt(compressedLength);
2245 compressedPacket.writeBytesNoNull(compressedBytes, offsetWrite,
2246 lengthToWrite);
2247
2248 return compressedPacket;
2249 }
2250
2251 private SocketFactory createSocketFactory() throws SQLException {
2252 try {
2253 if (socketFactoryClassName == null) {
2254 throw new SQLException("No name specified for socket factory",
2255 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
2256 }
2257
2258 return (SocketFactory) (Class.forName(socketFactoryClassName)
2259 .newInstance());
2260 } catch (Exception ex) {
2261 throw new SQLException("Could not create socket factory '"
2262 + socketFactoryClassName + "' due to underlying exception: "
2263 + ex.toString()
2264 + (this.connection.useParanoidErrorMessages() ? ""
2265 : Util
2266 .stackTraceToString(ex)),
2267 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
2268 }
2269 }
2270
2271 /**
2272 * Ensures that we don't hold on to overly-large send packets
2273 */
2274 private void reclaimLargeSharedSendPacket() {
2275 if ((this.sharedSendPacket != null)
2276 && (this.sharedSendPacket.getBufLength() > 1048576)) {
2277 this.sharedSendPacket = new Buffer(this.connection
2278 .getNetBufferLength());
2279 }
2280 }
2281
2282 /**
2283 * Secure authentication for 4.1 and newer servers.
2284 *
2285 * @param packet DOCUMENT ME!
2286 * @param packLength
2287 * @param user
2288 * @param password
2289 * @param database DOCUMENT ME!
2290 * @param writeClientParams
2291 *
2292 * @throws SQLException
2293 */
2294 private void secureAuth(Buffer packet, int packLength, String user,
2295 String password, String database, boolean writeClientParams)
2296 throws SQLException {
2297 // Passwords can be 16 chars long
2298 if (packet == null) {
2299 packet = new Buffer(packLength);
2300 }
2301
2302 if (writeClientParams) {
2303 if (this.use41Extensions) {
2304 if (versionMeetsMinimum(4, 1, 1)) {
2305 packet.writeLong(this.clientParam);
2306 packet.writeLong(this.maxThreeBytes);
2307
2308 // charset, JDBC will connect as 'latin1',
2309 // and use 'SET NAMES' to change to the desired
2310 // charset after the connection is established.
2311 packet.writeByte((byte) 8);
2312
2313 // Set of bytes reserved for future use.
2314 packet.writeBytesNoNull(new byte[23]);
2315 } else {
2316 packet.writeLong(this.clientParam);
2317 packet.writeLong(this.maxThreeBytes);
2318 }
2319 } else {
2320 packet.writeInt((int) this.clientParam);
2321 packet.writeLongInt(this.maxThreeBytes);
2322 }
2323 }
2324
2325 // User/Password data
2326 packet.writeString(user);
2327
2328 if (password.length() != 0) {
2329 /* Prepare false scramble */
2330 packet.writeString(FALSE_SCRAMBLE);
2331 } else {
2332 /* For empty password*/
2333 packet.writeString("");
2334 }
2335
2336 if (((this.serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
2337 && (database != null) && (database.length() > 0)) {
2338 packet.writeString(database);
2339 }
2340
2341 send(packet);
2342
2343 //
2344 // Don't continue stages if password is empty
2345 //
2346 if (password.length() > 0) {
2347 Buffer b = readPacket();
2348
2349 b.setPosition(0);
2350
2351 byte[] replyAsBytes = b.getByteBuffer();
2352
2353 if ((replyAsBytes.length == 25) && (replyAsBytes[0] != 0)) {
2354 // Old passwords will have '*' at the first byte of hash */
2355 if (replyAsBytes[0] != '*') {
2356 try {
2357 /* Build full password hash as it is required to decode scramble */
2358 byte[] buff = Security.passwordHashStage1(password);
2359
2360 /* Store copy as we'll need it later */
2361 byte[] passwordHash = new byte[buff.length];
2362 System.arraycopy(buff, 0, passwordHash, 0, buff.length);
2363
2364 /* Finally hash complete password using hash we got from server */
2365 passwordHash = Security.passwordHashStage2(passwordHash,
2366 replyAsBytes);
2367
2368 byte[] packetDataAfterSalt = new byte[replyAsBytes.length
2369 - 5];
2370
2371 System.arraycopy(replyAsBytes, 4, packetDataAfterSalt,
2372 0, replyAsBytes.length - 5);
2373
2374 byte[] mysqlScrambleBuff = new byte[20];
2375
2376 /* Decypt and store scramble 4 = hash for stage2 */
2377 Security.passwordCrypt(packetDataAfterSalt,
2378 mysqlScrambleBuff, passwordHash, 20);
2379
2380 /* Encode scramble with password. Recycle buffer */
2381 Security.passwordCrypt(mysqlScrambleBuff, buff, buff, 20);
2382
2383 Buffer packet2 = new Buffer(25);
2384 packet2.writeBytesNoNull(buff);
2385
2386 this.packetSequence++;
2387
2388 send(packet2, 24);
2389 } catch (NoSuchAlgorithmException nse) {
2390 throw new SQLException(
2391 "Failed to create message digest 'SHA-1' for authentication. "
2392 + " You must use a JDK that supports JCE to be able to use secure connection authentication",
2393 SQLError.SQL_STATE_GENERAL_ERROR);
2394 }
2395 } else {
2396 try {
2397 /* Create password to decode scramble */
2398 byte[] passwordHash = Security.createKeyFromOldPassword(password);
2399
2400 /* Decypt and store scramble 4 = hash for stage2 */
2401 byte[] netReadPos4 = new byte[replyAsBytes.length - 5];
2402
2403 System.arraycopy(replyAsBytes, 4, netReadPos4, 0,
2404 replyAsBytes.length - 5);
2405
2406 byte[] mysqlScrambleBuff = new byte[20];
2407
2408 /* Decypt and store scramble 4 = hash for stage2 */
2409 Security.passwordCrypt(netReadPos4, mysqlScrambleBuff,
2410 passwordHash, 20);
2411
2412 /* Finally scramble decoded scramble with password */
2413 String scrambledPassword = Util.scramble(new String(
2414 mysqlScrambleBuff), password);
2415
2416 Buffer packet2 = new Buffer(packLength);
2417 packet2.writeString(scrambledPassword);
2418 this.packetSequence++;
2419
2420 send(packet2, 24);
2421 } catch (NoSuchAlgorithmException nse) {
2422 throw new SQLException(
2423 "Failed to create message digest 'SHA-1' for authentication. "
2424 + " You must use a JDK that supports JCE to be able to use secure connection authentication",
2425 SQLError.SQL_STATE_GENERAL_ERROR);
2426 }
2427 }
2428 }
2429 }
2430 }
2431
2432 /**
2433 * Secure authentication for 4.1.1 and newer servers.
2434 *
2435 * @param packet DOCUMENT ME!
2436 * @param packLength
2437 * @param user
2438 * @param password
2439 * @param database DOCUMENT ME!
2440 * @param writeClientParams
2441 *
2442 * @throws SQLException
2443 */
2444 private void secureAuth411(Buffer packet, int packLength, String user,
2445 String password, String database, boolean writeClientParams)
2446 throws SQLException {
2447 // SERVER: public_seed=create_random_string()
2448 // send(public_seed)
2449 //
2450 // CLIENT: recv(public_seed)
2451 // hash_stage1=sha1("password")
2452 // hash_stage2=sha1(hash_stage1)
2453 // reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
2454 //
2455 // // this three steps are done in scramble()
2456 //
2457 // send(reply)
2458 //
2459 //
2460 // SERVER: recv(reply)
2461 // hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
2462 // candidate_hash2=sha1(hash_stage1)
2463 // check(candidate_hash2==hash_stage2)
2464 // Passwords can be 16 chars long
2465 if (packet == null) {
2466 packet = new Buffer(packLength);
2467 }
2468
2469 if (writeClientParams) {
2470 if (this.use41Extensions) {
2471 if (versionMeetsMinimum(4, 1, 1)) {
2472 packet.writeLong(this.clientParam);
2473 packet.writeLong(this.maxThreeBytes);
2474
2475 // charset, JDBC will connect as 'latin1',
2476 // and use 'SET NAMES' to change to the desired
2477 // charset after the connection is established.
2478 packet.writeByte((byte) 8);
2479
2480 // Set of bytes reserved for future use.
2481 packet.writeBytesNoNull(new byte[23]);
2482 } else {
2483 packet.writeLong(this.clientParam);
2484 packet.writeLong(this.maxThreeBytes);
2485 }
2486 } else {
2487 packet.writeInt((int) this.clientParam);
2488 packet.writeLongInt(this.maxThreeBytes);
2489 }
2490 }
2491
2492 // User/Password data
2493 packet.writeString(user);
2494
2495 if (password.length() != 0) {
2496 packet.writeByte((byte) 0x14);
2497
2498 try {
2499 packet.writeBytesNoNull(Security.scramble411(password, this.seed));
2500 } catch (NoSuchAlgorithmException nse) {
2501 throw new SQLException(
2502 "Failed to create message digest 'SHA-1' for authentication. "
2503 + " You must use a JDK that supports JCE to be able to use secure connection authentication",
2504 SQLError.SQL_STATE_GENERAL_ERROR);
2505 }
2506 } else {
2507 /* For empty password*/
2508 packet.writeByte((byte) 0);
2509 }
2510
2511 if (((this.serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
2512 && (database != null) && (database.length() > 0)) {
2513 packet.writeString(database);
2514 }
2515
2516 send(packet);
2517
2518 byte savePacketSequence = this.packetSequence++;
2519
2520 Buffer reply = checkErrorPacket();
2521
2522 if (reply.isLastDataPacket()) {
2523 /*
2524 By sending this very specific reply server asks us to send scrambled
2525 password in old format. The reply contains scramble_323.
2526 */
2527 this.packetSequence = ++savePacketSequence;
2528 packet.clear();
2529
2530 String seed323 = this.seed.substring(0, 8);
2531 packet.writeString(Util.newCrypt(password, seed323));
2532 send(packet);
2533
2534 /* Read what server thinks about out new auth message report */
2535 checkErrorPacket();
2536 }
2537 }
2538
2539 /**
2540 * Secure authentication for 4.1.1 and newer servers.
2541 *
2542 * @param packLength
2543 * @param serverCapabilities DOCUMENT ME!
2544 * @param clientParam
2545 * @param user
2546 * @param password
2547 * @param database DOCUMENT ME!
2548 *
2549 * @throws SQLException
2550 */
2551 private void secureAuth411(int packLength, int serverCapabilities,
2552 long clientParam, String user, String password, String database)
2553 throws SQLException {
2554 // SERVER: public_seed=create_random_string()
2555 // send(public_seed)
2556 //
2557 // CLIENT: recv(public_seed)
2558 // hash_stage1=sha1("password")
2559 // hash_stage2=sha1(hash_stage1)
2560 // reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
2561 //
2562 // // this three steps are done in scramble()
2563 //
2564 // send(reply)
2565 //
2566 //
2567 // SERVER: recv(reply)
2568 // hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
2569 // candidate_hash2=sha1(hash_stage1)
2570 // check(candidate_hash2==hash_stage2)
2571 // Passwords can be 16 chars long
2572 Buffer packet = new Buffer(packLength);
2573
2574 if (this.use41Extensions) {
2575 if (versionMeetsMinimum(4, 1, 1)) {
2576 packet.writeLong(this.clientParam);
2577 packet.writeLong(this.maxThreeBytes);
2578
2579 // charset, JDBC will connect as 'latin1',
2580 // and use 'SET NAMES' to change to the desired
2581 // charset after the connection is established.
2582 packet.writeByte((byte) 8);
2583
2584 // Set of bytes reserved for future use.
2585 packet.writeBytesNoNull(new byte[23]);
2586 } else {
2587 packet.writeLong(this.clientParam);
2588 packet.writeLong(this.maxThreeBytes);
2589 }
2590 } else {
2591 packet.writeInt((int) this.clientParam);
2592 packet.writeLongInt(this.maxThreeBytes);
2593 }
2594
2595 // User/Password data
2596 packet.writeString(user);
2597
2598 if (password.length() != 0) {
2599 packet.writeByte((byte) 0x14);
2600
2601 try {
2602 packet.writeBytesNoNull(Security.scramble411(password, this.seed));
2603 } catch (NoSuchAlgorithmException nse) {
2604 throw new SQLException(
2605 "Failed to create message digest 'SHA-1' for authentication. "
2606 + " You must use a JDK that supports JCE to be able to use secure connection authentication",
2607 SQLError.SQL_STATE_GENERAL_ERROR);
2608 }
2609 } else {
2610 /* For empty password*/
2611 packet.writeByte((byte) 0);
2612 }
2613
2614 if (((serverCapabilities & CLIENT_CONNECT_WITH_DB) != 0)
2615 && (database != null) && (database.length() > 0)) {
2616 packet.writeString(database);
2617 }
2618
2619 send(packet);
2620
2621 byte savePacketSequence = this.packetSequence++;
2622
2623 Buffer reply = checkErrorPacket();
2624
2625 if (reply.isLastDataPacket()) {
2626 /*
2627 By sending this very specific reply server asks us to send scrambled
2628 password in old format. The reply contains scramble_323.
2629 */
2630 this.packetSequence = ++savePacketSequence;
2631 packet.clear();
2632
2633 String seed323 = this.seed.substring(0, 8);
2634 packet.writeString(Util.newCrypt(password, seed323));
2635 send(packet);
2636
2637 /* Read what server thinks about out new auth message report */
2638 checkErrorPacket();
2639 }
2640 }
2641}