View Javadoc

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>&nbsp;<small>(double quote)</small> or
365              *            <code>'</code>&nbsp;<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 }