1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.api.util;
20
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.NoSuchElementException;
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 public class QuoteUtil
40 {
41 private static class DeQuotingStringIterator implements Iterator<String>
42 {
43 private enum State
44 {
45 START,
46 TOKEN,
47 QUOTE_SINGLE,
48 QUOTE_DOUBLE
49 }
50
51 private static final boolean DEBUG = false;
52
53 private final String input;
54 private final String delims;
55 private StringBuilder token;
56 private boolean hasToken = false;
57 private int i = 0;
58
59 public DeQuotingStringIterator(String input, String delims)
60 {
61 this.input = input;
62 this.delims = delims;
63 int len = input.length();
64 token = new StringBuilder(len > 1024?512:len / 2);
65 }
66
67 private void appendToken(char c)
68 {
69 if (hasToken)
70 {
71 token.append(c);
72 }
73 else
74 {
75 if (Character.isWhitespace(c))
76 {
77 return;
78 }
79 else
80 {
81 token.append(c);
82 hasToken = true;
83 }
84 }
85 }
86
87 private void debug(String format, Object... args)
88 {
89 if (DEBUG)
90 {
91 System.out.printf(format,args);
92 }
93 }
94
95 @Override
96 public boolean hasNext()
97 {
98
99 if (hasToken)
100 {
101 return true;
102 }
103
104 State state = State.START;
105 boolean escape = false;
106 int inputLen = input.length();
107
108 while (i < inputLen)
109 {
110 char c = input.charAt(i++);
111
112 switch (state)
113 {
114 case START:
115 {
116 if (c == '\'')
117 {
118 state = State.QUOTE_SINGLE;
119 appendToken(c);
120 }
121 else if (c == '\"')
122 {
123 state = State.QUOTE_DOUBLE;
124 appendToken(c);
125 }
126 else
127 {
128 appendToken(c);
129 state = State.TOKEN;
130 }
131 break;
132 }
133 case TOKEN:
134 {
135 if (delims.indexOf(c) >= 0)
136 {
137 debug("hasNext/t: %b [%s]%n",hasToken,token);
138 return hasToken;
139 }
140 else if (c == '\'')
141 {
142 state = State.QUOTE_SINGLE;
143 }
144 else if (c == '\"')
145 {
146 state = State.QUOTE_DOUBLE;
147 }
148 appendToken(c);
149 break;
150 }
151 case QUOTE_SINGLE:
152 {
153 if (escape)
154 {
155 escape = false;
156 appendToken(c);
157 }
158 else if (c == '\'')
159 {
160 appendToken(c);
161 state = State.TOKEN;
162 }
163 else if (c == '\\')
164 {
165 escape = true;
166 }
167 else
168 {
169 appendToken(c);
170 }
171 break;
172 }
173 case QUOTE_DOUBLE:
174 {
175 if (escape)
176 {
177 escape = false;
178 appendToken(c);
179 }
180 else if (c == '\"')
181 {
182 appendToken(c);
183 state = State.TOKEN;
184 }
185 else if (c == '\\')
186 {
187 escape = true;
188 }
189 else
190 {
191 appendToken(c);
192 }
193 break;
194 }
195 }
196 debug("%s <%s> : [%s]%n",state,c,token);
197 }
198
199 debug("hasNext/e: %b [%s]%n",hasToken,token);
200 return hasToken;
201 }
202
203 @Override
204 public String next()
205 {
206 if (!hasNext())
207 {
208 throw new NoSuchElementException();
209 }
210 String ret = token.toString();
211 token.setLength(0);
212 hasToken = false;
213 return QuoteUtil.dequote(ret.trim());
214 }
215
216 @Override
217 public void remove()
218 {
219 throw new UnsupportedOperationException("Remove not supported with this iterator");
220 }
221 }
222
223
224
225
226 public static final String ABNF_REQUIRED_QUOTING = "\"'\\\n\r\t\f\b%+ ;=";
227
228 private static final char UNICODE_TAG = 0xFFFF;
229 private static final char[] escapes = new char[32];
230
231 static
232 {
233 Arrays.fill(escapes,UNICODE_TAG);
234
235 escapes['\b'] = 'b';
236 escapes['\t'] = 't';
237 escapes['\n'] = 'n';
238 escapes['\f'] = 'f';
239 escapes['\r'] = 'r';
240 }
241
242 private static int dehex(byte b)
243 {
244 if ((b >= '0') && (b <= '9'))
245 {
246 return (byte)(b - '0');
247 }
248 if ((b >= 'a') && (b <= 'f'))
249 {
250 return (byte)((b - 'a') + 10);
251 }
252 if ((b >= 'A') && (b <= 'F'))
253 {
254 return (byte)((b - 'A') + 10);
255 }
256 throw new IllegalArgumentException("!hex:" + Integer.toHexString(0xff & b));
257 }
258
259
260
261
262
263
264
265
266 public static String dequote(String str)
267 {
268 char start = str.charAt(0);
269 if ((start == '\'') || (start == '\"'))
270 {
271
272 char end = str.charAt(str.length() - 1);
273 if (start == end)
274 {
275
276 return str.substring(1,str.length() - 1);
277 }
278 }
279 return str;
280 }
281
282 public static void escape(StringBuilder buf, String str)
283 {
284 for (char c : str.toCharArray())
285 {
286 if (c >= 32)
287 {
288
289 if ((c == '"') || (c == '\\'))
290 {
291 buf.append('\\');
292 }
293 buf.append(c);
294 }
295 else
296 {
297
298 char escaped = escapes[c];
299
300
301 if (escaped == UNICODE_TAG)
302 {
303 buf.append("\\u00");
304 if (c < 0x10)
305 {
306 buf.append('0');
307 }
308 buf.append(Integer.toString(c,16));
309 }
310 else
311 {
312
313 buf.append('\\').append(escaped);
314 }
315 }
316 }
317 }
318
319
320
321
322
323
324
325
326
327 public static void quote(StringBuilder buf, String str)
328 {
329 buf.append('"');
330 escape(buf,str);
331 buf.append('"');
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346 public static void quoteIfNeeded(StringBuilder buf, String str, String delim)
347 {
348 if (str == null)
349 {
350 return;
351 }
352
353 int len = str.length();
354 if (len == 0)
355 {
356 return;
357 }
358 int ch;
359 for (int i = 0; i < len; i++)
360 {
361 ch = str.codePointAt(i);
362 if (delim.indexOf(ch) >= 0)
363 {
364
365 quote(buf,str);
366 return;
367 }
368 }
369
370
371 buf.append(str);
372 }
373
374
375
376
377
378
379
380
381
382
383
384 public static Iterator<String> splitAt(String str, String delims)
385 {
386 return new DeQuotingStringIterator(str.trim(),delims);
387 }
388
389 public static String unescape(String str)
390 {
391 if (str == null)
392 {
393
394 return null;
395 }
396
397 int len = str.length();
398 if (len <= 1)
399 {
400
401 return str;
402 }
403
404 StringBuilder ret = new StringBuilder(len - 2);
405 boolean escaped = false;
406 char c;
407 for (int i = 0; i < len; i++)
408 {
409 c = str.charAt(i);
410 if (escaped)
411 {
412 escaped = false;
413 switch (c)
414 {
415 case 'n':
416 ret.append('\n');
417 break;
418 case 'r':
419 ret.append('\r');
420 break;
421 case 't':
422 ret.append('\t');
423 break;
424 case 'f':
425 ret.append('\f');
426 break;
427 case 'b':
428 ret.append('\b');
429 break;
430 case '\\':
431 ret.append('\\');
432 break;
433 case '/':
434 ret.append('/');
435 break;
436 case '"':
437 ret.append('"');
438 break;
439 case 'u':
440 ret.append((char)((dehex((byte)str.charAt(i++)) << 24) + (dehex((byte)str.charAt(i++)) << 16) + (dehex((byte)str.charAt(i++)) << 8) + (dehex((byte)str
441 .charAt(i++)))));
442 break;
443 default:
444 ret.append(c);
445 }
446 }
447 else if (c == '\\')
448 {
449 escaped = true;
450 }
451 else
452 {
453 ret.append(c);
454 }
455 }
456 return ret.toString();
457 }
458
459 public static String join(Object[] objs, String delim)
460 {
461 if (objs == null)
462 {
463 return "";
464 }
465 StringBuilder ret = new StringBuilder();
466 int len = objs.length;
467 for (int i = 0; i < len; i++)
468 {
469 if (i > 0)
470 {
471 ret.append(delim);
472 }
473 if (objs[i] instanceof String)
474 {
475 ret.append('"').append(objs[i]).append('"');
476 }
477 else
478 {
479 ret.append(objs[i]);
480 }
481 }
482 return ret.toString();
483 }
484
485 public static String join(Collection<?> objs, String delim)
486 {
487 if (objs == null)
488 {
489 return "";
490 }
491 StringBuilder ret = new StringBuilder();
492 boolean needDelim = false;
493 for (Object obj : objs)
494 {
495 if (needDelim)
496 {
497 ret.append(delim);
498 }
499 if (obj instanceof String)
500 {
501 ret.append('"').append(obj).append('"');
502 }
503 else
504 {
505 ret.append(obj);
506 }
507 needDelim = true;
508 }
509 return ret.toString();
510 }
511 }