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(final String source)
34 {
35 Null.not("source", source);
36
37 final List<String> result = new JSONList(source.trim());
38 return result.toArray(new String[result.size()]);
39 }
40
41 static String toString(final 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<String>
52 {
53 private static final long serialVersionUID = -8317241062936626298L;
54
55 JSONList(final 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(final String[] source)
105 {
106 super(Arrays.asList(source));
107 }
108
109 String join(final 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(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(final 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 @Override
199 public String toString()
200 {
201 try
202 {
203 return '[' + join(",") + ']';
204 }
205 catch (final Exception e)
206 {
207 return "";
208 }
209 }
210
211 /**
212 * Tokenizer takes a source string and extracts characters and tokens from it. Adapted from JSONTokener in the
213 * json.org package.
214 * <p>
215 * Kept as an inner class - or rather ghetto - so outer class can deal with Iterator conversion. Not the
216 * prettiest code in the world by a long shot.
217 */
218 private static class Tokenizer
219 {
220 /**
221 * The source string being tokenized.
222 */
223 private final String source;
224
225 /**
226 * The index of the next character.
227 */
228 private int index = 0;
229
230 /**
231 * Construct a JSONTokener from a string.
232 *
233 * @param s
234 * A source string.
235 */
236 Tokenizer(final String s)
237 {
238 source = s;
239 }
240
241 /**
242 * Back up one character. This provides a sort of lookahead capability, so that you can test for a digit or
243 * letter before attempting to parse the next number or identifier.
244 */
245 private void back()
246 {
247 if (index > 0)
248 {
249 index -= 1;
250 }
251 }
252
253 /**
254 * Determine if the source string still contains characters that next() can consume.
255 *
256 * @return true if not yet at the end of the source.
257 */
258 private boolean more()
259 {
260 return index < source.length();
261 }
262
263 /**
264 * Get the next character in the source string.
265 *
266 * @return The next character, or 0 if past the end of the source string.
267 */
268 private char next()
269 {
270 if (more())
271 {
272 final char c = source.charAt(index);
273 index += 1;
274 return c;
275 }
276 return 0;
277 }
278
279 /**
280 * Get the next n characters.
281 *
282 * @param n
283 * The number of characters to take.
284 * @return A string of n characters.
285 * @throws JSONException
286 * Substring bounds error if there are not n characters remaining in the source string.
287 */
288 private String next(final int n)
289 {
290 final int i = index;
291 final int j = i + n;
292 if (j >= source.length())
293 {
294 throw new IllegalStateException("Substring bounds error");
295 }
296 index += n;
297 return source.substring(i, j);
298 }
299
300 /**
301 * Get the next char in the string, skipping whitespace and comments (slashslash, slashstar, and hash).
302 *
303 * @return A character, or 0 if there are no more characters.
304 * @throws JSONException
305 * in case of a syntax error
306 */
307 private char nextClean()
308 {
309 for (;;)
310 {
311 char c = next();
312 if (c == '/')
313 {
314 switch (next())
315 {
316 case '/':
317 do
318 {
319 c = next();
320 }
321 while ((c != '\n') && (c != '\r') && (c != 0));
322 break;
323 case '*':
324 for (;;)
325 {
326 c = next();
327 if (c == 0)
328 {
329 throw new IllegalStateException("Unclosed comment");
330 }
331 if (c == '*')
332 {
333 if (next() == '/')
334 {
335 break;
336 }
337 back();
338 }
339 }
340 break;
341 default:
342 back();
343 return '/';
344 }
345 }
346 else if (c == '#')
347 {
348 do
349 {
350 c = next();
351 }
352 while ((c != '\n') && (c != '\r') && (c != 0));
353 }
354 else if ((c == 0) || (c > ' '))
355 {
356 return c;
357 }
358 }
359 }
360
361 /**
362 * Return the characters up to the next close quote character. Backslash processing is done. The formal JSON
363 * format does not allow strings in single quotes, but an implementation is allowed to accept them.
364 *
365 * @param quote
366 * The quoting character, either <code>"</code> <small>(double quote)</small> or
367 * <code>'</code> <small>(single quote)</small>.
368 * @return A String.
369 * @throws JSONException
370 * Unterminated string.
371 */
372 private String nextString(final char quote)
373 {
374 char c;
375 final StringBuffer sb = new StringBuffer();
376 for (;;)
377 {
378 c = next();
379 switch (c)
380 {
381 case 0:
382 case '\n':
383 case '\r':
384 throw new IllegalStateException("Unterminated string");
385 case '\\':
386 c = next();
387 switch (c)
388 {
389 case 'b':
390 sb.append('\b');
391 break;
392 case 't':
393 sb.append('\t');
394 break;
395 case 'n':
396 sb.append('\n');
397 break;
398 case 'f':
399 sb.append('\f');
400 break;
401 case 'r':
402 sb.append('\r');
403 break;
404 case 'u':
405 sb.append((char) Integer.parseInt(next(4), 16));
406 break;
407 case 'x':
408 sb.append((char) Integer.parseInt(next(2), 16));
409 break;
410 default:
411 sb.append(c);
412 }
413 break;
414 default:
415 if (c == quote)
416 {
417 return sb.toString();
418 }
419 sb.append(c);
420 }
421 }
422 }
423
424 /**
425 * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String,
426 * or the JSONObject.NULL object.
427 *
428 * @return An object.
429 * @throws JSONException
430 * If syntax error.
431 */
432 private String nextValue()
433 {
434 char c = nextClean();
435 String s;
436
437 switch (c)
438 {
439 case '"':
440 case '\'':
441 return nextString(c);
442 }
443
444 /*
445 * Handle unquoted text. This could be the values true, false, or null, or it can be a number. An
446 * implementation (such as this one) is allowed to also accept non-standard forms. Accumulate characters
447 * until we reach the end of the text or a formatting character.
448 */
449
450 final StringBuffer sb = new StringBuffer();
451 while ((c >= ' ') && (",:]}/\\\"[{;=#".indexOf(c) <= 0))
452 {
453 sb.append(c);
454 c = next();
455 }
456 back();
457
458 /*
459 * If it is true, false, or null, return the proper value.
460 */
461
462 s = sb.toString().trim();
463 if (s.equals(""))
464 {
465 throw new IllegalStateException("Missing value" + toString());
466 }
467 if (s.equalsIgnoreCase("null"))
468 {
469 return null;
470 }
471
472 return s;
473 }
474
475 void syntaxError(final String message)
476 {
477 throw new IllegalStateException(message + toString());
478 }
479
480 @Override
481 public String toString()
482 {
483 return " at character " + index + " of " + source;
484 }
485 }
486 }
487 }