1 //
2 // Copyright 1998 CDS Networks, Inc., Medford Oregon
3 //
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 1. Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // 3. All advertising materials mentioning features or use of this software
14 // must display the following acknowledgement:
15 // This product includes software developed by CDS Networks, Inc.
16 // 4. The name of CDS Networks, Inc. may not be used to endorse or promote
17 // products derived from this software without specific prior
18 // written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY CDS NETWORKS, INC. ``AS IS'' AND
21 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 // ARE DISCLAIMED. IN NO EVENT SHALL CDS NETWORKS, INC. BE LIABLE
24 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 // SUCH DAMAGE.
31 //
32
33
34 package com.internetcds.jdbc.tds;
35
36 import java.sql.*;
37
38
39 abstract public class EscapeProcessor
40 {
41 public static final String cvsVersion = "$Id: EscapeProcessor.html,v 1.1 2003/05/12 16:19:44 sinisa Exp $";
42
43 String input;
44
45 public EscapeProcessor(String sql)
46 {
47 input = sql;
48 } // EscapeProcessor()
49
50 abstract public String expandDBSpecificFunction(String escapeSequence)
51 throws SQLException;
52
53 /***
54 * is the string made up only of digits?
55 * <p>
56 * Note- Leading/trailing spaces or signs are not considered digits.
57 *
58 * @return true if the string has only digits, false otherwise.
59 */
60 private static boolean validDigits(String str)
61 {
62 boolean result = true;
63 int i;
64
65 for(i=0, result = true; result && i<str.length(); i++)
66 {
67 result = result && Character.isDigit(str.charAt(i));
68 }
69 return result;
70 } // validDigits()
71
72 /***
73 * Given a string and an index into that string return the index
74 * of the next non-whitespace character.
75 *
76 * @return index of next non-whitespace character.
77 */
78 private static int skipWhitespace(String str, int i)
79 {
80 while(i<str.length() && Character.isWhitespace(str.charAt(i)))
81 {
82 i++;
83 }
84 return i;
85 } // skipWhitespace()
86
87
88 /***
89 * Given a string and an index into that string, advanvance the index
90 * iff it is on a quote character.
91 *
92 * @return
93 */
94 private static int skipQuote(String str, int i)
95 {
96
97 // skip over the leading quote if it exists
98 if (i<str.length() && (str.charAt(i)=='\'' || str.charAt(i)=='"'))
99 {
100 // XXX Note- The spec appears to prohibit the quote character,
101 // but many drivers allow it. We should probably control this
102 // with a flag.
103 i++;
104 }
105 return i;
106 } // skipQuote()
107
108 /***
109 * Convert a JDBC SQL escape date sequence into a datestring recognized
110 * by SQLServer.
111 *
112 */
113 private static String getDate(String str)
114 throws SQLException
115 {
116 int i;
117
118 // skip over the "d "
119 i = 2;
120
121 // skip any additional spaces
122 i = skipWhitespace(str, i);
123
124 i = skipQuote(str, i);
125
126 // i is now up to the point where the date had better start.
127 if (((str.length()-i) < 10)
128 || str.charAt(i+4)!='-' || str.charAt(i+7)!='-')
129 {
130 throw new SQLException("Malformed date");
131 }
132
133 String year = str.substring(i, i+4);
134 String month = str.substring(i+5, i+5+2);
135 String day = str.substring(i+5+3, i+5+3+2);
136
137 // Make sure the year, month, and day are numeric
138 if (!validDigits(year) || !validDigits(month) || !validDigits(day))
139 {
140 throw new SQLException("Malformed date");
141 }
142
143 // Make sure there isn't any garbage after the date
144 i = i+10;
145 i = skipWhitespace(str, i);
146 i = skipQuote(str, i);
147 i = skipWhitespace(str, i);
148
149 if (i<str.length())
150 {
151 throw new SQLException("Malformed date");
152 }
153
154 return "'" + year + month + day + "'";
155 } // getDate()
156
157
158 /***
159 * Convert a JDBC SQL escape time sequence into a time string recognized
160 * by SQLServer.
161 *
162 */
163 private static String getTime(String str)
164 throws SQLException
165 {
166 int i;
167
168 // skip over the "t "
169 i = 2;
170
171 // skip any additional spaces
172 i = skipWhitespace(str, i);
173
174 i = skipQuote(str, i);
175
176 // i is now up to the point where the date had better start.
177 if (((str.length()-i) < 8)
178 || str.charAt(i+2)!=':' || str.charAt(i+5)!=':')
179 {
180 throw new SQLException("Malformed time");
181 }
182
183 String hour = str.substring(i, i+2);
184 String minute = str.substring(i+3, i+3+2);
185 String second = str.substring(i+3+3, i+3+3+2);
186
187 // Make sure the year, month, and day are numeric
188 if (!validDigits(hour) || !validDigits(minute) || !validDigits(second))
189 {
190 throw new SQLException("Malformed time");
191 }
192
193 // Make sure there isn't any garbage after the time
194 i = i+8;
195 i = skipWhitespace(str, i);
196 i = skipQuote(str, i);
197 i = skipWhitespace(str, i);
198
199 if (i<str.length())
200 {
201 throw new SQLException("Malformed time");
202 }
203
204 return "'" + hour + ":" + minute + ":" + second + "'";
205 } // getTime()
206
207
208 /***
209 * Convert a JDBC SQL escape timestamp sequence into a date-time string
210 * by SQLServer.
211 *
212 */
213 private static String getTimestamp(String str)
214 throws SQLException
215 {
216 int i;
217
218 // skip over the "d "
219 i = 2;
220
221 // skip any additional spaces
222 i = skipWhitespace(str, i);
223
224 i = skipQuote(str, i);
225
226 // i is now up to the point where the date had better start.
227 if (((str.length()-i) < 19)
228 || str.charAt(i+4)!='-' || str.charAt(i+7)!='-')
229 {
230 throw new SQLException("Malformed date");
231 }
232
233 String year = str.substring(i, i+4);
234 String month = str.substring(i+5, i+5+2);
235 String day = str.substring(i+5+3, i+5+3+2);
236
237 // Make sure the year, month, and day are numeric
238 if (!validDigits(year) || !validDigits(month) || !validDigits(day))
239 {
240 throw new SQLException("Malformed date");
241 }
242
243 // Make sure there is at least one space between date and time
244 i = i+10;
245 if (!Character.isWhitespace(str.charAt(i)))
246 {
247 throw new SQLException("Malformed date");
248 }
249
250 // skip the whitespace
251 i = skipWhitespace(str, i);
252
253 // see if it could be a time
254 if (((str.length()-i) < 8)
255 || str.charAt(i+2)!=':' || str.charAt(i+5)!=':')
256 {
257 throw new SQLException("Malformed time");
258 }
259 String hour = str.substring(i, i+2);
260 String minute = str.substring(i+3, i+3+2);
261 String second = str.substring(i+3+3, i+3+3+2);
262 String fraction = "000";
263 i = i+8;
264 if (str.length()>i && str.charAt(i)=='.')
265 {
266 fraction = "";
267 i++;
268 while(str.length()>i && validDigits(str.substring(i,i+1)))
269 {
270 fraction = fraction + str.substring(i,i+1);
271 i++;
272 }
273 if (fraction.length()>3)
274 {
275 fraction = fraction.substring(0,3);
276 }
277 else
278 {
279 while(fraction.length()<3)
280 {
281 fraction = fraction + "0";
282 }
283 }
284 }
285
286
287 // Make sure there isn't any garbage after the time
288 i = skipWhitespace(str, i);
289 i = skipQuote(str, i);
290 i = skipWhitespace(str, i);
291
292 if (i<str.length())
293 {
294 throw new SQLException("Malformed date");
295 }
296
297 return ("'" + year + month + day + " "
298 + hour + ":" + minute + ":" + second + "." + fraction + "'");
299 } // getTimestamp()
300
301
302 public String expandEscape(String escapeSequence)
303 throws SQLException
304 {
305 String str = new String(escapeSequence);
306 String result = null;
307
308 // XXX Is it always okay to trim leading and trailing blanks?
309 str = str.trim();
310
311 if (str.startsWith("fn "))
312 {
313 str = str.substring(3);
314
315 result = expandCommonFunction(str);
316 if (result == null)
317 {
318 result = expandDBSpecificFunction(str);
319 }
320 }
321 else if (str.startsWith("call ")
322 || (str.startsWith("?=")
323 && str.substring(2).trim().startsWith("call ")))
324 {
325 throw new SQLException("Not implemented yet");
326 }
327 else if (str.startsWith("d "))
328 {
329 result = getDate(str);
330 }
331 else if (str.startsWith("t "))
332 {
333 result = getTime(str);
334 }
335 else if (str.startsWith("ts "))
336 {
337 result = getTimestamp(str);
338 }
339 else if (str.startsWith("oj "))
340 {
341 throw new SQLException("Not implemented yet");
342 }
343 else
344 {
345 throw new SQLException("Unrecognized escape sequence-\n" +
346 escapeSequence);
347 }
348
349 return result;
350 } // expandEscape()
351
352
353 /***
354 * Expand functions that are common to both SQLServer and Sybase
355 *
356 */
357 public String expandCommonFunction(String str)
358 {
359 String result = null;
360
361
362 if (str.equalsIgnoreCase("user()"))
363 {
364 result = " user_name() ";
365 }
366 else if (str.equalsIgnoreCase("now()"))
367 {
368 result = " getdate() ";
369 }
370 return result;
371 } // expandCommonFunction()
372
373
374 public String nativeString()
375 throws SQLException
376 {
377 return nativeString(input, '//');
378 } // nativeString()
379
380 private String nativeString(String sql, char escapeCharacter)
381 throws SQLException
382 {
383 String result = "";
384
385 String escape = "";
386 int i;
387
388
389 // Simple finite state machine. Bonehead, but it works.
390 final int normal = 0;
391
392 final int inString = 1;
393 final int inStringWithBackquote = 2;
394
395 final int inEscape = 3;
396 final int inEscapeInString = 4;
397 final int inEscapeInStringWithBackquote = 5;
398
399 int state = normal;
400 char ch;
401
402
403 int escapeStartedAt = -1;
404 i = 0;
405 while(i<sql.length())
406 {
407 ch = sql.charAt(i);
408 switch(state)
409 {
410 case normal:
411 {
412 if (ch == '{')
413 {
414 escapeStartedAt = i;
415 state = inEscape;
416 escape = "";
417 }
418 else
419 {
420 result = result + ch;
421
422 if (ch == '\'') state = inString;
423 }
424 break;
425 }
426 case inString:
427 case inStringWithBackquote:
428 {
429 if ((i+1)<sql.length()
430 && ch == escapeCharacter
431 && (sql.charAt(i+1)=='_'
432 || sql.charAt(i+1)=='%'))
433 {
434 i++;
435 ch = sql.charAt(i);
436 result = result + '//' + ch;
437 }
438 else
439 {
440 result = result + ch;
441 if (state == inStringWithBackquote)
442 {
443 state = inString;
444 }
445 else
446 {
447 if (ch == '//') state = inStringWithBackquote;
448 if (ch == '\'') state = normal;
449 }
450 }
451 break;
452 }
453 case inEscape:
454 {
455 if (ch == '}')
456 {
457 // At this point there are a couple of things to
458 // consider. First, if the escape is of the form
459 // "{escape 'c'} but it is not at the end of the SQL
460 // we consider that a malformed SQL string. If it
461 // is the "{escape 'c'}" clause and it is at the end
462 // of the string then we have to go through and
463 // reparse this whole thing again, this time with an
464 // escape character. Any other escape is handled in
465 // the expandEscape method()
466
467 if (escape.startsWith("escape "))
468 {
469 char c;
470
471 // make sure it is the last thing in the sql
472 if (i+1!=sql.length())
473 {
474 throw new SQLException("Malformed statement. " +
475 "escape clause must be at " +
476 "the end of the query");
477 }
478
479
480 // parse the sql again, this time without the
481 // ending string but with the escape character
482 // set
483
484 c = findEscapeCharacter(sql.substring(escapeStartedAt));
485
486 result = nativeString(sql.substring(0, escapeStartedAt),
487 c);
488 state = normal;
489 }
490 else
491 {
492 state = normal;
493 result = result + expandEscape(escape);
494 escapeStartedAt = -1;
495 }
496 }
497 else
498 {
499 escape = escape + ch;
500 if (ch == '\'')
501 {
502 state = inEscapeInString;
503 }
504 }
505 break;
506 }
507 case inEscapeInString:
508 case inEscapeInStringWithBackquote:
509 {
510 escape = escape + ch;
511 if (state == inEscapeInStringWithBackquote)
512 {
513 state = inEscapeInString;
514 }
515 else
516 {
517 if (ch == '//') state = inEscapeInStringWithBackquote;
518 if (ch == '\'') state = inEscape;
519 }
520 break;
521 }
522 default:
523 {
524 throw new SQLException("Internal error. Unknown state in FSM");
525 }
526 }
527 i++;
528 }
529
530 if (state!=normal && state!=inString)
531 {
532 throw new SQLException("Syntax error in SQL escape syntax");
533 }
534 return result;
535 } // nativeString()
536
537 static char findEscapeCharacter(String original_str)
538 throws SQLException
539 {
540 String str = new String(original_str);
541
542 str = str.trim();
543 if (str.charAt(0)!='{' || str.charAt(str.length()-1)!='}'
544 || str.length()<12)
545 {
546 throw new SQLException("Internal Error");
547 }
548
549 str = str.substring(1, str.length()-1);
550 str = str.trim();
551
552 if (! str.startsWith("escape"))
553 {
554 throw new SQLException("Internal Error");
555 }
556
557 str = str.substring(6);
558 str = str.trim();
559 if (str.length()!=3 || str.charAt(0)!='\'' || str.charAt(2)!='\'')
560 {
561 throw new SQLException("Malformed escape clause- |" +
562 original_str + "|");
563 }
564
565 return str.charAt(1);
566 } // findEscapeCharacter()
567 }
This page automatically generated by Maven