blob: dc85da1b64b100a72a3ebc2b2e526a76f5fa82b3 [file] [log] [blame]
Jake Slack03928ae2014-05-13 18:41:56 -07001//
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
19package org.eclipse.jetty.util;
20
21import java.io.IOException;
22import java.util.Arrays;
23import java.util.NoSuchElementException;
24import 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 */
38public 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 * The characters ", \, \n, \r, \t, \f and \b are escaped
338 * @param buffer The Appendable
339 * @param input The String to quote.
340 */
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 */
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 /* ------------------------------------------------------------ */
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 * @return quoted string
476 */
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 */
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 */
572 public boolean getDouble()
573 {
574 return _double;
575 }
576
577 /* ------------------------------------------------------------ */
578 /**
579 * @param d handle double quotes if true
580 */
581 public void setDouble(boolean d)
582 {
583 _double=d;
584 }
585
586 /* ------------------------------------------------------------ */
587 /**
588 * @return handle single quotes if true
589 */
590 public boolean getSingle()
591 {
592 return _single;
593 }
594
595 /* ------------------------------------------------------------ */
596 /**
597 * @param single handle single quotes if true
598 */
599 public void setSingle(boolean single)
600 {
601 _single=single;
602 }
603}