~team-java-connector/mariadb-java-client/mariadb_java_connector_1.1.7

« back to all changes in this revision

Viewing changes to src/main/java/org/mariadb/jdbc/internal/common/Utils.java

  • Committer: Puneet Dewan
  • Date: 2014-05-22 21:11:43 UTC
  • Revision ID: puneetd30@gmail.com-20140522211143-fk6no51e4berop92
Original code version 1.1.7

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
MariaDB Client for Java
 
3
 
 
4
Copyright (c) 2012 Monty Program Ab.
 
5
 
 
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)
 
9
any later version.
 
10
 
 
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
 
14
for more details.
 
15
 
 
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.
 
18
 
 
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:
 
22
 
 
23
 
 
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.
 
29
 
 
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.
 
33
 
 
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.
 
37
 
 
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
 
47
OF SUCH DAMAGE.
 
48
*/
 
49
package org.mariadb.jdbc.internal.common;
 
50
 
 
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;
 
56
 
 
57
 
 
58
public class Utils {
 
59
 
 
60
 
 
61
    
 
62
 
 
63
    enum LexState
 
64
    {
 
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 */
 
71
    };
 
72
 
 
73
 
 
74
     public static List<String> createQueryParts(String queryString, boolean noBackslashEscapes)
 
75
     {
 
76
          List<String> list = new ArrayList<String>();
 
77
          LexState state = LexState.Normal;
 
78
          char lastChar= '\0';
 
79
 
 
80
          StringBuffer sb = new StringBuffer();
 
81
 
 
82
          boolean singleQuotes = false;
 
83
          boolean isParam = false;
 
84
 
 
85
          char[] query = queryString.toCharArray();
 
86
 
 
87
          for (int i = 0; i < query.length; i++)  {
 
88
              if (state == LexState.Escape) {
 
89
                  sb.append(query[i]);
 
90
                  state = LexState.String;
 
91
                  continue;
 
92
              }
 
93
 
 
94
              char c = query[i];
 
95
              switch (c)  {
 
96
                  case '*':
 
97
                      if (state == LexState.Normal && lastChar == '/')
 
98
                          state = LexState.SlashStarComment;
 
99
                      break;
 
100
                  case '/':
 
101
                      if (state == LexState.SlashStarComment && lastChar == '*')
 
102
                          state = LexState.Normal;
 
103
                      else if (state == LexState.Normal && lastChar == '/')
 
104
                          state = LexState.EOLComment;
 
105
                      break;
 
106
 
 
107
                  case '#':
 
108
                      if (state == LexState.Normal)
 
109
                          state = LexState.EOLComment;
 
110
                      break;
 
111
 
 
112
                  case '-':
 
113
                      if (state == LexState.Normal && lastChar == '-')
 
114
                          state = LexState.EOLComment;
 
115
                      break;
 
116
 
 
117
                  case '\n':
 
118
                      if (state == LexState.EOLComment)
 
119
                          state = LexState.Normal;
 
120
                      break;
 
121
 
 
122
                  case '"':
 
123
                      if (state == LexState.Normal) {
 
124
                          state = LexState.String;
 
125
                          singleQuotes = false;
 
126
                      }
 
127
                      else if (state == LexState.String && !singleQuotes)
 
128
                          state = LexState.Normal;
 
129
                      break;
 
130
 
 
131
                  case '\'':
 
132
                      if (state == LexState.Normal) {
 
133
                          state = LexState.String;
 
134
                          singleQuotes = true;
 
135
                      }
 
136
                      else if (state == LexState.String && singleQuotes)
 
137
                          state = LexState.Normal;
 
138
                      break;
 
139
 
 
140
                  case '\\':
 
141
                      if (noBackslashEscapes)
 
142
                          break;
 
143
                      if (state == LexState.String)
 
144
                          state = LexState.Escape;
 
145
                      break;
 
146
 
 
147
                  case '?':
 
148
                      if (state == LexState.Normal)
 
149
                          isParam = true;
 
150
                      break;
 
151
              }
 
152
              lastChar = c;
 
153
              if (isParam)  {
 
154
                  list.add(sb.toString());
 
155
                  sb.setLength(0);
 
156
                  isParam = false;
 
157
              }
 
158
              else  {
 
159
                  sb.append(c);
 
160
              }
 
161
 
 
162
          }
 
163
          list.add(sb.toString());
 
164
          return list;
 
165
     }
 
166
 
 
167
 
 
168
    public static String escapeString(String s, boolean noBackslashEscapes) {
 
169
        if (s.indexOf("'") == -1) {
 
170
                if (noBackslashEscapes)
 
171
                        return s;
 
172
                if (s.indexOf("\\") == -1)
 
173
                        return s;
 
174
        }
 
175
        String escaped = s.replace("'", "''");
 
176
        if(noBackslashEscapes)
 
177
                return escaped;
 
178
        return escaped.replace("\\","\\\\");
 
179
    }
 
180
     
 
181
    /**
 
182
     * encrypts a password
 
183
     * <p/>
 
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
 
188
     *
 
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
 
193
     */
 
194
    public static byte[] encryptPassword(final String password, final byte[] seed) throws NoSuchAlgorithmException {
 
195
        if (password == null || password.equals("")) {
 
196
            return new byte[0];
 
197
        }
 
198
 
 
199
        final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
 
200
        final byte[] stage1 = messageDigest.digest(password.getBytes());
 
201
        messageDigest.reset();
 
202
 
 
203
        final byte[] stage2 = messageDigest.digest(stage1);
 
204
        messageDigest.reset();
 
205
 
 
206
        messageDigest.update(seed);
 
207
        messageDigest.update(stage2);
 
208
 
 
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]);
 
213
        }
 
214
        return returnBytes;
 
215
    }
 
216
 
 
217
 
 
218
 
 
219
    /**
 
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.
 
224
     *
 
225
     * @param orig the original byte array
 
226
     * @param length how big the resulting byte array will be
 
227
     * @return the copied byte array
 
228
     */
 
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);
 
234
        return result;
 
235
    }
 
236
 
 
237
    /**
 
238
     * Copies from original byte array to a new byte array. The resulting byte array is
 
239
     * always "to-from" size.
 
240
     *
 
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
 
246
     */
 
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);
 
252
        return result;
 
253
    }
 
254
 
 
255
 
 
256
 
 
257
    /**
 
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
 
265
     */
 
266
    public static String replaceFunctionParameter(String s)  {
 
267
 
 
268
        if (!s.contains("SQL_"))
 
269
            return s;
 
270
 
 
271
        char[] input = s.toCharArray();
 
272
        StringBuffer sb = new StringBuffer();
 
273
        int i;
 
274
        for (i = 0; i < input.length; i++)  {
 
275
            if (input[i] != ' ')
 
276
                break;
 
277
        }
 
278
 
 
279
        for (; ((input[i] >= 'a' && i <= 'z') || (input[i] >= 'A' && input[i] <= 'Z')) && i < input.length; i++)                {
 
280
            sb.append(input[i]);
 
281
        }
 
282
        String func = sb.toString().toLowerCase();
 
283
 
 
284
        if ( func.equals("convert") || func.equals("timestampdiff") || func.equals("timestampadd")) {
 
285
            String paramPrefix;
 
286
 
 
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] != '(')
 
291
                        break;
 
292
                }
 
293
                if (i == input.length)
 
294
                    return new String(input);
 
295
 
 
296
 
 
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);
 
303
            }
 
304
 
 
305
            // Handle "convert(value, type)" case
 
306
            // extract last parameter, after the last ','
 
307
            int lastCommaIndex = s.lastIndexOf(',');
 
308
 
 
309
            for (i = lastCommaIndex + 1; i < input.length; i++) {
 
310
                if (!Character.isWhitespace(input[i]))
 
311
                  break;
 
312
            }
 
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));
 
318
 
 
319
        }
 
320
        return new String(input);
 
321
     }
 
322
 
 
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);
 
330
        }
 
331
        else if(escaped.startsWith("{oj ")) {
 
332
            // Outer join
 
333
            return nativeSQL(escaped.substring(4, endIndex), noBackslashEscapes);
 
334
        }
 
335
        else if(escaped.startsWith("{d "))  {
 
336
            // date literal
 
337
            return escaped.substring(3, endIndex);
 
338
        }
 
339
        else if(escaped.startsWith("{t ")) {
 
340
            // time literal
 
341
            return escaped.substring(3, endIndex);
 
342
        }
 
343
        else if (escaped.startsWith("{ts ")) {
 
344
            //timestamp literal
 
345
            return escaped.substring(4, endIndex);
 
346
        }
 
347
        else if(escaped.startsWith("{d'"))  {
 
348
            // date literal, no space 
 
349
            return escaped.substring(2, endIndex);
 
350
        }
 
351
        else if(escaped.startsWith("{t'")) {
 
352
            // time literal
 
353
            return escaped.substring(2, endIndex);
 
354
        }
 
355
        else if (escaped.startsWith("{ts'")) {
 
356
            //timestamp literal
 
357
            return escaped.substring(3, endIndex);
 
358
        }
 
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.
 
361
 
 
362
            return  nativeSQL(escaped.substring(1, endIndex), noBackslashEscapes);
 
363
        }
 
364
        else if (escaped.startsWith("{escape ")) {
 
365
            return  escaped.substring(1, endIndex);
 
366
        }
 
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);
 
376
                }
 
377
            }
 
378
        }
 
379
        throw new SQLException("unknown escape sequence " + escaped);
 
380
    }
 
381
 
 
382
 
 
383
    public static String nativeSQL(String sql, boolean noBackslashEscapes) throws SQLException{
 
384
        if (sql.indexOf('{') == -1)
 
385
            return sql;
 
386
 
 
387
        StringBuffer escapeSequenceBuf = new StringBuffer();
 
388
        StringBuffer sqlBuffer = new StringBuffer();
 
389
 
 
390
        char [] a = sql.toCharArray();
 
391
        char lastChar = 0;
 
392
        boolean inQuote = false;
 
393
        char quoteChar = 0;
 
394
        boolean inComment = false;
 
395
        boolean isSlashSlashComment = false;
 
396
        int inEscapeSeq  = 0;
 
397
 
 
398
        for(int i = 0 ; i< a.length; i++) {
 
399
            char c = a[i];
 
400
            if (lastChar == '\\' && !noBackslashEscapes) {
 
401
                sqlBuffer.append(c);
 
402
                continue;
 
403
            }
 
404
 
 
405
            switch(c) {
 
406
                case '\'':
 
407
                case '"':
 
408
                    if (!inComment) {
 
409
                        if(inQuote) {
 
410
                            if (quoteChar == c) {
 
411
                                inQuote = false;
 
412
                            }
 
413
                        } else {
 
414
                            inQuote = true;
 
415
                            quoteChar = c;
 
416
                        }
 
417
                    }
 
418
                    break;
 
419
 
 
420
                case '*':
 
421
                    if (!inQuote && !inComment) {
 
422
                        if(lastChar == '/') {
 
423
                            inComment = true;
 
424
                            isSlashSlashComment = false;
 
425
                        }
 
426
                    }
 
427
                    break;
 
428
                case '/':
 
429
                case '-':
 
430
                    if (!inQuote) {
 
431
                        if (inComment) {
 
432
                            if (lastChar == '*' && !isSlashSlashComment) {
 
433
                                inComment = false;
 
434
                            }
 
435
                            else if (lastChar == c && isSlashSlashComment) {
 
436
                                inComment = false;
 
437
                            }
 
438
                        }
 
439
                        else {
 
440
                            if(lastChar == c) {
 
441
                                inComment = true;
 
442
                                isSlashSlashComment = true;
 
443
                            }
 
444
                            else if (lastChar == '*') {
 
445
                                inComment = true;
 
446
                                isSlashSlashComment = false;
 
447
                            }
 
448
                        }
 
449
                    }
 
450
                    break;
 
451
                case 'S':
 
452
                    // skip SQL_xxx and SQL_TSI_xxx in functions
 
453
                    // This would convert e.g SQL_INTEGER => INTEGER, SQL_TSI_HOUR=>HOUR
 
454
 
 
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] == '_') {
 
458
                               i += 8;
 
459
                               continue;
 
460
                           }
 
461
                           i += 4;
 
462
                           continue;
 
463
                       }
 
464
                    }
 
465
                    break;
 
466
                case '\n':
 
467
                    if (inComment && isSlashSlashComment) {
 
468
                        // slash-slash and dash-dash comments ends with the end of line
 
469
                        inComment = false;
 
470
                    }
 
471
                    break;
 
472
                case '{':
 
473
                    if (!inQuote && ! inComment) {
 
474
                        inEscapeSeq++;
 
475
                    }
 
476
                    break;
 
477
 
 
478
                case '}':
 
479
                    if (!inQuote && ! inComment) {
 
480
                        inEscapeSeq--;
 
481
                        if (inEscapeSeq == 0) {
 
482
                            escapeSequenceBuf.append(c);
 
483
                            sqlBuffer.append(resolveEscapes(escapeSequenceBuf.toString(), noBackslashEscapes));
 
484
                            escapeSequenceBuf.setLength(0);
 
485
                            continue;
 
486
                        }
 
487
                    }
 
488
 
 
489
 
 
490
            }
 
491
            lastChar = c;
 
492
            if(inEscapeSeq > 0) {
 
493
                escapeSequenceBuf.append(c);
 
494
            } else {
 
495
                sqlBuffer.append(c);
 
496
            }
 
497
        }
 
498
        if (inEscapeSeq > 0)
 
499
            throw new SQLException("Invalid escape sequence , missing closing '}' character in '" + sqlBuffer);
 
500
        return sqlBuffer.toString();
 
501
    }
 
502
}
 
 
b'\\ No newline at end of file'