View Javadoc

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