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
349 int len = str.length();
350 int ch;
351 for (int i = 0; i < len; i++)
352 {
353 ch = str.codePointAt(i);
354 if (delim.indexOf(ch) >= 0)
355 {
356
357 quote(buf,str);
358 return;
359 }
360 }
361
362
363 buf.append(str);
364 }
365
366
367
368
369
370
371
372
373
374
375
376 public static Iterator<String> splitAt(String str, String delims)
377 {
378 return new DeQuotingStringIterator(str.trim(),delims);
379 }
380
381 public static String unescape(String str)
382 {
383 if (str == null)
384 {
385
386 return null;
387 }
388
389 int len = str.length();
390 if (len <= 1)
391 {
392
393 return str;
394 }
395
396 StringBuilder ret = new StringBuilder(len - 2);
397 boolean escaped = false;
398 char c;
399 for (int i = 0; i < len; i++)
400 {
401 c = str.charAt(i);
402 if (escaped)
403 {
404 escaped = false;
405 switch (c)
406 {
407 case 'n':
408 ret.append('\n');
409 break;
410 case 'r':
411 ret.append('\r');
412 break;
413 case 't':
414 ret.append('\t');
415 break;
416 case 'f':
417 ret.append('\f');
418 break;
419 case 'b':
420 ret.append('\b');
421 break;
422 case '\\':
423 ret.append('\\');
424 break;
425 case '/':
426 ret.append('/');
427 break;
428 case '"':
429 ret.append('"');
430 break;
431 case 'u':
432 ret.append((char)((dehex((byte)str.charAt(i++)) << 24) + (dehex((byte)str.charAt(i++)) << 16) + (dehex((byte)str.charAt(i++)) << 8) + (dehex((byte)str
433 .charAt(i++)))));
434 break;
435 default:
436 ret.append(c);
437 }
438 }
439 else if (c == '\\')
440 {
441 escaped = true;
442 }
443 else
444 {
445 ret.append(c);
446 }
447 }
448 return ret.toString();
449 }
450
451 public static String join(Object[] objs, String delim)
452 {
453 if (objs == null)
454 {
455 return "";
456 }
457 StringBuilder ret = new StringBuilder();
458 int len = objs.length;
459 for (int i = 0; i < len; i++)
460 {
461 if (i > 0)
462 {
463 ret.append(delim);
464 }
465 if (objs[i] instanceof String)
466 {
467 ret.append('"').append(objs[i]).append('"');
468 }
469 else
470 {
471 ret.append(objs[i]);
472 }
473 }
474 return ret.toString();
475 }
476
477 public static String join(Collection<?> objs, String delim)
478 {
479 if (objs == null)
480 {
481 return "";
482 }
483 StringBuilder ret = new StringBuilder();
484 boolean needDelim = false;
485 for (Object obj : objs)
486 {
487 if (needDelim)
488 {
489 ret.append(delim);
490 }
491 if (obj instanceof String)
492 {
493 ret.append('"').append(obj).append('"');
494 }
495 else
496 {
497 ret.append(obj);
498 }
499 needDelim = true;
500 }
501 return ret.toString();
502 }
503 }