1 //
2 // ========================================================================
3 // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.util;
20
21 import java.io.UnsupportedEncodingException;
22 import java.nio.charset.StandardCharsets;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.eclipse.jetty.util.log.Log;
27 import org.eclipse.jetty.util.log.Logger;
28
29 /** Fast String Utilities.
30 *
31 * These string utilities provide both convenience methods and
32 * performance improvements over most standard library versions. The
33 * main aim of the optimizations is to avoid object creation unless
34 * absolutely required.
35 *
36 *
37 */
38 public class StringUtil
39 {
40 private static final Logger LOG = Log.getLogger(StringUtil.class);
41
42
43 private final static Trie<String> CHARSETS= new ArrayTrie<>(256);
44
45 public static final String ALL_INTERFACES="0.0.0.0";
46 public static final String CRLF="\015\012";
47
48 /** @deprecated use {@link System#lineSeparator()} instead */
49 @Deprecated
50 public static final String __LINE_SEPARATOR = System.lineSeparator();
51
52 public static final String __ISO_8859_1="iso-8859-1";
53 public final static String __UTF8="utf-8";
54 public final static String __UTF16="utf-16";
55
56 static
57 {
58 CHARSETS.put("utf-8",__UTF8);
59 CHARSETS.put("utf8",__UTF8);
60 CHARSETS.put("utf-16",__UTF16);
61 CHARSETS.put("utf16",__UTF16);
62 CHARSETS.put("iso-8859-1",__ISO_8859_1);
63 CHARSETS.put("iso_8859_1",__ISO_8859_1);
64 }
65
66 /* ------------------------------------------------------------ */
67 /** Convert alternate charset names (eg utf8) to normalized
68 * name (eg UTF-8).
69 * @param s the charset to normalize
70 * @return the normalized charset (or null if normalized version not found)
71 */
72 public static String normalizeCharset(String s)
73 {
74 String n=CHARSETS.get(s);
75 return (n==null)?s:n;
76 }
77
78 /* ------------------------------------------------------------ */
79 /** Convert alternate charset names (eg utf8) to normalized
80 * name (eg UTF-8).
81 * @param s the charset to normalize
82 * @param offset the offset in the charset
83 * @param length the length of the charset in the input param
84 * @return the normalized charset (or null if not found)
85 */
86 public static String normalizeCharset(String s,int offset,int length)
87 {
88 String n=CHARSETS.get(s,offset,length);
89 return (n==null)?s.substring(offset,offset+length):n;
90 }
91
92
93 /* ------------------------------------------------------------ */
94 public static final char[] lowercases = {
95 '\000','\001','\002','\003','\004','\005','\006','\007',
96 '\010','\011','\012','\013','\014','\015','\016','\017',
97 '\020','\021','\022','\023','\024','\025','\026','\027',
98 '\030','\031','\032','\033','\034','\035','\036','\037',
99 '\040','\041','\042','\043','\044','\045','\046','\047',
100 '\050','\051','\052','\053','\054','\055','\056','\057',
101 '\060','\061','\062','\063','\064','\065','\066','\067',
102 '\070','\071','\072','\073','\074','\075','\076','\077',
103 '\100','\141','\142','\143','\144','\145','\146','\147',
104 '\150','\151','\152','\153','\154','\155','\156','\157',
105 '\160','\161','\162','\163','\164','\165','\166','\167',
106 '\170','\171','\172','\133','\134','\135','\136','\137',
107 '\140','\141','\142','\143','\144','\145','\146','\147',
108 '\150','\151','\152','\153','\154','\155','\156','\157',
109 '\160','\161','\162','\163','\164','\165','\166','\167',
110 '\170','\171','\172','\173','\174','\175','\176','\177' };
111
112 /* ------------------------------------------------------------ */
113 /**
114 * fast lower case conversion. Only works on ascii (not unicode)
115 * @param s the string to convert
116 * @return a lower case version of s
117 */
118 public static String asciiToLowerCase(String s)
119 {
120 if (s == null)
121 return null;
122
123 char[] c = null;
124 int i=s.length();
125
126 // look for first conversion
127 while (i-->0)
128 {
129 char c1=s.charAt(i);
130 if (c1<=127)
131 {
132 char c2=lowercases[c1];
133 if (c1!=c2)
134 {
135 c=s.toCharArray();
136 c[i]=c2;
137 break;
138 }
139 }
140 }
141
142 while (i-->0)
143 {
144 if(c[i]<=127)
145 c[i] = lowercases[c[i]];
146 }
147
148 return c==null?s:new String(c);
149 }
150
151
152 /* ------------------------------------------------------------ */
153 public static boolean startsWithIgnoreCase(String s,String w)
154 {
155 if (w==null)
156 return true;
157
158 if (s==null || s.length()<w.length())
159 return false;
160
161 for (int i=0;i<w.length();i++)
162 {
163 char c1=s.charAt(i);
164 char c2=w.charAt(i);
165 if (c1!=c2)
166 {
167 if (c1<=127)
168 c1=lowercases[c1];
169 if (c2<=127)
170 c2=lowercases[c2];
171 if (c1!=c2)
172 return false;
173 }
174 }
175 return true;
176 }
177
178 /* ------------------------------------------------------------ */
179 public static boolean endsWithIgnoreCase(String s,String w)
180 {
181 if (w==null)
182 return true;
183
184 if (s==null)
185 return false;
186
187 int sl=s.length();
188 int wl=w.length();
189
190 if (sl<wl)
191 return false;
192
193 for (int i=wl;i-->0;)
194 {
195 char c1=s.charAt(--sl);
196 char c2=w.charAt(i);
197 if (c1!=c2)
198 {
199 if (c1<=127)
200 c1=lowercases[c1];
201 if (c2<=127)
202 c2=lowercases[c2];
203 if (c1!=c2)
204 return false;
205 }
206 }
207 return true;
208 }
209
210 /* ------------------------------------------------------------ */
211 /**
212 * returns the next index of a character from the chars string
213 * @param s the input string to search
214 * @param chars the chars to look for
215 * @return the index of the character in the input stream found.
216 */
217 public static int indexFrom(String s,String chars)
218 {
219 for (int i=0;i<s.length();i++)
220 if (chars.indexOf(s.charAt(i))>=0)
221 return i;
222 return -1;
223 }
224
225 /* ------------------------------------------------------------ */
226 /**
227 * replace substrings within string.
228 * @param s the input string
229 * @param sub the string to look for
230 * @param with the string to replace with
231 * @return the now replaced string
232 */
233 public static String replace(String s, String sub, String with)
234 {
235 int c=0;
236 int i=s.indexOf(sub,c);
237 if (i == -1)
238 return s;
239
240 StringBuilder buf = new StringBuilder(s.length()+with.length());
241
242 do
243 {
244 buf.append(s.substring(c,i));
245 buf.append(with);
246 c=i+sub.length();
247 } while ((i=s.indexOf(sub,c))!=-1);
248
249 if (c<s.length())
250 buf.append(s.substring(c,s.length()));
251
252 return buf.toString();
253 }
254
255 /* ------------------------------------------------------------ */
256 /** Remove single or double quotes.
257 * @param s the input string
258 * @return the string with quotes removed
259 */
260 @Deprecated
261 public static String unquote(String s)
262 {
263 return QuotedStringTokenizer.unquote(s);
264 }
265
266
267 /* ------------------------------------------------------------ */
268 /** Append substring to StringBuilder
269 * @param buf StringBuilder to append to
270 * @param s String to append from
271 * @param offset The offset of the substring
272 * @param length The length of the substring
273 */
274 public static void append(StringBuilder buf,
275 String s,
276 int offset,
277 int length)
278 {
279 synchronized(buf)
280 {
281 int end=offset+length;
282 for (int i=offset; i<end;i++)
283 {
284 if (i>=s.length())
285 break;
286 buf.append(s.charAt(i));
287 }
288 }
289 }
290
291
292 /* ------------------------------------------------------------ */
293 /**
294 * append hex digit
295 * @param buf the buffer to append to
296 * @param b the byte to append
297 * @param base the base of the hex output (almost always 16).
298 *
299 */
300 public static void append(StringBuilder buf,byte b,int base)
301 {
302 int bi=0xff&b;
303 int c='0'+(bi/base)%base;
304 if (c>'9')
305 c= 'a'+(c-'0'-10);
306 buf.append((char)c);
307 c='0'+bi%base;
308 if (c>'9')
309 c= 'a'+(c-'0'-10);
310 buf.append((char)c);
311 }
312
313 /* ------------------------------------------------------------ */
314 /**
315 * Append 2 digits (zero padded) to the StringBuffer
316 *
317 * @param buf the buffer to append to
318 * @param i the value to append
319 */
320 public static void append2digits(StringBuffer buf,int i)
321 {
322 if (i<100)
323 {
324 buf.append((char)(i/10+'0'));
325 buf.append((char)(i%10+'0'));
326 }
327 }
328
329 /* ------------------------------------------------------------ */
330 /**
331 * Append 2 digits (zero padded) to the StringBuilder
332 *
333 * @param buf the buffer to append to
334 * @param i the value to append
335 */
336 public static void append2digits(StringBuilder buf,int i)
337 {
338 if (i<100)
339 {
340 buf.append((char)(i/10+'0'));
341 buf.append((char)(i%10+'0'));
342 }
343 }
344
345 /* ------------------------------------------------------------ */
346 /** Return a non null string.
347 * @param s String
348 * @return The string passed in or empty string if it is null.
349 */
350 public static String nonNull(String s)
351 {
352 if (s==null)
353 return "";
354 return s;
355 }
356
357 /* ------------------------------------------------------------ */
358 public static boolean equals(String s,char[] buf, int offset, int length)
359 {
360 if (s.length()!=length)
361 return false;
362 for (int i=0;i<length;i++)
363 if (buf[offset+i]!=s.charAt(i))
364 return false;
365 return true;
366 }
367
368 /* ------------------------------------------------------------ */
369 public static String toUTF8String(byte[] b,int offset,int length)
370 {
371 return new String(b,offset,length,StandardCharsets.UTF_8);
372 }
373
374 /* ------------------------------------------------------------ */
375 public static String toString(byte[] b,int offset,int length,String charset)
376 {
377 try
378 {
379 return new String(b,offset,length,charset);
380 }
381 catch (UnsupportedEncodingException e)
382 {
383 throw new IllegalArgumentException(e);
384 }
385 }
386
387 /**
388 * Find the index of a control characters in String
389 * <p>
390 * This will return a result on the first occurrence of a control character, regardless if
391 * there are more than one.
392 * </p>
393 * <p>
394 * Note: uses codepoint version of {@link Character#isISOControl(int)} to support Unicode better.
395 * </p>
396 *
397 * <pre>
398 * indexOfControlChars(null) == -1
399 * indexOfControlChars("") == -1
400 * indexOfControlChars("\r\n") == 0
401 * indexOfControlChars("\t") == 0
402 * indexOfControlChars(" ") == -1
403 * indexOfControlChars("a") == -1
404 * indexOfControlChars(".") == -1
405 * indexOfControlChars(";\n") == 1
406 * indexOfControlChars("abc\f") == 3
407 * indexOfControlChars("z\010") == 1
408 * indexOfControlChars(":\u001c") == 1
409 * </pre>
410 *
411 * @param str
412 * the string to test.
413 * @return the index of first control character in string, -1 if no control characters encountered
414 */
415 public static int indexOfControlChars(String str)
416 {
417 if (str == null)
418 {
419 return -1;
420 }
421 int len = str.length();
422 for (int i = 0; i < len; i++)
423 {
424 if (Character.isISOControl(str.codePointAt(i)))
425 {
426 // found a control character, we can stop searching now
427 return i;
428 }
429 }
430 // no control characters
431 return -1;
432 }
433
434 /* ------------------------------------------------------------ */
435 /**
436 * Test if a string is null or only has whitespace characters in it.
437 * <p>
438 * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
439 *
440 * <pre>
441 * isBlank(null) == true
442 * isBlank("") == true
443 * isBlank("\r\n") == true
444 * isBlank("\t") == true
445 * isBlank(" ") == true
446 * isBlank("a") == false
447 * isBlank(".") == false
448 * isBlank(";\n") == false
449 * </pre>
450 *
451 * @param str
452 * the string to test.
453 * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
454 */
455 public static boolean isBlank(String str)
456 {
457 if (str == null)
458 {
459 return true;
460 }
461 int len = str.length();
462 for (int i = 0; i < len; i++)
463 {
464 if (!Character.isWhitespace(str.codePointAt(i)))
465 {
466 // found a non-whitespace, we can stop searching now
467 return false;
468 }
469 }
470 // only whitespace
471 return true;
472 }
473
474 /* ------------------------------------------------------------ */
475 /**
476 * Test if a string is not null and contains at least 1 non-whitespace characters in it.
477 * <p>
478 * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
479 *
480 * <pre>
481 * isNotBlank(null) == false
482 * isNotBlank("") == false
483 * isNotBlank("\r\n") == false
484 * isNotBlank("\t") == false
485 * isNotBlank(" ") == false
486 * isNotBlank("a") == true
487 * isNotBlank(".") == true
488 * isNotBlank(";\n") == true
489 * </pre>
490 *
491 * @param str
492 * the string to test.
493 * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
494 */
495 public static boolean isNotBlank(String str)
496 {
497 if (str == null)
498 {
499 return false;
500 }
501 int len = str.length();
502 for (int i = 0; i < len; i++)
503 {
504 if (!Character.isWhitespace(str.codePointAt(i)))
505 {
506 // found a non-whitespace, we can stop searching now
507 return true;
508 }
509 }
510 // only whitespace
511 return false;
512 }
513
514 /* ------------------------------------------------------------ */
515 public static boolean isUTF8(String charset)
516 {
517 return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
518 }
519
520
521 /* ------------------------------------------------------------ */
522 public static String printable(String name)
523 {
524 if (name==null)
525 return null;
526 StringBuilder buf = new StringBuilder(name.length());
527 for (int i=0;i<name.length();i++)
528 {
529 char c=name.charAt(i);
530 if (!Character.isISOControl(c))
531 buf.append(c);
532 }
533 return buf.toString();
534 }
535
536 /* ------------------------------------------------------------ */
537 public static String printable(byte[] b)
538 {
539 StringBuilder buf = new StringBuilder();
540 for (int i=0;i<b.length;i++)
541 {
542 char c=(char)b[i];
543 if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
544 buf.append(c);
545 else
546 {
547 buf.append("0x");
548 TypeUtil.toHex(b[i],buf);
549 }
550 }
551 return buf.toString();
552 }
553
554 public static byte[] getBytes(String s)
555 {
556 return s.getBytes(StandardCharsets.ISO_8859_1);
557 }
558
559 public static byte[] getUtf8Bytes(String s)
560 {
561 return s.getBytes(StandardCharsets.UTF_8);
562 }
563
564 public static byte[] getBytes(String s,String charset)
565 {
566 try
567 {
568 return s.getBytes(charset);
569 }
570 catch(Exception e)
571 {
572 LOG.warn(e);
573 return s.getBytes();
574 }
575 }
576
577
578
579 /**
580 * Converts a binary SID to a string SID
581 *
582 * http://en.wikipedia.org/wiki/Security_Identifier
583 *
584 * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
585 * @param sidBytes the SID bytes to build from
586 * @return the string SID
587 */
588 public static String sidBytesToString(byte[] sidBytes)
589 {
590 StringBuilder sidString = new StringBuilder();
591
592 // Identify this as a SID
593 sidString.append("S-");
594
595 // Add SID revision level (expect 1 but may change someday)
596 sidString.append(Byte.toString(sidBytes[0])).append('-');
597
598 StringBuilder tmpBuilder = new StringBuilder();
599
600 // crunch the six bytes of issuing authority value
601 for (int i = 2; i <= 7; ++i)
602 {
603 tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
604 }
605
606 sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
607
608 // the number of subAuthorities we need to attach
609 int subAuthorityCount = sidBytes[1];
610
611 // attach each of the subAuthorities
612 for (int i = 0; i < subAuthorityCount; ++i)
613 {
614 int offset = i * 4;
615 tmpBuilder.setLength(0);
616 // these need to be zero padded hex and little endian
617 tmpBuilder.append(String.format("%02X%02X%02X%02X",
618 (sidBytes[11 + offset] & 0xFF),
619 (sidBytes[10 + offset] & 0xFF),
620 (sidBytes[9 + offset] & 0xFF),
621 (sidBytes[8 + offset] & 0xFF)));
622 sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
623 }
624
625 return sidString.toString();
626 }
627
628 /**
629 * Converts a string SID to a binary SID
630 *
631 * http://en.wikipedia.org/wiki/Security_Identifier
632 *
633 * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
634 * @param sidString the string SID
635 * @return the binary SID
636 */
637 public static byte[] sidStringToBytes( String sidString )
638 {
639 String[] sidTokens = sidString.split("-");
640
641 int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
642
643 int byteCount = 0;
644 byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
645
646 // the revision byte
647 sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
648
649 // the # of sub authorities byte
650 sidBytes[byteCount++] = (byte)subAuthorityCount;
651
652 // the certAuthority
653 String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
654
655 while( hexStr.length() < 12) // pad to 12 characters
656 {
657 hexStr = "0" + hexStr;
658 }
659
660 // place the certAuthority 6 bytes
661 for ( int i = 0 ; i < hexStr.length(); i = i + 2)
662 {
663 sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
664 }
665
666
667 for ( int i = 3; i < sidTokens.length ; ++i)
668 {
669 hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
670
671 while( hexStr.length() < 8) // pad to 8 characters
672 {
673 hexStr = "0" + hexStr;
674 }
675
676 // place the inverted sub authorities, 4 bytes each
677 for ( int j = hexStr.length(); j > 0; j = j - 2)
678 {
679 sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
680 }
681 }
682
683 return sidBytes;
684 }
685
686 /**
687 * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
688 *
689 * @param string A String containing an integer.
690 * @param from The index to start parsing from
691 * @return an int
692 */
693 public static int toInt(String string,int from)
694 {
695 int val = 0;
696 boolean started = false;
697 boolean minus = false;
698
699 for (int i = from; i < string.length(); i++)
700 {
701 char b = string.charAt(i);
702 if (b <= ' ')
703 {
704 if (started)
705 break;
706 }
707 else if (b >= '0' && b <= '9')
708 {
709 val = val * 10 + (b - '0');
710 started = true;
711 }
712 else if (b == '-' && !started)
713 {
714 minus = true;
715 }
716 else
717 break;
718 }
719
720 if (started)
721 return minus?(-val):val;
722 throw new NumberFormatException(string);
723 }
724
725 /**
726 * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
727 *
728 * @param string
729 * A String containing an integer.
730 * @return an int
731 */
732 public static long toLong(String string)
733 {
734 long val = 0;
735 boolean started = false;
736 boolean minus = false;
737
738 for (int i = 0; i < string.length(); i++)
739 {
740 char b = string.charAt(i);
741 if (b <= ' ')
742 {
743 if (started)
744 break;
745 }
746 else if (b >= '0' && b <= '9')
747 {
748 val = val * 10L + (b - '0');
749 started = true;
750 }
751 else if (b == '-' && !started)
752 {
753 minus = true;
754 }
755 else
756 break;
757 }
758
759 if (started)
760 return minus?(-val):val;
761 throw new NumberFormatException(string);
762 }
763
764 /**
765 * Truncate a string to a max size.
766 *
767 * @param str the string to possibly truncate
768 * @param maxSize the maximum size of the string
769 * @return the truncated string. if <code>str</code> param is null, then the returned string will also be null.
770 */
771 public static String truncate(String str, int maxSize)
772 {
773 if (str == null)
774 {
775 return null;
776 }
777
778 if (str.length() <= maxSize)
779 {
780 return str;
781 }
782
783 return str.substring(0,maxSize);
784 }
785
786 /**
787 * Parse the string representation of a list using {@link #csvSplit(List,String,int,int)}
788 * @param s The string to parse, expected to be enclosed as '[...]'
789 * @return An array of parsed values.
790 */
791 public static String[] arrayFromString(String s)
792 {
793 if (s==null)
794 return new String[]{};
795
796 if (!s.startsWith("[") || !s.endsWith("]"))
797 throw new IllegalArgumentException();
798 if (s.length()==2)
799 return new String[]{};
800
801 return csvSplit(s,1,s.length()-2);
802 }
803
804 /**
805 * Parse a CSV string using {@link #csvSplit(List,String, int, int)}
806 * @param s The string to parse
807 * @return An array of parsed values.
808 */
809 public static String[] csvSplit(String s)
810 {
811 if (s==null)
812 return null;
813 return csvSplit(s,0,s.length());
814 }
815
816 /**
817 * Parse a CSV string using {@link #csvSplit(List,String, int, int)}
818 * @param s The string to parse
819 * @param off The offset into the string to start parsing
820 * @param len The len in characters to parse
821 * @return An array of parsed values.
822 */
823 public static String[] csvSplit(String s, int off,int len)
824 {
825 if (s==null)
826 return null;
827 if (off<0 || len<0 || off>s.length())
828 throw new IllegalArgumentException();
829
830 List<String> list = new ArrayList<>();
831 csvSplit(list,s,off,len);
832 return list.toArray(new String[list.size()]);
833 }
834
835 enum CsvSplitState { PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA };
836
837 /** Split a quoted comma separated string to a list
838 * <p>Handle <a href="https://www.ietf.org/rfc/rfc4180.txt">rfc4180</a>-like
839 * CSV strings, with the exceptions:<ul>
840 * <li>quoted values may contain double quotes escaped with back-slash
841 * <li>Non-quoted values are trimmed of leading trailing white space
842 * <li>trailing commas are ignored
843 * <li>double commas result in a empty string value
844 * </ul>
845 * @param list The Collection to split to (or null to get a new list)
846 * @param s The string to parse
847 * @param off The offset into the string to start parsing
848 * @param len The len in characters to parse
849 * @return list containing the parsed list values
850 */
851 public static List<String> csvSplit(List<String> list,String s, int off,int len)
852 {
853 if (list==null)
854 list=new ArrayList<>();
855 CsvSplitState state = CsvSplitState.PRE_DATA;
856 StringBuilder out = new StringBuilder();
857 int last=-1;
858 while (len>0)
859 {
860 char ch = s.charAt(off++);
861 len--;
862
863 switch(state)
864 {
865 case PRE_DATA:
866 if (Character.isWhitespace(ch))
867 continue;
868
869 if ('"'==ch)
870 {
871 state=CsvSplitState.QUOTE;
872 continue;
873 }
874
875 if (','==ch)
876 {
877 list.add("");
878 continue;
879 }
880
881 state=CsvSplitState.DATA;
882 out.append(ch);
883 continue;
884
885 case DATA:
886 if (Character.isWhitespace(ch))
887 {
888 last=out.length();
889 out.append(ch);
890 state=CsvSplitState.WHITE;
891 continue;
892 }
893
894 if (','==ch)
895 {
896 list.add(out.toString());
897 out.setLength(0);
898 state=CsvSplitState.PRE_DATA;
899 continue;
900 }
901
902 out.append(ch);
903 continue;
904
905 case WHITE:
906 if (Character.isWhitespace(ch))
907 {
908 out.append(ch);
909 continue;
910 }
911
912 if (','==ch)
913 {
914 out.setLength(last);
915 list.add(out.toString());
916 out.setLength(0);
917 state=CsvSplitState.PRE_DATA;
918 continue;
919 }
920
921 state=CsvSplitState.DATA;
922 out.append(ch);
923 last=-1;
924 continue;
925
926 case QUOTE:
927 if ('\\'==ch)
928 {
929 state=CsvSplitState.SLOSH;
930 continue;
931 }
932 if ('"'==ch)
933 {
934 list.add(out.toString());
935 out.setLength(0);
936 state=CsvSplitState.POST_DATA;
937 continue;
938 }
939 out.append(ch);
940 continue;
941
942 case SLOSH:
943 out.append(ch);
944 state=CsvSplitState.QUOTE;
945 continue;
946
947 case POST_DATA:
948 if (','==ch)
949 {
950 state=CsvSplitState.PRE_DATA;
951 continue;
952 }
953 continue;
954 }
955 }
956
957 switch(state)
958 {
959 case PRE_DATA:
960 case POST_DATA:
961 break;
962
963 case DATA:
964 case QUOTE:
965 case SLOSH:
966 list.add(out.toString());
967 break;
968
969 case WHITE:
970 out.setLength(last);
971 list.add(out.toString());
972 break;
973 }
974
975 return list;
976 }
977
978 public static String sanitizeXmlString(String html)
979 {
980 if (html==null)
981 return null;
982
983 int i=0;
984
985 // Are there any characters that need sanitizing?
986 loop: for (;i<html.length();i++)
987 {
988 char c=html.charAt(i);
989
990 switch(c)
991 {
992 case '&' :
993 case '<' :
994 case '>' :
995 case '\'':
996 case '"':
997 break loop;
998
999 default:
1000 if (Character.isISOControl(c) && !Character.isWhitespace(c))
1001 break loop;
1002 }
1003 }
1004
1005 // No characters need sanitizing, so return original string
1006 if (i==html.length())
1007 return html;
1008
1009 // Create builder with OK content so far
1010 StringBuilder out = new StringBuilder(html.length()*4/3);
1011 out.append(html,0,i);
1012
1013 // sanitize remaining content
1014 for (;i<html.length();i++)
1015 {
1016 char c=html.charAt(i);
1017
1018 switch(c)
1019 {
1020 case '&' :
1021 out.append("&");
1022 break;
1023 case '<' :
1024 out.append("<");
1025 break;
1026 case '>' :
1027 out.append(">");
1028 break;
1029 case '\'':
1030 out.append("'");
1031 break;
1032 case '"':
1033 out.append(""");
1034 break;
1035
1036 default:
1037 if (Character.isISOControl(c) && !Character.isWhitespace(c))
1038 out.append('?');
1039 else
1040 out.append(c);
1041 }
1042 }
1043 return out.toString();
1044 }
1045
1046 /* ------------------------------------------------------------ */
1047 /** The String value of an Object
1048 * <p>This method calls {@link String#valueOf(Object)} unless the object is null,
1049 * in which case null is returned</p>
1050 * @param object The object
1051 * @return String value or null
1052 */
1053 public static String valueOf(Object object)
1054 {
1055 return object==null?null:String.valueOf(object);
1056 }
1057
1058 }