• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 //  ------------------------------------------------------------------------
5 //  All rights reserved. This program and the accompanying materials
6 //  are made available under the terms of the Eclipse Public License v1.0
7 //  and Apache License v2.0 which accompanies this distribution.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18 
19 package org.eclipse.jetty.util;
20 
21 import java.io.IOException;
22 import java.util.Arrays;
23 import java.util.NoSuchElementException;
24 import java.util.StringTokenizer;
25 
26 /* ------------------------------------------------------------ */
27 /** StringTokenizer with Quoting support.
28  *
29  * This class is a copy of the java.util.StringTokenizer API and
30  * the behaviour is the same, except that single and double quoted
31  * string values are recognised.
32  * Delimiters within quotes are not considered delimiters.
33  * Quotes can be escaped with '\'.
34  *
35  * @see java.util.StringTokenizer
36  *
37  */
38 public class QuotedStringTokenizer
39     extends StringTokenizer
40 {
41     private final static String __delim="\t\n\r";
42     private String _string;
43     private String _delim = __delim;
44     private boolean _returnQuotes=false;
45     private boolean _returnDelimiters=false;
46     private StringBuffer _token;
47     private boolean _hasToken=false;
48     private int _i=0;
49     private int _lastStart=0;
50     private boolean _double=true;
51     private boolean _single=true;
52 
53     /* ------------------------------------------------------------ */
QuotedStringTokenizer(String str, String delim, boolean returnDelimiters, boolean returnQuotes)54     public QuotedStringTokenizer(String str,
55                                  String delim,
56                                  boolean returnDelimiters,
57                                  boolean returnQuotes)
58     {
59         super("");
60         _string=str;
61         if (delim!=null)
62             _delim=delim;
63         _returnDelimiters=returnDelimiters;
64         _returnQuotes=returnQuotes;
65 
66         if (_delim.indexOf('\'')>=0 ||
67             _delim.indexOf('"')>=0)
68             throw new Error("Can't use quotes as delimiters: "+_delim);
69 
70         _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
71     }
72 
73     /* ------------------------------------------------------------ */
QuotedStringTokenizer(String str, String delim, boolean returnDelimiters)74     public QuotedStringTokenizer(String str,
75                                  String delim,
76                                  boolean returnDelimiters)
77     {
78         this(str,delim,returnDelimiters,false);
79     }
80 
81     /* ------------------------------------------------------------ */
QuotedStringTokenizer(String str, String delim)82     public QuotedStringTokenizer(String str,
83                                  String delim)
84     {
85         this(str,delim,false,false);
86     }
87 
88     /* ------------------------------------------------------------ */
QuotedStringTokenizer(String str)89     public QuotedStringTokenizer(String str)
90     {
91         this(str,null,false,false);
92     }
93 
94     /* ------------------------------------------------------------ */
95     @Override
hasMoreTokens()96     public boolean hasMoreTokens()
97     {
98         // Already found a token
99         if (_hasToken)
100             return true;
101 
102         _lastStart=_i;
103 
104         int state=0;
105         boolean escape=false;
106         while (_i<_string.length())
107         {
108             char c=_string.charAt(_i++);
109 
110             switch (state)
111             {
112               case 0: // Start
113                   if(_delim.indexOf(c)>=0)
114                   {
115                       if (_returnDelimiters)
116                       {
117                           _token.append(c);
118                           return _hasToken=true;
119                       }
120                   }
121                   else if (c=='\'' && _single)
122                   {
123                       if (_returnQuotes)
124                           _token.append(c);
125                       state=2;
126                   }
127                   else if (c=='\"' && _double)
128                   {
129                       if (_returnQuotes)
130                           _token.append(c);
131                       state=3;
132                   }
133                   else
134                   {
135                       _token.append(c);
136                       _hasToken=true;
137                       state=1;
138                   }
139                   break;
140 
141               case 1: // Token
142                   _hasToken=true;
143                   if(_delim.indexOf(c)>=0)
144                   {
145                       if (_returnDelimiters)
146                           _i--;
147                       return _hasToken;
148                   }
149                   else if (c=='\'' && _single)
150                   {
151                       if (_returnQuotes)
152                           _token.append(c);
153                       state=2;
154                   }
155                   else if (c=='\"' && _double)
156                   {
157                       if (_returnQuotes)
158                           _token.append(c);
159                       state=3;
160                   }
161                   else
162                   {
163                       _token.append(c);
164                   }
165                   break;
166 
167               case 2: // Single Quote
168                   _hasToken=true;
169                   if (escape)
170                   {
171                       escape=false;
172                       _token.append(c);
173                   }
174                   else if (c=='\'')
175                   {
176                       if (_returnQuotes)
177                           _token.append(c);
178                       state=1;
179                   }
180                   else if (c=='\\')
181                   {
182                       if (_returnQuotes)
183                           _token.append(c);
184                       escape=true;
185                   }
186                   else
187                   {
188                       _token.append(c);
189                   }
190                   break;
191 
192               case 3: // Double Quote
193                   _hasToken=true;
194                   if (escape)
195                   {
196                       escape=false;
197                       _token.append(c);
198                   }
199                   else if (c=='\"')
200                   {
201                       if (_returnQuotes)
202                           _token.append(c);
203                       state=1;
204                   }
205                   else if (c=='\\')
206                   {
207                       if (_returnQuotes)
208                           _token.append(c);
209                       escape=true;
210                   }
211                   else
212                   {
213                       _token.append(c);
214                   }
215                   break;
216             }
217         }
218 
219         return _hasToken;
220     }
221 
222     /* ------------------------------------------------------------ */
223     @Override
nextToken()224     public String nextToken()
225         throws NoSuchElementException
226     {
227         if (!hasMoreTokens() || _token==null)
228             throw new NoSuchElementException();
229         String t=_token.toString();
230         _token.setLength(0);
231         _hasToken=false;
232         return t;
233     }
234 
235     /* ------------------------------------------------------------ */
236     @Override
nextToken(String delim)237     public String nextToken(String delim)
238         throws NoSuchElementException
239     {
240         _delim=delim;
241         _i=_lastStart;
242         _token.setLength(0);
243         _hasToken=false;
244         return nextToken();
245     }
246 
247     /* ------------------------------------------------------------ */
248     @Override
hasMoreElements()249     public boolean hasMoreElements()
250     {
251         return hasMoreTokens();
252     }
253 
254     /* ------------------------------------------------------------ */
255     @Override
nextElement()256     public Object nextElement()
257         throws NoSuchElementException
258     {
259         return nextToken();
260     }
261 
262     /* ------------------------------------------------------------ */
263     /** Not implemented.
264      */
265     @Override
countTokens()266     public int countTokens()
267     {
268         return -1;
269     }
270 
271 
272     /* ------------------------------------------------------------ */
273     /** Quote a string.
274      * The string is quoted only if quoting is required due to
275      * embedded delimiters, quote characters or the
276      * empty string.
277      * @param s The string to quote.
278      * @param delim the delimiter to use to quote the string
279      * @return quoted string
280      */
quoteIfNeeded(String s, String delim)281     public static String quoteIfNeeded(String s, String delim)
282     {
283         if (s==null)
284             return null;
285         if (s.length()==0)
286             return "\"\"";
287 
288 
289         for (int i=0;i<s.length();i++)
290         {
291             char c = s.charAt(i);
292             if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
293             {
294                 StringBuffer b=new StringBuffer(s.length()+8);
295                 quote(b,s);
296                 return b.toString();
297             }
298         }
299 
300         return s;
301     }
302 
303     /* ------------------------------------------------------------ */
304     /** Quote a string.
305      * The string is quoted only if quoting is required due to
306      * embeded delimiters, quote characters or the
307      * empty string.
308      * @param s The string to quote.
309      * @return quoted string
310      */
quote(String s)311     public static String quote(String s)
312     {
313         if (s==null)
314             return null;
315         if (s.length()==0)
316             return "\"\"";
317 
318         StringBuffer b=new StringBuffer(s.length()+8);
319         quote(b,s);
320         return b.toString();
321 
322     }
323 
324     private static final char[] escapes = new char[32];
325     static
326     {
Arrays.fill(escapes, (char)0xFFFF)327         Arrays.fill(escapes, (char)0xFFFF);
328         escapes['\b'] = 'b';
329         escapes['\t'] = 't';
330         escapes['\n'] = 'n';
331         escapes['\f'] = 'f';
332         escapes['\r'] = 'r';
333     }
334 
335     /* ------------------------------------------------------------ */
336     /** Quote a string into an Appendable.
337      * The characters ", \, \n, \r, \t, \f and \b are escaped
338      * @param buffer The Appendable
339      * @param input The String to quote.
340      */
quote(Appendable buffer, String input)341     public static void quote(Appendable buffer, String input)
342     {
343         try
344         {
345             buffer.append('"');
346             for (int i = 0; i < input.length(); ++i)
347             {
348                 char c = input.charAt(i);
349                 if (c >= 32)
350                 {
351                     if (c == '"' || c == '\\')
352                         buffer.append('\\');
353                     buffer.append(c);
354                 }
355                 else
356                 {
357                     char escape = escapes[c];
358                     if (escape == 0xFFFF)
359                     {
360                         // Unicode escape
361                         buffer.append('\\').append('u').append('0').append('0');
362                         if (c < 0x10)
363                             buffer.append('0');
364                         buffer.append(Integer.toString(c, 16));
365                     }
366                     else
367                     {
368                         buffer.append('\\').append(escape);
369                     }
370                 }
371             }
372             buffer.append('"');
373         }
374         catch (IOException x)
375         {
376             throw new RuntimeException(x);
377         }
378     }
379 
380     /* ------------------------------------------------------------ */
381     /** Quote a string into a StringBuffer only if needed.
382      * Quotes are forced if any delim characters are present.
383      *
384      * @param buf The StringBuffer
385      * @param s The String to quote.
386      * @param delim String of characters that must be quoted.
387      * @return true if quoted;
388      */
quoteIfNeeded(Appendable buf, String s,String delim)389     public static boolean quoteIfNeeded(Appendable buf, String s,String delim)
390     {
391         for (int i=0;i<s.length();i++)
392         {
393             char c = s.charAt(i);
394             if (delim.indexOf(c)>=0)
395             {
396             	quote(buf,s);
397             	return true;
398             }
399         }
400 
401         try
402         {
403             buf.append(s);
404             return false;
405         }
406         catch(IOException e)
407         {
408             throw new RuntimeException(e);
409         }
410     }
411 
412 
413     /* ------------------------------------------------------------ */
unquoteOnly(String s)414     public static String unquoteOnly(String s)
415     {
416         return unquoteOnly(s, false);
417     }
418 
419 
420     /* ------------------------------------------------------------ */
421     /** Unquote a string, NOT converting unicode sequences
422      * @param s The string to unquote.
423      * @param lenient if true, will leave in backslashes that aren't valid escapes
424      * @return quoted string
425      */
unquoteOnly(String s, boolean lenient)426     public static String unquoteOnly(String s, boolean lenient)
427     {
428         if (s==null)
429             return null;
430         if (s.length()<2)
431             return s;
432 
433         char first=s.charAt(0);
434         char last=s.charAt(s.length()-1);
435         if (first!=last || (first!='"' && first!='\''))
436             return s;
437 
438         StringBuilder b = new StringBuilder(s.length() - 2);
439         boolean escape=false;
440         for (int i=1;i<s.length()-1;i++)
441         {
442             char c = s.charAt(i);
443 
444             if (escape)
445             {
446                 escape=false;
447                 if (lenient && !isValidEscaping(c))
448                 {
449                     b.append('\\');
450                 }
451                 b.append(c);
452             }
453             else if (c=='\\')
454             {
455                 escape=true;
456             }
457             else
458             {
459                 b.append(c);
460             }
461         }
462 
463         return b.toString();
464     }
465 
466     /* ------------------------------------------------------------ */
unquote(String s)467     public static String unquote(String s)
468     {
469         return unquote(s,false);
470     }
471 
472     /* ------------------------------------------------------------ */
473     /** Unquote a string.
474      * @param s The string to unquote.
475      * @return quoted string
476      */
unquote(String s, boolean lenient)477     public static String unquote(String s, boolean lenient)
478     {
479         if (s==null)
480             return null;
481         if (s.length()<2)
482             return s;
483 
484         char first=s.charAt(0);
485         char last=s.charAt(s.length()-1);
486         if (first!=last || (first!='"' && first!='\''))
487             return s;
488 
489         StringBuilder b = new StringBuilder(s.length() - 2);
490         boolean escape=false;
491         for (int i=1;i<s.length()-1;i++)
492         {
493             char c = s.charAt(i);
494 
495             if (escape)
496             {
497                 escape=false;
498                 switch (c)
499                 {
500                     case 'n':
501                         b.append('\n');
502                         break;
503                     case 'r':
504                         b.append('\r');
505                         break;
506                     case 't':
507                         b.append('\t');
508                         break;
509                     case 'f':
510                         b.append('\f');
511                         break;
512                     case 'b':
513                         b.append('\b');
514                         break;
515                     case '\\':
516                         b.append('\\');
517                         break;
518                     case '/':
519                         b.append('/');
520                         break;
521                     case '"':
522                         b.append('"');
523                         break;
524                     case 'u':
525                         b.append((char)(
526                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
527                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
528                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
529                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
530                                 )
531                         );
532                         break;
533                     default:
534                         if (lenient && !isValidEscaping(c))
535                         {
536                             b.append('\\');
537                         }
538                         b.append(c);
539                 }
540             }
541             else if (c=='\\')
542             {
543                 escape=true;
544             }
545             else
546             {
547                 b.append(c);
548             }
549         }
550 
551         return b.toString();
552     }
553 
554 
555     /* ------------------------------------------------------------ */
556     /** Check that char c (which is preceded by a backslash) is a valid
557      * escape sequence.
558      * @param c
559      * @return
560      */
isValidEscaping(char c)561     private static boolean isValidEscaping(char c)
562     {
563         return ((c == 'n') || (c == 'r') || (c == 't') ||
564                  (c == 'f') || (c == 'b') || (c == '\\') ||
565                  (c == '/') || (c == '"') || (c == 'u'));
566     }
567 
568     /* ------------------------------------------------------------ */
569     /**
570      * @return handle double quotes if true
571      */
getDouble()572     public boolean getDouble()
573     {
574         return _double;
575     }
576 
577     /* ------------------------------------------------------------ */
578     /**
579      * @param d handle double quotes if true
580      */
setDouble(boolean d)581     public void setDouble(boolean d)
582     {
583         _double=d;
584     }
585 
586     /* ------------------------------------------------------------ */
587     /**
588      * @return handle single quotes if true
589      */
getSingle()590     public boolean getSingle()
591     {
592         return _single;
593     }
594 
595     /* ------------------------------------------------------------ */
596     /**
597      * @param single handle single quotes if true
598      */
setSingle(boolean single)599     public void setSingle(boolean single)
600     {
601         _single=single;
602     }
603 }
604