1 package com.atlassian.security.auth.trustedapps;
2
3 /*
4 * Portions Copyright (c) 2002 JSON.org Permission is hereby granted, free of charge, to any person obtaining a copy of
5 * this software and associated documentation files (the "Software"), to deal in the Software without restriction,
6 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
8 * conditions: The above copyright notice and this permission notice shall be included in all copies or substantial
9 * portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT
10 * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
11 * A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
12 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
13 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 */
15
16 import java.util.Arrays;
17 import java.util.LinkedList;
18 import java.util.List;
19
20 /**
21 * String manipulation methods.
22 */
23 class StringUtil
24 {
25 /**
26 * Splits a comma separated list of values. Values must be quoted and can be encased in square-brackets (JSON
27 * style).
28 *
29 * @param the
30 * source String
31 * @return an array of String values
32 */
33 static String[] split(String source)
34 {
35 Null.not("source", source);
36
37 final List result = new JSONList(source.trim());
38 return (String[]) result.toArray(new String[result.size()]);
39 }
40
41 static String toString(String[] source)
42 {
43 Null.not("source", source);
44
45 return new JSONList(source).toString();
46 }
47
48 /**
49 * subset of JSONArray that simply takes a String and converts to a list.
50 */
51 private static class JSONList extends LinkedList
52 {
53 private static final long serialVersionUID = -8317241062936626298L;
54
55 JSONList(String source)
56 {
57 final Tokenizer tokenizer = new Tokenizer(source);
58
59 if (tokenizer.nextClean() != '[')
60 {
61 tokenizer.syntaxError("String must start with square bracket");
62 }
63 switch (tokenizer.nextClean())
64 {
65 case ']':
66 case 0:
67 return;
68 }
69 tokenizer.back();
70 for (;;)
71 {
72 if (tokenizer.nextClean() == ',')
73 {
74 tokenizer.back();
75 this.add(null);
76 }
77 else
78 {
79 tokenizer.back();
80 this.add(tokenizer.nextValue());
81 }
82 final char nextClean = tokenizer.nextClean();
83 switch (nextClean)
84 {
85 case ';':
86 case ',':
87 switch (tokenizer.nextClean())
88 {
89 case ']':
90 case 0:
91 return;
92 }
93 tokenizer.back();
94 break;
95 case ']':
96 case 0:
97 return;
98 default:
99 tokenizer.syntaxError("Expected a ',' or ']' rather than: " + (int) nextClean);
100 }
101 }
102 }
103
104 JSONList(String[] source)
105 {
106 super(Arrays.asList(source));
107 }
108
109 String join(String separator)
110 {
111 final int len = size();
112 final StringBuffer sb = new StringBuffer();
113
114 for (int i = 0; i < len; i += 1)
115 {
116 if (i > 0)
117 {
118 sb.append(separator);
119 }
120 sb.append(quote((String) this.get(i)));
121 }
122 return sb.toString();
123 }
124
125 /**
126 * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be
127 * inserted within </, allowing JSON text to be delivered in HTML. In JSON text, a string cannot contain a
128 * control character or an unescaped quote or backslash.
129 *
130 * @param string
131 * A String
132 * @return A String correctly formatted for insertion in a JSON text.
133 */
134 String quote(String string)
135 {
136 if ((string == null) || (string.length() == 0))
137 {
138 return "\"\"";
139 }
140
141 char b;
142 char c = 0;
143 int i;
144 final int len = string.length();
145 final StringBuffer sb = new StringBuffer(len + 4);
146 String t;
147
148 sb.append('"');
149 for (i = 0; i < len; i += 1)
150 {
151 b = c;
152 c = string.charAt(i);
153 switch (c)
154 {
155 case '\\':
156 case '"':
157 sb.append('\\');
158 sb.append(c);
159 break;
160 case '/':
161 if (b == '<')
162 {
163 sb.append('\\');
164 }
165 sb.append(c);
166 break;
167 case '\b':
168 sb.append("\\b");
169 break;
170 case '\t':
171 sb.append("\\t");
172 break;
173 case '\n':
174 sb.append("\\n");
175 break;
176 case '\f':
177 sb.append("\\f");
178 break;
179 case '\r':
180 sb.append("\\r");
181 break;
182 default:
183 if ((c < ' ') || ((c >= '\u0080') && (c < '\u00a0')) || ((c >= '\u2000') && (c < '\u2100')))
184 {
185 t = "000" + Integer.toHexString(c);
186 sb.append("\\u").append(t.substring(t.length() - 4));
187 }
188 else
189 {
190 sb.append(c);
191 }
192 }
193 }
194 sb.append('"');
195 return sb.toString();
196 }
197
198 public String toString()
199 {
200 try
201 {
202 return '[' + join(",") + ']';
203 }
204 catch (final Exception e)
205 {
206 return "";
207 }
208 }
209 /**
210 * Tokenizer takes a source string and extracts characters and tokens from it. Adapted from JSONTokener in the
211 * json.org package.
212 * <p>
213 * Kept as an inner class - or rather ghetto - so outer class can deal with Iterator conversion. Not the
214 * prettiest code in the world by a long shot.
215 */
216 private static class Tokenizer
217 {
218 /**
219 * The source string being tokenized.
220 */
221 private final String source;
222
223 /**
224 * The index of the next character.
225 */
226 private int index = 0;
227
228 /**
229 * Construct a JSONTokener from a string.
230 *
231 * @param s
232 * A source string.
233 */
234 Tokenizer(String s)
235 {
236 source = s;
237 }
238
239 /**
240 * Back up one character. This provides a sort of lookahead capability, so that you can test for a digit or
241 * letter before attempting to parse the next number or identifier.
242 */
243 private void back()
244 {
245 if (index > 0)
246 {
247 index -= 1;
248 }
249 }
250
251 /**
252 * Determine if the source string still contains characters that next() can consume.
253 *
254 * @return true if not yet at the end of the source.
255 */
256 private boolean more()
257 {
258 return index < source.length();
259 }
260
261 /**
262 * Get the next character in the source string.
263 *
264 * @return The next character, or 0 if past the end of the source string.
265 */
266 private char next()
267 {
268 if (more())
269 {
270 final char c = source.charAt(index);
271 index += 1;
272 return c;
273 }
274 return 0;
275 }
276
277 /**
278 * Get the next n characters.
279 *
280 * @param n
281 * The number of characters to take.
282 * @return A string of n characters.
283 * @throws JSONException
284 * Substring bounds error if there are not n characters remaining in the source string.
285 */
286 private String next(int n)
287 {
288 final int i = index;
289 final int j = i + n;
290 if (j >= source.length())
291 {
292 throw new IllegalStateException("Substring bounds error");
293 }
294 index += n;
295 return source.substring(i, j);
296 }
297
298 /**
299 * Get the next char in the string, skipping whitespace and comments (slashslash, slashstar, and hash).
300 *
301 * @return A character, or 0 if there are no more characters.
302 * @throws JSONException
303 * in case of a syntax error
304 */
305 private char nextClean()
306 {
307 for (;;)
308 {
309 char c = next();
310 if (c == '/')
311 {
312 switch (next())
313 {
314 case '/':
315 do
316 {
317 c = next();
318 }
319 while ((c != '\n') && (c != '\r') && (c != 0));
320 break;
321 case '*':
322 for (;;)
323 {
324 c = next();
325 if (c == 0)
326 {
327 throw new IllegalStateException("Unclosed comment");
328 }
329 if (c == '*')
330 {
331 if (next() == '/')
332 {
333 break;
334 }
335 back();
336 }
337 }
338 break;
339 default:
340 back();
341 return '/';
342 }
343 }
344 else if (c == '#')
345 {
346 do
347 {
348 c = next();
349 }
350 while ((c != '\n') && (c != '\r') && (c != 0));
351 }
352 else if ((c == 0) || (c > ' '))
353 {
354 return c;
355 }
356 }
357 }
358
359 /**
360 * Return the characters up to the next close quote character. Backslash processing is done. The formal JSON
361 * format does not allow strings in single quotes, but an implementation is allowed to accept them.
362 *
363 * @param quote
364 * The quoting character, either <code>"</code> <small>(double quote)</small> or
365 * <code>'</code> <small>(single quote)</small>.
366 * @return A String.
367 * @throws JSONException
368 * Unterminated string.
369 */
370 private String nextString(char quote)
371 {
372 char c;
373 final StringBuffer sb = new StringBuffer();
374 for (;;)
375 {
376 c = next();
377 switch (c)
378 {
379 case 0:
380 case '\n':
381 case '\r':
382 throw new IllegalStateException("Unterminated string");
383 case '\\':
384 c = next();
385 switch (c)
386 {
387 case 'b':
388 sb.append('\b');
389 break;
390 case 't':
391 sb.append('\t');
392 break;
393 case 'n':
394 sb.append('\n');
395 break;
396 case 'f':
397 sb.append('\f');
398 break;
399 case 'r':
400 sb.append('\r');
401 break;
402 case 'u':
403 sb.append((char) Integer.parseInt(next(4), 16));
404 break;
405 case 'x':
406 sb.append((char) Integer.parseInt(next(2), 16));
407 break;
408 default:
409 sb.append(c);
410 }
411 break;
412 default:
413 if (c == quote)
414 {
415 return sb.toString();
416 }
417 sb.append(c);
418 }
419 }
420 }
421
422 /**
423 * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String,
424 * or the JSONObject.NULL object.
425 *
426 * @return An object.
427 * @throws JSONException
428 * If syntax error.
429 */
430 private String nextValue()
431 {
432 char c = nextClean();
433 String s;
434
435 switch (c)
436 {
437 case '"':
438 case '\'':
439 return nextString(c);
440 }
441
442 /*
443 * Handle unquoted text. This could be the values true, false, or null, or it can be a number. An
444 * implementation (such as this one) is allowed to also accept non-standard forms. Accumulate characters
445 * until we reach the end of the text or a formatting character.
446 */
447
448 final StringBuffer sb = new StringBuffer();
449 while ((c >= ' ') && (",:]}/\\\"[{;=#".indexOf(c) <= 0))
450 {
451 sb.append(c);
452 c = next();
453 }
454 back();
455
456 /*
457 * If it is true, false, or null, return the proper value.
458 */
459
460 s = sb.toString().trim();
461 if (s.equals(""))
462 {
463 throw new IllegalStateException("Missing value" + toString());
464 }
465 if (s.equalsIgnoreCase("null"))
466 {
467 return null;
468 }
469
470 return s;
471 }
472
473 void syntaxError(String message)
474 {
475 throw new IllegalStateException(message + toString());
476 }
477
478 public String toString()
479 {
480 return " at character " + index + " of " + source;
481 }
482 }
483 }
484 }