1 // ========================================================================
2 // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.util;
15
16 import java.util.NoSuchElementException;
17 import java.util.StringTokenizer;
18
19 /* ------------------------------------------------------------ */
20 /** StringTokenizer with Quoting support.
21 *
22 * This class is a copy of the java.util.StringTokenizer API and
23 * the behaviour is the same, except that single and doulbe quoted
24 * string values are recognized.
25 * Delimiters within quotes are not considered delimiters.
26 * Quotes can be escaped with '\'.
27 *
28 * @see java.util.StringTokenizer
29 *
30 */
31 public class QuotedStringTokenizer
32 extends StringTokenizer
33 {
34 private final static String __delim="\t\n\r";
35 private String _string;
36 private String _delim = __delim;
37 private boolean _returnQuotes=false;
38 private boolean _returnDelimiters=false;
39 private StringBuffer _token;
40 private boolean _hasToken=false;
41 private int _i=0;
42 private int _lastStart=0;
43 private boolean _double=true;
44 private boolean _single=true;
45
46 /* ------------------------------------------------------------ */
47 public QuotedStringTokenizer(String str,
48 String delim,
49 boolean returnDelimiters,
50 boolean returnQuotes)
51 {
52 super("");
53 _string=str;
54 if (delim!=null)
55 _delim=delim;
56 _returnDelimiters=returnDelimiters;
57 _returnQuotes=returnQuotes;
58
59 if (_delim.indexOf('\'')>=0 ||
60 _delim.indexOf('"')>=0)
61 throw new Error("Can't use quotes as delimiters: "+_delim);
62
63 _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
64 }
65
66 /* ------------------------------------------------------------ */
67 public QuotedStringTokenizer(String str,
68 String delim,
69 boolean returnDelimiters)
70 {
71 this(str,delim,returnDelimiters,false);
72 }
73
74 /* ------------------------------------------------------------ */
75 public QuotedStringTokenizer(String str,
76 String delim)
77 {
78 this(str,delim,false,false);
79 }
80
81 /* ------------------------------------------------------------ */
82 public QuotedStringTokenizer(String str)
83 {
84 this(str,null,false,false);
85 }
86
87 /* ------------------------------------------------------------ */
88 public boolean hasMoreTokens()
89 {
90 // Already found a token
91 if (_hasToken)
92 return true;
93
94 _lastStart=_i;
95
96 int state=0;
97 boolean escape=false;
98 while (_i<_string.length())
99 {
100 char c=_string.charAt(_i++);
101
102 switch (state)
103 {
104 case 0: // Start
105 if(_delim.indexOf(c)>=0)
106 {
107 if (_returnDelimiters)
108 {
109 _token.append(c);
110 return _hasToken=true;
111 }
112 }
113 else if (c=='\'' && _single)
114 {
115 if (_returnQuotes)
116 _token.append(c);
117 state=2;
118 }
119 else if (c=='\"' && _double)
120 {
121 if (_returnQuotes)
122 _token.append(c);
123 state=3;
124 }
125 else
126 {
127 _token.append(c);
128 _hasToken=true;
129 state=1;
130 }
131 continue;
132
133 case 1: // Token
134 _hasToken=true;
135 if(_delim.indexOf(c)>=0)
136 {
137 if (_returnDelimiters)
138 _i--;
139 return _hasToken;
140 }
141 else if (c=='\'' && _single)
142 {
143 if (_returnQuotes)
144 _token.append(c);
145 state=2;
146 }
147 else if (c=='\"' && _double)
148 {
149 if (_returnQuotes)
150 _token.append(c);
151 state=3;
152 }
153 else
154 _token.append(c);
155 continue;
156
157
158 case 2: // Single Quote
159 _hasToken=true;
160 if (escape)
161 {
162 escape=false;
163 _token.append(c);
164 }
165 else if (c=='\'')
166 {
167 if (_returnQuotes)
168 _token.append(c);
169 state=1;
170 }
171 else if (c=='\\')
172 {
173 if (_returnQuotes)
174 _token.append(c);
175 escape=true;
176 }
177 else
178 _token.append(c);
179 continue;
180
181
182 case 3: // Double Quote
183 _hasToken=true;
184 if (escape)
185 {
186 escape=false;
187 _token.append(c);
188 }
189 else if (c=='\"')
190 {
191 if (_returnQuotes)
192 _token.append(c);
193 state=1;
194 }
195 else if (c=='\\')
196 {
197 if (_returnQuotes)
198 _token.append(c);
199 escape=true;
200 }
201 else
202 _token.append(c);
203 continue;
204 }
205 }
206
207 return _hasToken;
208 }
209
210 /* ------------------------------------------------------------ */
211 public String nextToken()
212 throws NoSuchElementException
213 {
214 if (!hasMoreTokens() || _token==null)
215 throw new NoSuchElementException();
216 String t=_token.toString();
217 _token.setLength(0);
218 _hasToken=false;
219 return t;
220 }
221
222 /* ------------------------------------------------------------ */
223 public String nextToken(String delim)
224 throws NoSuchElementException
225 {
226 _delim=delim;
227 _i=_lastStart;
228 _token.setLength(0);
229 _hasToken=false;
230 return nextToken();
231 }
232
233 /* ------------------------------------------------------------ */
234 public boolean hasMoreElements()
235 {
236 return hasMoreTokens();
237 }
238
239 /* ------------------------------------------------------------ */
240 public Object nextElement()
241 throws NoSuchElementException
242 {
243 return nextToken();
244 }
245
246 /* ------------------------------------------------------------ */
247 /** Not implemented.
248 */
249 public int countTokens()
250 {
251 return -1;
252 }
253
254
255 /* ------------------------------------------------------------ */
256 /** Quote a string.
257 * The string is quoted only if quoting is required due to
258 * embeded delimiters, quote characters or the
259 * empty string.
260 * @param s The string to quote.
261 * @return quoted string
262 */
263 public static String quote(String s, String delim)
264 {
265 if (s==null)
266 return null;
267 if (s.length()==0)
268 return "\"\"";
269
270
271 for (int i=0;i<s.length();i++)
272 {
273 char c = s.charAt(i);
274 if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
275 {
276 StringBuffer b=new StringBuffer(s.length()+8);
277 quote(b,s);
278 return b.toString();
279 }
280 }
281
282 return s;
283 }
284
285 /* ------------------------------------------------------------ */
286 /** Quote a string.
287 * The string is quoted only if quoting is required due to
288 * embeded delimiters, quote characters or the
289 * empty string.
290 * @param s The string to quote.
291 * @return quoted string
292 */
293 public static String quote(String s)
294 {
295 if (s==null)
296 return null;
297 if (s.length()==0)
298 return "\"\"";
299
300 StringBuffer b=new StringBuffer(s.length()+8);
301 quote(b,s);
302 return b.toString();
303
304 }
305
306
307 /* ------------------------------------------------------------ */
308 /** Quote a string into a StringBuffer.
309 * The characters ", \, \n, \r, \t, \f and \b are escaped
310 * @param buf The StringBuffer
311 * @param s The String to quote.
312 */
313 public static void quote(StringBuffer buf, String s)
314 {
315 synchronized(buf)
316 {
317 buf.append('"');
318
319 int i=0;
320 loop:
321 for (;i<s.length();i++)
322 {
323 char c = s.charAt(i);
324 switch(c)
325 {
326 case '"':
327 buf.append(s,0,i);
328 buf.append("\\\"");
329 break loop;
330 case '\\':
331 buf.append(s,0,i);
332 buf.append("\\\\");
333 break loop;
334 case '\n':
335 buf.append(s,0,i);
336 buf.append("\\n");
337 break loop;
338 case '\r':
339 buf.append(s,0,i);
340 buf.append("\\r");
341 break loop;
342 case '\t':
343 buf.append(s,0,i);
344 buf.append("\\t");
345 break loop;
346 case '\f':
347 buf.append(s,0,i);
348 buf.append("\\f");
349 break loop;
350 case '\b':
351 buf.append(s,0,i);
352 buf.append("\\b");
353 break loop;
354
355 default:
356 continue;
357 }
358 }
359 if (i==s.length())
360 buf.append(s);
361 else
362 {
363 i++;
364 for (;i<s.length();i++)
365 {
366 char c = s.charAt(i);
367 switch(c)
368 {
369 case '"':
370 buf.append("\\\"");
371 continue;
372 case '\\':
373 buf.append("\\\\");
374 continue;
375 case '\n':
376 buf.append("\\n");
377 continue;
378 case '\r':
379 buf.append("\\r");
380 continue;
381 case '\t':
382 buf.append("\\t");
383 continue;
384 case '\f':
385 buf.append("\\f");
386 continue;
387 case '\b':
388 buf.append("\\b");
389 continue;
390
391 default:
392 buf.append(c);
393 continue;
394 }
395 }
396 }
397
398 buf.append('"');
399 }
400
401
402
403 }
404
405
406 /* ------------------------------------------------------------ */
407 /** Quote a string into a StringBuffer.
408 * The characters ", \, \n, \r, \t, \f and \b are escaped
409 * @param buf The StringBuffer
410 * @param s The String to quote.
411 */
412 public static void quote(StringBuilder buf, String s)
413 {
414 buf.append('"');
415
416 int i=0;
417 loop:
418 for (;i<s.length();i++)
419 {
420 char c = s.charAt(i);
421 switch(c)
422 {
423 case '"':
424 buf.append(s,0,i);
425 buf.append("\\\"");
426 break loop;
427 case '\\':
428 buf.append(s,0,i);
429 buf.append("\\\\");
430 break loop;
431 case '\n':
432 buf.append(s,0,i);
433 buf.append("\\n");
434 break loop;
435 case '\r':
436 buf.append(s,0,i);
437 buf.append("\\r");
438 break loop;
439 case '\t':
440 buf.append(s,0,i);
441 buf.append("\\t");
442 break loop;
443 case '\f':
444 buf.append(s,0,i);
445 buf.append("\\f");
446 break loop;
447 case '\b':
448 buf.append(s,0,i);
449 buf.append("\\b");
450 break loop;
451
452 default:
453 continue;
454 }
455 }
456 if (i==s.length())
457 buf.append(s);
458 else
459 {
460 i++;
461 for (;i<s.length();i++)
462 {
463 char c = s.charAt(i);
464 switch(c)
465 {
466 case '"':
467 buf.append("\\\"");
468 continue;
469 case '\\':
470 buf.append("\\\\");
471 continue;
472 case '\n':
473 buf.append("\\n");
474 continue;
475 case '\r':
476 buf.append("\\r");
477 continue;
478 case '\t':
479 buf.append("\\t");
480 continue;
481 case '\f':
482 buf.append("\\f");
483 continue;
484 case '\b':
485 buf.append("\\b");
486 continue;
487
488 default:
489 buf.append(c);
490 continue;
491 }
492 }
493 }
494
495 buf.append('"');
496 }
497
498
499
500
501
502 /* ------------------------------------------------------------ */
503 /** Quote a string into a StringBuffer.
504 * The characters ", \, \n, \r, \t, \f, \b are escaped.
505 * Quotes are forced if any escaped characters are present or there
506 * is a ", ', space, + or % character.
507 *
508 * @param buf The StringBuffer
509 * @param s The String to quote.
510 */
511 public static void quoteIfNeeded(StringBuffer buf, String s)
512 {
513 synchronized(buf)
514 {
515 int e=-1;
516
517 search: for (int i=0;i<s.length();i++)
518 {
519 char c = s.charAt(i);
520 switch(c)
521 {
522 case '"':
523 case '\\':
524 case '\n':
525 case '\r':
526 case '\t':
527 case '\f':
528 case '\b':
529 case '%':
530 case '+':
531 case ' ':
532 e=i;
533 buf.append('"');
534 // TODO when 1.4 support is dropped: buf.append(s,0,e);
535 for (int j=0;j<e;j++)
536 buf.append(s.charAt(j));
537 break search;
538
539 default:
540 continue;
541 }
542 }
543
544 if (e<0)
545 {
546 buf.append(s);
547 return;
548 }
549
550 for (int i=e;i<s.length();i++)
551 {
552 char c = s.charAt(i);
553 switch(c)
554 {
555 case '"':
556 buf.append("\\\"");
557 continue;
558 case '\\':
559 buf.append("\\\\");
560 continue;
561 case '\n':
562 buf.append("\\n");
563 continue;
564 case '\r':
565 buf.append("\\r");
566 continue;
567 case '\t':
568 buf.append("\\t");
569 continue;
570 case '\f':
571 buf.append("\\f");
572 continue;
573 case '\b':
574 buf.append("\\b");
575 continue;
576
577 default:
578 buf.append(c);
579 continue;
580 }
581 }
582 buf.append('"');
583 }
584 }
585
586 /* ------------------------------------------------------------ */
587 /** Quote a string into a StringBuffer.
588 * The characters ", \, \n, \r, \t, \f, \b are escaped.
589 * Quotes are forced if any escaped characters are present or there
590 * is a ", ', space, + or % character.
591 *
592 * @param buf The StringBuilder
593 * @param s The String to quote.
594 */
595 public static void quoteIfNeeded(StringBuilder buf, String s)
596 {
597 int e=-1;
598
599 search: for (int i=0;i<s.length();i++)
600 {
601 char c = s.charAt(i);
602 switch(c)
603 {
604 case '"':
605 case '\\':
606 case '\n':
607 case '\r':
608 case '\t':
609 case '\f':
610 case '\b':
611 case '%':
612 case '+':
613 case ' ':
614 e=i;
615 buf.append('"');
616 // TODO when 1.4 support is dropped: buf.append(s,0,e);
617 for (int j=0;j<e;j++)
618 buf.append(s.charAt(j));
619 break search;
620
621 default:
622 continue;
623 }
624 }
625
626 if (e<0)
627 {
628 buf.append(s);
629 return;
630 }
631
632 for (int i=e;i<s.length();i++)
633 {
634 char c = s.charAt(i);
635 switch(c)
636 {
637 case '"':
638 buf.append("\\\"");
639 continue;
640 case '\\':
641 buf.append("\\\\");
642 continue;
643 case '\n':
644 buf.append("\\n");
645 continue;
646 case '\r':
647 buf.append("\\r");
648 continue;
649 case '\t':
650 buf.append("\\t");
651 continue;
652 case '\f':
653 buf.append("\\f");
654 continue;
655 case '\b':
656 buf.append("\\b");
657 continue;
658
659 default:
660 buf.append(c);
661 continue;
662 }
663 }
664 buf.append('"');
665
666 }
667
668 /* ------------------------------------------------------------ */
669 /** Unquote a string.
670 * @param s The string to unquote.
671 * @return quoted string
672 */
673 public static String unquote(String s)
674 {
675 if (s==null)
676 return null;
677 if (s.length()<2)
678 return s;
679
680 char first=s.charAt(0);
681 char last=s.charAt(s.length()-1);
682 if (first!=last || (first!='"' && first!='\''))
683 return s;
684
685 StringBuffer b=new StringBuffer(s.length()-2);
686 synchronized(b)
687 {
688 boolean escape=false;
689 for (int i=1;i<s.length()-1;i++)
690 {
691 char c = s.charAt(i);
692
693 if (escape)
694 {
695 escape=false;
696 switch (c)
697 {
698 case 'n':
699 b.append('\n');
700 break;
701 case 'r':
702 b.append('\r');
703 break;
704 case 't':
705 b.append('\t');
706 break;
707 case 'f':
708 b.append('\f');
709 break;
710 case 'b':
711 b.append('\b');
712 break;
713 case 'u':
714 b.append((char)(
715 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
716 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
717 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
718 (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
719 )
720 );
721 break;
722 default:
723 b.append(c);
724 }
725 }
726 else if (c=='\\')
727 {
728 escape=true;
729 continue;
730 }
731 else
732 b.append(c);
733 }
734
735 return b.toString();
736 }
737 }
738
739 /* ------------------------------------------------------------ */
740 /**
741 * @return handle double quotes if true
742 */
743 public boolean getDouble()
744 {
745 return _double;
746 }
747
748 /* ------------------------------------------------------------ */
749 /**
750 * @param d handle double quotes if true
751 */
752 public void setDouble(boolean d)
753 {
754 _double=d;
755 }
756
757 /* ------------------------------------------------------------ */
758 /**
759 * @return handle single quotes if true
760 */
761 public boolean getSingle()
762 {
763 return _single;
764 }
765
766 /* ------------------------------------------------------------ */
767 /**
768 * @param single handle single quotes if true
769 */
770 public void setSingle(boolean single)
771 {
772 _single=single;
773 }
774 }
775
776
777
778
779
780
781
782
783
784
785
786