View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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      /* ------------------------------------------------------------ */
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      /* ------------------------------------------------------------ */
74      public QuotedStringTokenizer(String str,
75                                   String delim,
76                                   boolean returnDelimiters)
77      {
78          this(str,delim,returnDelimiters,false);
79      }
80  
81      /* ------------------------------------------------------------ */
82      public QuotedStringTokenizer(String str,
83                                   String delim)
84      {
85          this(str,delim,false,false);
86      }
87  
88      /* ------------------------------------------------------------ */
89      public QuotedStringTokenizer(String str)
90      {
91          this(str,null,false,false);
92      }
93  
94      /* ------------------------------------------------------------ */
95      @Override
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
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
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
249     public boolean hasMoreElements()
250     {
251         return hasMoreTokens();
252     }
253 
254     /* ------------------------------------------------------------ */
255     @Override
256     public Object nextElement()
257         throws NoSuchElementException
258     {
259         return nextToken();
260     }
261 
262     /* ------------------------------------------------------------ */
263     /** Not implemented.
264      */
265     @Override
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      */
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      */
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     {
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      * Only quotes and backslash are escaped.
338      * @param buffer The Appendable
339      * @param input The String to quote.
340      */
341     public static void quoteOnly(Appendable buffer, String input)
342     {
343         if(input==null)
344             return;
345 
346         try
347         {
348             buffer.append('"');
349             for (int i = 0; i < input.length(); ++i)
350             {
351                 char c = input.charAt(i);
352                 if (c == '"' || c == '\\')
353                     buffer.append('\\');
354                 buffer.append(c);
355             }
356             buffer.append('"');
357         }
358         catch (IOException x)
359         {
360             throw new RuntimeException(x);
361         }
362     }
363 
364     /* ------------------------------------------------------------ */
365     /** Quote a string into an Appendable.
366      * The characters ", \, \n, \r, \t, \f and \b are escaped
367      * @param buffer The Appendable
368      * @param input The String to quote.
369      */
370     public static void quote(Appendable buffer, String input)
371     {
372         if(input==null)
373             return;
374 
375         try
376         {
377             buffer.append('"');
378             for (int i = 0; i < input.length(); ++i)
379             {
380                 char c = input.charAt(i);
381                 if (c >= 32)
382                 {
383                     if (c == '"' || c == '\\')
384                         buffer.append('\\');
385                     buffer.append(c);
386                 }
387                 else
388                 {
389                     char escape = escapes[c];
390                     if (escape == 0xFFFF)
391                     {
392                         // Unicode escape
393                         buffer.append('\\').append('u').append('0').append('0');
394                         if (c < 0x10)
395                             buffer.append('0');
396                         buffer.append(Integer.toString(c, 16));
397                     }
398                     else
399                     {
400                         buffer.append('\\').append(escape);
401                     }
402                 }
403             }
404             buffer.append('"');
405         }
406         catch (IOException x)
407         {
408             throw new RuntimeException(x);
409         }
410     }
411 
412 
413     /* ------------------------------------------------------------ */
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      */
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     /* ------------------------------------------------------------ */
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      * @param lenient true if unquoting should be lenient to escaped content, leaving some alone, false if string unescaping
476      * @return quoted string
477      */
478     public static String unquote(String s, boolean lenient)
479     {
480         if (s==null)
481             return null;
482         if (s.length()<2)
483             return s;
484 
485         char first=s.charAt(0);
486         char last=s.charAt(s.length()-1);
487         if (first!=last || (first!='"' && first!='\''))
488             return s;
489 
490         StringBuilder b = new StringBuilder(s.length() - 2);
491         boolean escape=false;
492         for (int i=1;i<s.length()-1;i++)
493         {
494             char c = s.charAt(i);
495 
496             if (escape)
497             {
498                 escape=false;
499                 switch (c)
500                 {
501                     case 'n':
502                         b.append('\n');
503                         break;
504                     case 'r':
505                         b.append('\r');
506                         break;
507                     case 't':
508                         b.append('\t');
509                         break;
510                     case 'f':
511                         b.append('\f');
512                         break;
513                     case 'b':
514                         b.append('\b');
515                         break;
516                     case '\\':
517                         b.append('\\');
518                         break;
519                     case '/':
520                         b.append('/');
521                         break;
522                     case '"':
523                         b.append('"');
524                         break;
525                     case 'u':
526                         b.append((char)(
527                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
528                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
529                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
530                                 (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
531                                 )
532                         );
533                         break;
534                     default:
535                         if (lenient && !isValidEscaping(c))
536                         {
537                             b.append('\\');
538                         }
539                         b.append(c);
540                 }
541             }
542             else if (c=='\\')
543             {
544                 escape=true;
545             }
546             else
547             {
548                 b.append(c);
549             }
550         }
551 
552         return b.toString();
553     }
554 
555 
556     /* ------------------------------------------------------------ */
557     /** Check that char c (which is preceded by a backslash) is a valid
558      * escape sequence.
559      * @param c
560      * @return
561      */
562     private static boolean isValidEscaping(char c)
563     {
564         return ((c == 'n') || (c == 'r') || (c == 't') ||
565                  (c == 'f') || (c == 'b') || (c == '\\') ||
566                  (c == '/') || (c == '"') || (c == 'u'));
567     }
568 
569     /* ------------------------------------------------------------ */
570     public static boolean isQuoted(String s)
571     {
572         return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
573     }
574 
575     /* ------------------------------------------------------------ */
576     /**
577      * @return handle double quotes if true
578      */
579     public boolean getDouble()
580     {
581         return _double;
582     }
583 
584     /* ------------------------------------------------------------ */
585     /**
586      * @param d handle double quotes if true
587      */
588     public void setDouble(boolean d)
589     {
590         _double=d;
591     }
592 
593     /* ------------------------------------------------------------ */
594     /**
595      * @return handle single quotes if true
596      */
597     public boolean getSingle()
598     {
599         return _single;
600     }
601 
602     /* ------------------------------------------------------------ */
603     /**
604      * @param single handle single quotes if true
605      */
606     public void setSingle(boolean single)
607     {
608         _single=single;
609     }
610 }