View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.Charset;
23  import java.nio.charset.StandardCharsets;
24  
25  import org.eclipse.jetty.util.log.Log;
26  import org.eclipse.jetty.util.log.Logger;
27  
28  /** Fast String Utilities.
29   *
30   * These string utilities provide both convenience methods and
31   * performance improvements over most standard library versions. The
32   * main aim of the optimizations is to avoid object creation unless
33   * absolutely required.
34   *
35   * 
36   */
37  public class StringUtil
38  {
39      private static final Logger LOG = Log.getLogger(StringUtil.class);
40      
41      
42      private final static Trie<String> CHARSETS= new ArrayTrie<>(256);
43      
44      public static final String ALL_INTERFACES="0.0.0.0";
45      public static final String CRLF="\015\012";
46      
47      /** @deprecated use {@link System#lineSeparator()} instead */
48      @Deprecated
49      public static final String __LINE_SEPARATOR = System.lineSeparator();
50         
51      public static final String __ISO_8859_1="ISO-8859-1";
52      public final static String __UTF8="UTF-8";
53      public final static String __UTF16="UTF-16";
54  
55      /**
56       * @deprecated Use {@link StandardCharsets#UTF_8}
57       */
58      @Deprecated
59      public final static Charset __UTF8_CHARSET=StandardCharsets.UTF_8;
60      /**
61       * @deprecated Use {@link StandardCharsets#ISO_8859_1}
62       */
63      @Deprecated
64      public final static Charset __ISO_8859_1_CHARSET=StandardCharsets.ISO_8859_1;
65      /**
66       * @deprecated Use {@link StandardCharsets#UTF_16}
67       */
68      @Deprecated
69      public final static Charset __UTF16_CHARSET=StandardCharsets.UTF_16;
70      /**
71       * @deprecated Use {@link StandardCharsets#US_ASCII}
72       */
73      @Deprecated
74      public final static Charset __US_ASCII_CHARSET=StandardCharsets.US_ASCII;
75      
76      static
77      {
78          CHARSETS.put("UTF-8",__UTF8);
79          CHARSETS.put("UTF8",__UTF8);
80          CHARSETS.put("UTF-16",__UTF16);
81          CHARSETS.put("UTF16",__UTF16);
82          CHARSETS.put("ISO-8859-1",__ISO_8859_1);
83          CHARSETS.put("ISO_8859_1",__ISO_8859_1);
84      }
85      
86      /* ------------------------------------------------------------ */
87      /** Convert alternate charset names (eg utf8) to normalized
88       * name (eg UTF-8).
89       */
90      public static String normalizeCharset(String s)
91      {
92          String n=CHARSETS.get(s);
93          return (n==null)?s:n;
94      }
95      
96      /* ------------------------------------------------------------ */
97      /** Convert alternate charset names (eg utf8) to normalized
98       * name (eg UTF-8).
99       */
100     public static String normalizeCharset(String s,int offset,int length)
101     {
102         String n=CHARSETS.get(s,offset,length);       
103         return (n==null)?s.substring(offset,offset+length):n;
104     }
105     
106 
107     /* ------------------------------------------------------------ */
108     public static final char[] lowercases = {
109           '\000','\001','\002','\003','\004','\005','\006','\007',
110           '\010','\011','\012','\013','\014','\015','\016','\017',
111           '\020','\021','\022','\023','\024','\025','\026','\027',
112           '\030','\031','\032','\033','\034','\035','\036','\037',
113           '\040','\041','\042','\043','\044','\045','\046','\047',
114           '\050','\051','\052','\053','\054','\055','\056','\057',
115           '\060','\061','\062','\063','\064','\065','\066','\067',
116           '\070','\071','\072','\073','\074','\075','\076','\077',
117           '\100','\141','\142','\143','\144','\145','\146','\147',
118           '\150','\151','\152','\153','\154','\155','\156','\157',
119           '\160','\161','\162','\163','\164','\165','\166','\167',
120           '\170','\171','\172','\133','\134','\135','\136','\137',
121           '\140','\141','\142','\143','\144','\145','\146','\147',
122           '\150','\151','\152','\153','\154','\155','\156','\157',
123           '\160','\161','\162','\163','\164','\165','\166','\167',
124           '\170','\171','\172','\173','\174','\175','\176','\177' };
125 
126     /* ------------------------------------------------------------ */
127     /**
128      * fast lower case conversion. Only works on ascii (not unicode)
129      * @param s the string to convert
130      * @return a lower case version of s
131      */
132     public static String asciiToLowerCase(String s)
133     {
134         char[] c = null;
135         int i=s.length();
136 
137         // look for first conversion
138         while (i-->0)
139         {
140             char c1=s.charAt(i);
141             if (c1<=127)
142             {
143                 char c2=lowercases[c1];
144                 if (c1!=c2)
145                 {
146                     c=s.toCharArray();
147                     c[i]=c2;
148                     break;
149                 }
150             }
151         }
152 
153         while (i-->0)
154         {
155             if(c[i]<=127)
156                 c[i] = lowercases[c[i]];
157         }
158         
159         return c==null?s:new String(c);
160     }
161 
162 
163     /* ------------------------------------------------------------ */
164     public static boolean startsWithIgnoreCase(String s,String w)
165     {
166         if (w==null)
167             return true;
168         
169         if (s==null || s.length()<w.length())
170             return false;
171         
172         for (int i=0;i<w.length();i++)
173         {
174             char c1=s.charAt(i);
175             char c2=w.charAt(i);
176             if (c1!=c2)
177             {
178                 if (c1<=127)
179                     c1=lowercases[c1];
180                 if (c2<=127)
181                     c2=lowercases[c2];
182                 if (c1!=c2)
183                     return false;
184             }
185         }
186         return true;
187     }
188     
189     /* ------------------------------------------------------------ */
190     public static boolean endsWithIgnoreCase(String s,String w)
191     {
192         if (w==null)
193             return true;
194 
195         if (s==null)
196             return false;
197             
198         int sl=s.length();
199         int wl=w.length();
200         
201         if (sl<wl)
202             return false;
203         
204         for (int i=wl;i-->0;)
205         {
206             char c1=s.charAt(--sl);
207             char c2=w.charAt(i);
208             if (c1!=c2)
209             {
210                 if (c1<=127)
211                     c1=lowercases[c1];
212                 if (c2<=127)
213                     c2=lowercases[c2];
214                 if (c1!=c2)
215                     return false;
216             }
217         }
218         return true;
219     }
220     
221     /* ------------------------------------------------------------ */
222     /**
223      * returns the next index of a character from the chars string
224      */
225     public static int indexFrom(String s,String chars)
226     {
227         for (int i=0;i<s.length();i++)
228            if (chars.indexOf(s.charAt(i))>=0)
229               return i;
230         return -1;
231     }
232     
233     /* ------------------------------------------------------------ */
234     /**
235      * replace substrings within string.
236      */
237     public static String replace(String s, String sub, String with)
238     {
239         int c=0;
240         int i=s.indexOf(sub,c);
241         if (i == -1)
242             return s;
243     
244         StringBuilder buf = new StringBuilder(s.length()+with.length());
245 
246         do
247         {
248             buf.append(s.substring(c,i));
249             buf.append(with);
250             c=i+sub.length();
251         } while ((i=s.indexOf(sub,c))!=-1);
252 
253         if (c<s.length())
254             buf.append(s.substring(c,s.length()));
255 
256         return buf.toString();
257         
258     }
259 
260 
261     /* ------------------------------------------------------------ */
262     /** Remove single or double quotes.
263      */
264     public static String unquote(String s)
265     {
266         return QuotedStringTokenizer.unquote(s);
267     }
268 
269 
270     /* ------------------------------------------------------------ */
271     /** Append substring to StringBuilder 
272      * @param buf StringBuilder to append to
273      * @param s String to append from
274      * @param offset The offset of the substring
275      * @param length The length of the substring
276      */
277     public static void append(StringBuilder buf,
278                               String s,
279                               int offset,
280                               int length)
281     {
282         synchronized(buf)
283         {
284             int end=offset+length;
285             for (int i=offset; i<end;i++)
286             {
287                 if (i>=s.length())
288                     break;
289                 buf.append(s.charAt(i));
290             }
291         }
292     }
293 
294     
295     /* ------------------------------------------------------------ */
296     /**
297      * append hex digit
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     public static void append2digits(StringBuffer buf,int i)
315     {
316         if (i<100)
317         {
318             buf.append((char)(i/10+'0'));
319             buf.append((char)(i%10+'0'));
320         }
321     }
322     
323     /* ------------------------------------------------------------ */
324     public static void append2digits(StringBuilder buf,int i)
325     {
326         if (i<100)
327         {
328             buf.append((char)(i/10+'0'));
329             buf.append((char)(i%10+'0'));
330         }
331     }
332     
333     /* ------------------------------------------------------------ */
334     /** Return a non null string.
335      * @param s String
336      * @return The string passed in or empty string if it is null. 
337      */
338     public static String nonNull(String s)
339     {
340         if (s==null)
341             return "";
342         return s;
343     }
344     
345     /* ------------------------------------------------------------ */
346     public static boolean equals(String s,char[] buf, int offset, int length)
347     {
348         if (s.length()!=length)
349             return false;
350         for (int i=0;i<length;i++)
351             if (buf[offset+i]!=s.charAt(i))
352                 return false;
353         return true;
354     }
355 
356     /* ------------------------------------------------------------ */
357     public static String toUTF8String(byte[] b,int offset,int length)
358     {
359         return new String(b,offset,length,StandardCharsets.UTF_8);
360     }
361 
362     /* ------------------------------------------------------------ */
363     public static String toString(byte[] b,int offset,int length,String charset)
364     {
365         try
366         {
367             return new String(b,offset,length,charset);
368         }
369         catch (UnsupportedEncodingException e)
370         {
371             throw new IllegalArgumentException(e);
372         }
373     }
374 
375     /* ------------------------------------------------------------ */
376     /**
377      * Test if a string is null or only has whitespace characters in it.
378      * <p>
379      * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
380      * 
381      * <pre>
382      *   isBlank(null)   == true
383      *   isBlank("")     == true
384      *   isBlank("\r\n") == true
385      *   isBlank("\t")   == true
386      *   isBlank("   ")  == true
387      *   isBlank("a")    == false
388      *   isBlank(".")    == false
389      *   isBlank(";\n")  == false
390      * </pre>
391      * 
392      * @param str
393      *            the string to test.
394      * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
395      */
396     public static boolean isBlank(String str)
397     {
398         if (str == null)
399         {
400             return true;
401         }
402         int len = str.length();
403         for (int i = 0; i < len; i++)
404         {
405             if (!Character.isWhitespace(str.codePointAt(i)))
406             {
407                 // found a non-whitespace, we can stop searching  now
408                 return false;
409             }
410         }
411         // only whitespace
412         return true;
413     }
414     
415     /* ------------------------------------------------------------ */
416     /**
417      * Test if a string is not null and contains at least 1 non-whitespace characters in it.
418      * <p>
419      * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
420      * 
421      * <pre>
422      *   isNotBlank(null)   == false
423      *   isNotBlank("")     == false
424      *   isNotBlank("\r\n") == false
425      *   isNotBlank("\t")   == false
426      *   isNotBlank("   ")  == false
427      *   isNotBlank("a")    == true
428      *   isNotBlank(".")    == true
429      *   isNotBlank(";\n")  == true
430      * </pre>
431      * 
432      * @param str
433      *            the string to test.
434      * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
435      */
436     public static boolean isNotBlank(String str)
437     {
438         if (str == null)
439         {
440             return false;
441         }
442         int len = str.length();
443         for (int i = 0; i < len; i++)
444         {
445             if (!Character.isWhitespace(str.codePointAt(i)))
446             {
447                 // found a non-whitespace, we can stop searching  now
448                 return true;
449             }
450         }
451         // only whitespace
452         return false;
453     }
454 
455     /* ------------------------------------------------------------ */
456     public static boolean isUTF8(String charset)
457     {
458         return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
459     }
460 
461 
462     /* ------------------------------------------------------------ */
463     public static String printable(String name)
464     {
465         if (name==null)
466             return null;
467         StringBuilder buf = new StringBuilder(name.length());
468         for (int i=0;i<name.length();i++)
469         {
470             char c=name.charAt(i);
471             if (!Character.isISOControl(c))
472                 buf.append(c);
473         }
474         return buf.toString();
475     }
476     
477     /* ------------------------------------------------------------ */
478     public static String printable(byte[] b)
479     {
480         StringBuilder buf = new StringBuilder();
481         for (int i=0;i<b.length;i++)
482         {
483             char c=(char)b[i];
484             if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
485                 buf.append(c);
486             else 
487             {
488                 buf.append("0x");
489                 TypeUtil.toHex(b[i],buf);
490             }
491         }
492         return buf.toString();
493     }
494     
495     public static byte[] getBytes(String s)
496     {
497         return s.getBytes(StandardCharsets.ISO_8859_1);
498     }
499     
500     public static byte[] getUtf8Bytes(String s)
501     {
502         return s.getBytes(StandardCharsets.UTF_8);
503     }
504     
505     public static byte[] getBytes(String s,String charset)
506     {
507         try
508         {
509             return s.getBytes(charset);
510         }
511         catch(Exception e)
512         {
513             LOG.warn(e);
514             return s.getBytes();
515         }
516     }
517     
518     
519     
520     /**
521      * Converts a binary SID to a string SID
522      * 
523      * http://en.wikipedia.org/wiki/Security_Identifier
524      * 
525      * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
526      */
527     public static String sidBytesToString(byte[] sidBytes)
528     {
529         StringBuilder sidString = new StringBuilder();
530         
531         // Identify this as a SID
532         sidString.append("S-");
533         
534         // Add SID revision level (expect 1 but may change someday)
535         sidString.append(Byte.toString(sidBytes[0])).append('-');
536         
537         StringBuilder tmpBuilder = new StringBuilder();
538         
539         // crunch the six bytes of issuing authority value
540         for (int i = 2; i <= 7; ++i)
541         {
542             tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
543         }
544         
545         sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
546    
547         // the number of subAuthorities we need to attach
548         int subAuthorityCount = sidBytes[1];
549 
550         // attach each of the subAuthorities
551         for (int i = 0; i < subAuthorityCount; ++i)
552         {
553             int offset = i * 4;
554             tmpBuilder.setLength(0);
555             // these need to be zero padded hex and little endian
556             tmpBuilder.append(String.format("%02X%02X%02X%02X", 
557                     (sidBytes[11 + offset] & 0xFF),
558                     (sidBytes[10 + offset] & 0xFF),
559                     (sidBytes[9 + offset] & 0xFF),
560                     (sidBytes[8 + offset] & 0xFF)));  
561             sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
562         }
563         
564         return sidString.toString();
565     }
566     
567     /**
568      * Converts a string SID to a binary SID
569      * 
570      * http://en.wikipedia.org/wiki/Security_Identifier
571      * 
572      * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
573      */
574     public static byte[] sidStringToBytes( String sidString )
575     {
576         String[] sidTokens = sidString.split("-");
577         
578         int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
579         
580         int byteCount = 0;
581         byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
582         
583         // the revision byte
584         sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
585 
586         // the # of sub authorities byte
587         sidBytes[byteCount++] = (byte)subAuthorityCount;
588 
589         // the certAuthority
590         String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
591         
592         while( hexStr.length() < 12) // pad to 12 characters
593         {
594             hexStr = "0" + hexStr;
595         }
596 
597         // place the certAuthority 6 bytes
598         for ( int i = 0 ; i < hexStr.length(); i = i + 2)
599         {
600             sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
601         }
602                 
603         
604         for ( int i = 3; i < sidTokens.length ; ++i)
605         {
606             hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
607             
608             while( hexStr.length() < 8) // pad to 8 characters
609             {
610                 hexStr = "0" + hexStr;
611             }     
612             
613             // place the inverted sub authorities, 4 bytes each
614             for ( int j = hexStr.length(); j > 0; j = j - 2)
615             {          
616                 sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
617             }
618         }
619       
620         return sidBytes;
621     }
622     
623 
624     /**
625      * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
626      * 
627      * @param string
628      *            A String containing an integer.
629      * @return an int
630      */
631     public static int toInt(String string)
632     {
633         int val = 0;
634         boolean started = false;
635         boolean minus = false;
636 
637         for (int i = 0; i < string.length(); i++)
638         {
639             char b = string.charAt(i);
640             if (b <= ' ')
641             {
642                 if (started)
643                     break;
644             }
645             else if (b >= '0' && b <= '9')
646             {
647                 val = val * 10 + (b - '0');
648                 started = true;
649             }
650             else if (b == '-' && !started)
651             {
652                 minus = true;
653             }
654             else
655                 break;
656         }
657 
658         if (started)
659             return minus?(-val):val;
660         throw new NumberFormatException(string);
661     }
662 
663     /**
664      * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
665      * 
666      * @param string
667      *            A String containing an integer.
668      * @return an int
669      */
670     public static long toLong(String string)
671     {
672         long val = 0;
673         boolean started = false;
674         boolean minus = false;
675 
676         for (int i = 0; i < string.length(); i++)
677         {
678             char b = string.charAt(i);
679             if (b <= ' ')
680             {
681                 if (started)
682                     break;
683             }
684             else if (b >= '0' && b <= '9')
685             {
686                 val = val * 10L + (b - '0');
687                 started = true;
688             }
689             else if (b == '-' && !started)
690             {
691                 minus = true;
692             }
693             else
694                 break;
695         }
696 
697         if (started)
698             return minus?(-val):val;
699         throw new NumberFormatException(string);
700     }
701     
702     /**
703      * Truncate a string to a max size.
704      * 
705      * @param str the string to possibly truncate
706      * @param maxSize the maximum size of the string
707      * @return the truncated string.  if <code>str</code> param is null, then the returned string will also be null.
708      */
709     public static String truncate(String str, int maxSize)
710     {
711         if (str == null)
712         {
713             return null;
714         }
715 
716         if (str.length() <= maxSize)
717         {
718             return str;
719         }
720 
721         return str.substring(0,maxSize);
722     }
723 
724     public static String[] arrayFromString(String s) 
725     {
726         if (s==null)
727             return new String[]{};
728 
729         if (!s.startsWith("[") || !s.endsWith("]"))
730             throw new IllegalArgumentException();
731         if (s.length()==2)
732             return new String[]{};
733 
734         return s.substring(1,s.length()-1).split(" *, *");
735     }
736 
737 }