2
MariaDB Client for Java
4
Copyright (c) 2012 Monty Program Ab.
6
This library is free software; you can redistribute it and/or modify it under
7
the terms of the GNU Lesser General Public License as published by the Free
8
Software Foundation; either version 2.1 of the License, or (at your option)
11
This library is distributed in the hope that it will be useful, but
12
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16
You should have received a copy of the GNU Lesser General Public License along
17
with this library; if not, write to Monty Program Ab info@montyprogram.com.
19
This particular MariaDB Client for Java file is work
20
derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
21
the following copyright and notice provisions:
24
Copyright (c) 2009-2011, Marcus Eriksson, Jay Pipes
25
Redistribution and use in source and binary forms, with or without modification,
26
are permitted provided that the following conditions are met:
27
Redistributions of source code must retain the above copyright notice, this list
28
of conditions and the following disclaimer.
30
Redistributions in binary form must reproduce the above copyright notice, this
31
list of conditions and the following disclaimer in the documentation and/or
32
other materials provided with the distribution.
34
Neither the name of the driver nor the names of its contributors may not be
35
used to endorse or promote products derived from this software without specific
36
prior written permission.
38
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
39
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
40
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
41
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
42
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
43
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
45
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
49
package org.mariadb.jdbc.internal.common;
51
import java.security.MessageDigest;
52
import java.security.NoSuchAlgorithmException;
53
import java.sql.SQLException;
54
import java.util.ArrayList;
55
import java.util.List;
65
Normal, /* inside query */
66
String, /* inside string */
67
SlashStarComment, /* inside slash-star comment */
68
Escape, /* found backslash */
69
Parameter, /* parameter placeholder found */
70
EOLComment /* # comment, or // comment, or -- comment */
74
public static List<String> createQueryParts(String queryString, boolean noBackslashEscapes)
76
List<String> list = new ArrayList<String>();
77
LexState state = LexState.Normal;
80
StringBuffer sb = new StringBuffer();
82
boolean singleQuotes = false;
83
boolean isParam = false;
85
char[] query = queryString.toCharArray();
87
for (int i = 0; i < query.length; i++) {
88
if (state == LexState.Escape) {
90
state = LexState.String;
97
if (state == LexState.Normal && lastChar == '/')
98
state = LexState.SlashStarComment;
101
if (state == LexState.SlashStarComment && lastChar == '*')
102
state = LexState.Normal;
103
else if (state == LexState.Normal && lastChar == '/')
104
state = LexState.EOLComment;
108
if (state == LexState.Normal)
109
state = LexState.EOLComment;
113
if (state == LexState.Normal && lastChar == '-')
114
state = LexState.EOLComment;
118
if (state == LexState.EOLComment)
119
state = LexState.Normal;
123
if (state == LexState.Normal) {
124
state = LexState.String;
125
singleQuotes = false;
127
else if (state == LexState.String && !singleQuotes)
128
state = LexState.Normal;
132
if (state == LexState.Normal) {
133
state = LexState.String;
136
else if (state == LexState.String && singleQuotes)
137
state = LexState.Normal;
141
if (noBackslashEscapes)
143
if (state == LexState.String)
144
state = LexState.Escape;
148
if (state == LexState.Normal)
154
list.add(sb.toString());
163
list.add(sb.toString());
168
public static String escapeString(String s, boolean noBackslashEscapes) {
169
if (s.indexOf("'") == -1) {
170
if (noBackslashEscapes)
172
if (s.indexOf("\\") == -1)
175
String escaped = s.replace("'", "''");
176
if(noBackslashEscapes)
178
return escaped.replace("\\","\\\\");
182
* encrypts a password
184
* protocol for authentication is like this: 1. mysql server sends a random array of bytes (the seed) 2. client
185
* makes a sha1 digest of the password 3. client hashes the output of 2 4. client digests the seed 5. client updates
186
* the digest with the output from 3 6. an xor of the output of 5 and 2 is sent to server 7. server does the same
187
* thing and verifies that the scrambled passwords match
189
* @param password the password to encrypt
190
* @param seed the seed to use
191
* @return a scrambled password
192
* @throws NoSuchAlgorithmException if SHA1 is not available on the platform we are using
194
public static byte[] encryptPassword(final String password, final byte[] seed) throws NoSuchAlgorithmException {
195
if (password == null || password.equals("")) {
199
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
200
final byte[] stage1 = messageDigest.digest(password.getBytes());
201
messageDigest.reset();
203
final byte[] stage2 = messageDigest.digest(stage1);
204
messageDigest.reset();
206
messageDigest.update(seed);
207
messageDigest.update(stage2);
209
final byte[] digest = messageDigest.digest();
210
final byte[] returnBytes = new byte[digest.length];
211
for (int i = 0; i < digest.length; i++) {
212
returnBytes[i] = (byte) (stage1[i] ^ digest[i]);
220
* Copies the original byte array content to a new byte array. The resulting byte array is
221
* always "length" size. If length is smaller than the original byte array, the resulting
222
* byte array is truncated. If length is bigger than the original byte array, the resulting
223
* byte array is filled with zero bytes.
225
* @param orig the original byte array
226
* @param length how big the resulting byte array will be
227
* @return the copied byte array
229
public static byte[] copyWithLength(byte[] orig, int length) {
230
// No need to initialize with zero bytes, because the bytes are already initialized with that
231
byte[] result = new byte[length];
232
int howMuchToCopy = length < orig.length ? length : orig.length;
233
System.arraycopy(orig, 0, result, 0, howMuchToCopy);
238
* Copies from original byte array to a new byte array. The resulting byte array is
239
* always "to-from" size.
241
* @param orig the original byte array
242
* @param from index of first byte in original byte array which will be copied
243
* @param to index of last byte in original byte array which will be copied. This can be
244
* outside of the original byte array
245
* @return resulting array
247
public static byte[] copyRange(byte[] orig, int from, int to) {
248
int length = to - from;
249
byte[] result = new byte[length];
250
int howMuchToCopy = orig.length - from < length ? orig.length - from : length;
251
System.arraycopy(orig, from, result, 0, howMuchToCopy);
258
* Helper function to replace function parameters in escaped string.
259
* 3 functions are handles :
260
* - CONVERT(value, type) , we replace SQL_XXX types with XXX, i.e SQL_INTEGER with INTEGER
261
* - TIMESTAMPDIFF(type, ...) or TIMESTAMPADD(type, ...) , we replace SQL_TSI_XXX in type with XXX, i.e
262
* SQL_TSI_HOUR with HOUR
263
* @param s - input string
264
* @return unescaped string
266
public static String replaceFunctionParameter(String s) {
268
if (!s.contains("SQL_"))
271
char[] input = s.toCharArray();
272
StringBuffer sb = new StringBuffer();
274
for (i = 0; i < input.length; i++) {
279
for (; ((input[i] >= 'a' && i <= 'z') || (input[i] >= 'A' && input[i] <= 'Z')) && i < input.length; i++) {
282
String func = sb.toString().toLowerCase();
284
if ( func.equals("convert") || func.equals("timestampdiff") || func.equals("timestampadd")) {
287
if (func.equals("timestampdiff") || func.equals("timestampadd")) {
288
// Skip to first parameter
289
for (; i < input.length; i++) {
290
if (!Character.isWhitespace(input[i]) && input[i] != '(')
293
if (i == input.length)
294
return new String(input);
297
if (i >= input.length - 8)
298
return new String(input);
299
paramPrefix = new String(input, i, 8);
300
if (paramPrefix.equals("SQL_TSI_"))
301
return new String(input, 0, i) + new String(input, i + 8, input.length - (i+8));
302
return new String(input);
305
// Handle "convert(value, type)" case
306
// extract last parameter, after the last ','
307
int lastCommaIndex = s.lastIndexOf(',');
309
for (i = lastCommaIndex + 1; i < input.length; i++) {
310
if (!Character.isWhitespace(input[i]))
313
if (i>= input.length - 4)
314
return new String(input);
315
paramPrefix = new String(input, i, 4);
316
if (paramPrefix.equals("SQL_"))
317
return new String(input, 0, i) + new String(input, i+4 , input.length - (i+4));
320
return new String(input);
323
private static String resolveEscapes(String escaped, boolean noBackslashEscapes) throws SQLException{
324
if(escaped.charAt(0) != '{' || escaped.charAt(escaped.length()-1) != '}')
325
throw new SQLException("unexpected escaped string");
326
int endIndex = escaped.length()-1;
327
if (escaped.startsWith("{fn ")) {
328
String resolvedParams = replaceFunctionParameter(escaped.substring(4,endIndex));
329
return nativeSQL(resolvedParams, noBackslashEscapes);
331
else if(escaped.startsWith("{oj ")) {
333
return nativeSQL(escaped.substring(4, endIndex), noBackslashEscapes);
335
else if(escaped.startsWith("{d ")) {
337
return escaped.substring(3, endIndex);
339
else if(escaped.startsWith("{t ")) {
341
return escaped.substring(3, endIndex);
343
else if (escaped.startsWith("{ts ")) {
345
return escaped.substring(4, endIndex);
347
else if(escaped.startsWith("{d'")) {
348
// date literal, no space
349
return escaped.substring(2, endIndex);
351
else if(escaped.startsWith("{t'")) {
353
return escaped.substring(2, endIndex);
355
else if (escaped.startsWith("{ts'")) {
357
return escaped.substring(3, endIndex);
359
else if (escaped.startsWith("{call ") || escaped.startsWith("{CALL ")) {
360
// We support uppercase "{CALL" only because Connector/J supports it. It is not in the JDBC spec.
362
return nativeSQL(escaped.substring(1, endIndex), noBackslashEscapes);
364
else if (escaped.startsWith("{escape ")) {
365
return escaped.substring(1, endIndex);
367
else if (escaped.startsWith("{?")) {
368
// likely ?=call(...)
369
return nativeSQL(escaped.substring(1, endIndex), noBackslashEscapes);
370
} else if (escaped.startsWith("{ ")) {
371
// Spaces before keyword, this is not JDBC compliant, however some it works in some drivers,
372
// so we support it, too
373
for(int i = 2; i < escaped.length(); i++) {
374
if (!Character.isWhitespace(escaped.charAt(i))) {
375
return resolveEscapes("{" + escaped.substring(i), noBackslashEscapes);
379
throw new SQLException("unknown escape sequence " + escaped);
383
public static String nativeSQL(String sql, boolean noBackslashEscapes) throws SQLException{
384
if (sql.indexOf('{') == -1)
387
StringBuffer escapeSequenceBuf = new StringBuffer();
388
StringBuffer sqlBuffer = new StringBuffer();
390
char [] a = sql.toCharArray();
392
boolean inQuote = false;
394
boolean inComment = false;
395
boolean isSlashSlashComment = false;
398
for(int i = 0 ; i< a.length; i++) {
400
if (lastChar == '\\' && !noBackslashEscapes) {
410
if (quoteChar == c) {
421
if (!inQuote && !inComment) {
422
if(lastChar == '/') {
424
isSlashSlashComment = false;
432
if (lastChar == '*' && !isSlashSlashComment) {
435
else if (lastChar == c && isSlashSlashComment) {
442
isSlashSlashComment = true;
444
else if (lastChar == '*') {
446
isSlashSlashComment = false;
452
// skip SQL_xxx and SQL_TSI_xxx in functions
453
// This would convert e.g SQL_INTEGER => INTEGER, SQL_TSI_HOUR=>HOUR
455
if (!inQuote && !inComment && inEscapeSeq > 0) {
456
if (i + 4 < a.length && a[i+1] == 'Q' && a[i+2] == 'L' && a[i+3] == 'L' && a[i+4] == '_') {
457
if(i+8 < a.length && a[i+5] == 'T' && a[i+6] == 'S' && a[i+7] == 'I' && a[i+8] == '_') {
467
if (inComment && isSlashSlashComment) {
468
// slash-slash and dash-dash comments ends with the end of line
473
if (!inQuote && ! inComment) {
479
if (!inQuote && ! inComment) {
481
if (inEscapeSeq == 0) {
482
escapeSequenceBuf.append(c);
483
sqlBuffer.append(resolveEscapes(escapeSequenceBuf.toString(), noBackslashEscapes));
484
escapeSequenceBuf.setLength(0);
492
if(inEscapeSeq > 0) {
493
escapeSequenceBuf.append(c);
499
throw new SQLException("Invalid escape sequence , missing closing '}' character in '" + sqlBuffer);
500
return sqlBuffer.toString();
b'\\ No newline at end of file'