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