• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * Copyright 2003-2007 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 package org.jivesoftware.smack.util;
22 
23 import java.io.UnsupportedEncodingException;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.text.DateFormat;
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Calendar;
31 import java.util.Collections;
32 import java.util.Comparator;
33 import java.util.Date;
34 import java.util.List;
35 import java.util.Random;
36 import java.util.TimeZone;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 
40 /**
41  * A collection of utility methods for String objects.
42  */
43 public class StringUtils {
44 
45 	/**
46      * Date format as defined in XEP-0082 - XMPP Date and Time Profiles. The time zone is set to
47      * UTC.
48      * <p>
49      * Date formats are not synchronized. Since multiple threads access the format concurrently, it
50      * must be synchronized externally or you can use the convenience methods
51      * {@link #parseXEP0082Date(String)} and {@link #formatXEP0082Date(Date)}.
52      * @deprecated This public version will be removed in favor of using the methods defined within this class.
53      */
54     public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
55 
56     /*
57      * private version to use internally so we don't have to be concerned with thread safety.
58      */
59     private static final DateFormat dateFormatter = DateFormatType.XEP_0082_DATE_PROFILE.createFormatter();
60     private static final Pattern datePattern = Pattern.compile("^\\d+-\\d+-\\d+$");
61 
62     private static final DateFormat timeFormatter = DateFormatType.XEP_0082_TIME_MILLIS_ZONE_PROFILE.createFormatter();
63     private static final Pattern timePattern = Pattern.compile("^(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))$");
64     private static final DateFormat timeNoZoneFormatter = DateFormatType.XEP_0082_TIME_MILLIS_PROFILE.createFormatter();
65     private static final Pattern timeNoZonePattern = Pattern.compile("^(\\d+:){2}\\d+.\\d+$");
66 
67     private static final DateFormat timeNoMillisFormatter = DateFormatType.XEP_0082_TIME_ZONE_PROFILE.createFormatter();
68     private static final Pattern timeNoMillisPattern = Pattern.compile("^(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))$");
69     private static final DateFormat timeNoMillisNoZoneFormatter = DateFormatType.XEP_0082_TIME_PROFILE.createFormatter();
70     private static final Pattern timeNoMillisNoZonePattern = Pattern.compile("^(\\d+:){2}\\d+$");
71 
72     private static final DateFormat dateTimeFormatter = DateFormatType.XEP_0082_DATETIME_MILLIS_PROFILE.createFormatter();
73     private static final Pattern dateTimePattern = Pattern.compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+.\\d+(Z|([+-](\\d+:\\d+)))?$");
74     private static final DateFormat dateTimeNoMillisFormatter = DateFormatType.XEP_0082_DATETIME_PROFILE.createFormatter();
75     private static final Pattern dateTimeNoMillisPattern = Pattern.compile("^\\d+(-\\d+){2}+T(\\d+:){2}\\d+(Z|([+-](\\d+:\\d+)))?$");
76 
77     private static final DateFormat xep0091Formatter = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
78     private static final DateFormat xep0091Date6DigitFormatter = new SimpleDateFormat("yyyyMd'T'HH:mm:ss");
79     private static final DateFormat xep0091Date7Digit1MonthFormatter = new SimpleDateFormat("yyyyMdd'T'HH:mm:ss");
80     private static final DateFormat xep0091Date7Digit2MonthFormatter = new SimpleDateFormat("yyyyMMd'T'HH:mm:ss");
81     private static final Pattern xep0091Pattern = Pattern.compile("^\\d+T\\d+:\\d+:\\d+$");
82 
83     private static final List<PatternCouplings> couplings = new ArrayList<PatternCouplings>();
84 
85     static {
86     	TimeZone utc = TimeZone.getTimeZone("UTC");
87         XEP_0082_UTC_FORMAT.setTimeZone(utc);
88         dateFormatter.setTimeZone(utc);
89         timeFormatter.setTimeZone(utc);
90         timeNoZoneFormatter.setTimeZone(utc);
91         timeNoMillisFormatter.setTimeZone(utc);
92         timeNoMillisNoZoneFormatter.setTimeZone(utc);
93         dateTimeFormatter.setTimeZone(utc);
94         dateTimeNoMillisFormatter.setTimeZone(utc);
95 
96         xep0091Formatter.setTimeZone(utc);
97         xep0091Date6DigitFormatter.setTimeZone(utc);
98         xep0091Date7Digit1MonthFormatter.setTimeZone(utc);
99         xep0091Date7Digit1MonthFormatter.setLenient(false);
100         xep0091Date7Digit2MonthFormatter.setTimeZone(utc);
101         xep0091Date7Digit2MonthFormatter.setLenient(false);
102 
couplings.add(new PatternCouplings(datePattern, dateFormatter))103         couplings.add(new PatternCouplings(datePattern, dateFormatter));
couplings.add(new PatternCouplings(dateTimePattern, dateTimeFormatter, true))104         couplings.add(new PatternCouplings(dateTimePattern, dateTimeFormatter, true));
couplings.add(new PatternCouplings(dateTimeNoMillisPattern, dateTimeNoMillisFormatter, true))105         couplings.add(new PatternCouplings(dateTimeNoMillisPattern, dateTimeNoMillisFormatter, true));
couplings.add(new PatternCouplings(timePattern, timeFormatter, true))106         couplings.add(new PatternCouplings(timePattern, timeFormatter, true));
couplings.add(new PatternCouplings(timeNoZonePattern, timeNoZoneFormatter))107         couplings.add(new PatternCouplings(timeNoZonePattern, timeNoZoneFormatter));
couplings.add(new PatternCouplings(timeNoMillisPattern, timeNoMillisFormatter, true))108         couplings.add(new PatternCouplings(timeNoMillisPattern, timeNoMillisFormatter, true));
couplings.add(new PatternCouplings(timeNoMillisNoZonePattern, timeNoMillisNoZoneFormatter))109         couplings.add(new PatternCouplings(timeNoMillisNoZonePattern, timeNoMillisNoZoneFormatter));
110     }
111 
112     private static final char[] QUOTE_ENCODE = "&quot;".toCharArray();
113     private static final char[] APOS_ENCODE = "&apos;".toCharArray();
114     private static final char[] AMP_ENCODE = "&amp;".toCharArray();
115     private static final char[] LT_ENCODE = "&lt;".toCharArray();
116     private static final char[] GT_ENCODE = "&gt;".toCharArray();
117 
118     /**
119      * Parses the given date string in the <a href="http://xmpp.org/extensions/xep-0082.html">XEP-0082 - XMPP Date and Time Profiles</a>.
120      *
121      * @param dateString the date string to parse
122      * @return the parsed Date
123      * @throws ParseException if the specified string cannot be parsed
124      * @deprecated Use {@link #parseDate(String)} instead.
125      *
126      */
parseXEP0082Date(String dateString)127     public static Date parseXEP0082Date(String dateString) throws ParseException {
128     	return parseDate(dateString);
129     }
130 
131     /**
132      * Parses the given date string in either of the three profiles of <a href="http://xmpp.org/extensions/xep-0082.html">XEP-0082 - XMPP Date and Time Profiles</a>
133      * or <a href="http://xmpp.org/extensions/xep-0091.html">XEP-0091 - Legacy Delayed Delivery</a> format.
134      * <p>
135      * This method uses internal date formatters and is thus threadsafe.
136      * @param dateString the date string to parse
137      * @return the parsed Date
138      * @throws ParseException if the specified string cannot be parsed
139      */
parseDate(String dateString)140     public static Date parseDate(String dateString) throws ParseException {
141         Matcher matcher = xep0091Pattern.matcher(dateString);
142 
143         /*
144          * if date is in XEP-0091 format handle ambiguous dates missing the
145          * leading zero in month and day
146          */
147         if (matcher.matches()) {
148         	int length = dateString.split("T")[0].length();
149 
150             if (length < 8) {
151                 Date date = handleDateWithMissingLeadingZeros(dateString, length);
152 
153                 if (date != null)
154                 	return date;
155             }
156             else {
157             	synchronized (xep0091Formatter) {
158                 	return xep0091Formatter.parse(dateString);
159 				}
160             }
161         }
162         else {
163         	for (PatternCouplings coupling : couplings) {
164                 matcher = coupling.pattern.matcher(dateString);
165 
166                 if (matcher.matches())
167                 {
168                 	if (coupling.needToConvertTimeZone) {
169                 		dateString = coupling.convertTime(dateString);
170                 	}
171 
172                     synchronized (coupling.formatter) {
173                     	return coupling.formatter.parse(dateString);
174                     }
175                 }
176 			}
177         }
178 
179         /*
180          * We assume it is the XEP-0082 DateTime profile with no milliseconds at this point.  If it isn't, is is just not parseable, then we attempt
181          * to parse it regardless and let it throw the ParseException.
182          */
183         synchronized (dateTimeNoMillisFormatter) {
184         	return dateTimeNoMillisFormatter.parse(dateString);
185         }
186     }
187 
188     /**
189      * Parses the given date string in different ways and returns the date that
190      * lies in the past and/or is nearest to the current date-time.
191      *
192      * @param stampString date in string representation
193      * @param dateLength
194      * @param noFuture
195      * @return the parsed date
196      * @throws ParseException The date string was of an unknown format
197      */
handleDateWithMissingLeadingZeros(String stampString, int dateLength)198     private static Date handleDateWithMissingLeadingZeros(String stampString, int dateLength) throws ParseException {
199         if (dateLength == 6) {
200         	synchronized (xep0091Date6DigitFormatter) {
201 				return xep0091Date6DigitFormatter.parse(stampString);
202 			}
203         }
204         Calendar now = Calendar.getInstance();
205 
206         Calendar oneDigitMonth = parseXEP91Date(stampString, xep0091Date7Digit1MonthFormatter);
207         Calendar twoDigitMonth = parseXEP91Date(stampString, xep0091Date7Digit2MonthFormatter);
208 
209         List<Calendar> dates = filterDatesBefore(now, oneDigitMonth, twoDigitMonth);
210 
211         if (!dates.isEmpty()) {
212             return determineNearestDate(now, dates).getTime();
213         }
214         return null;
215     }
216 
parseXEP91Date(String stampString, DateFormat dateFormat)217     private static Calendar parseXEP91Date(String stampString, DateFormat dateFormat) {
218         try {
219             synchronized (dateFormat) {
220                 dateFormat.parse(stampString);
221                 return dateFormat.getCalendar();
222             }
223         }
224         catch (ParseException e) {
225             return null;
226         }
227     }
228 
filterDatesBefore(Calendar now, Calendar... dates)229     private static List<Calendar> filterDatesBefore(Calendar now, Calendar... dates) {
230         List<Calendar> result = new ArrayList<Calendar>();
231 
232         for (Calendar calendar : dates) {
233             if (calendar != null && calendar.before(now)) {
234                 result.add(calendar);
235             }
236         }
237 
238         return result;
239     }
240 
determineNearestDate(final Calendar now, List<Calendar> dates)241     private static Calendar determineNearestDate(final Calendar now, List<Calendar> dates) {
242 
243         Collections.sort(dates, new Comparator<Calendar>() {
244 
245             public int compare(Calendar o1, Calendar o2) {
246                 Long diff1 = new Long(now.getTimeInMillis() - o1.getTimeInMillis());
247                 Long diff2 = new Long(now.getTimeInMillis() - o2.getTimeInMillis());
248                 return diff1.compareTo(diff2);
249             }
250 
251         });
252 
253         return dates.get(0);
254     }
255 
256     /**
257      * Formats a Date into a XEP-0082 - XMPP Date and Time Profiles string.
258      *
259      * @param date the time value to be formatted into a time string
260      * @return the formatted time string in XEP-0082 format
261      */
formatXEP0082Date(Date date)262     public static String formatXEP0082Date(Date date) {
263         synchronized (dateTimeFormatter) {
264             return dateTimeFormatter.format(date);
265         }
266     }
267 
formatDate(Date toFormat, DateFormatType type)268     public static String formatDate(Date toFormat, DateFormatType type)
269     {
270     	return null;
271     }
272 
273     /**
274      * Returns the name portion of a XMPP address. For example, for the
275      * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no
276      * username is present in the address, the empty string will be returned.
277      *
278      * @param XMPPAddress the XMPP address.
279      * @return the name portion of the XMPP address.
280      */
parseName(String XMPPAddress)281     public static String parseName(String XMPPAddress) {
282         if (XMPPAddress == null) {
283             return null;
284         }
285         int atIndex = XMPPAddress.lastIndexOf("@");
286         if (atIndex <= 0) {
287             return "";
288         }
289         else {
290             return XMPPAddress.substring(0, atIndex);
291         }
292     }
293 
294     /**
295      * Returns the server portion of a XMPP address. For example, for the
296      * address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned.
297      * If no server is present in the address, the empty string will be returned.
298      *
299      * @param XMPPAddress the XMPP address.
300      * @return the server portion of the XMPP address.
301      */
parseServer(String XMPPAddress)302     public static String parseServer(String XMPPAddress) {
303         if (XMPPAddress == null) {
304             return null;
305         }
306         int atIndex = XMPPAddress.lastIndexOf("@");
307         // If the String ends with '@', return the empty string.
308         if (atIndex + 1 > XMPPAddress.length()) {
309             return "";
310         }
311         int slashIndex = XMPPAddress.indexOf("/");
312         if (slashIndex > 0 && slashIndex > atIndex) {
313             return XMPPAddress.substring(atIndex + 1, slashIndex);
314         }
315         else {
316             return XMPPAddress.substring(atIndex + 1);
317         }
318     }
319 
320     /**
321      * Returns the resource portion of a XMPP address. For example, for the
322      * address "matt@jivesoftware.com/Smack", "Smack" would be returned. If no
323      * resource is present in the address, the empty string will be returned.
324      *
325      * @param XMPPAddress the XMPP address.
326      * @return the resource portion of the XMPP address.
327      */
parseResource(String XMPPAddress)328     public static String parseResource(String XMPPAddress) {
329         if (XMPPAddress == null) {
330             return null;
331         }
332         int slashIndex = XMPPAddress.indexOf("/");
333         if (slashIndex + 1 > XMPPAddress.length() || slashIndex < 0) {
334             return "";
335         }
336         else {
337             return XMPPAddress.substring(slashIndex + 1);
338         }
339     }
340 
341     /**
342      * Returns the XMPP address with any resource information removed. For example,
343      * for the address "matt@jivesoftware.com/Smack", "matt@jivesoftware.com" would
344      * be returned.
345      *
346      * @param XMPPAddress the XMPP address.
347      * @return the bare XMPP address without resource information.
348      */
parseBareAddress(String XMPPAddress)349     public static String parseBareAddress(String XMPPAddress) {
350         if (XMPPAddress == null) {
351             return null;
352         }
353         int slashIndex = XMPPAddress.indexOf("/");
354         if (slashIndex < 0) {
355             return XMPPAddress;
356         }
357         else if (slashIndex == 0) {
358             return "";
359         }
360         else {
361             return XMPPAddress.substring(0, slashIndex);
362         }
363     }
364 
365     /**
366      * Returns true if jid is a full JID (i.e. a JID with resource part).
367      *
368      * @param jid
369      * @return true if full JID, false otherwise
370      */
isFullJID(String jid)371     public static boolean isFullJID(String jid) {
372         if (parseName(jid).length() <= 0 || parseServer(jid).length() <= 0
373                 || parseResource(jid).length() <= 0) {
374             return false;
375         }
376         return true;
377     }
378 
379     /**
380      * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106).
381      * Escaping replaces characters prohibited by node-prep with escape sequences,
382      * as follows:<p>
383      *
384      * <table border="1">
385      * <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
386      * <tr><td>&lt;space&gt;</td><td>\20</td></tr>
387      * <tr><td>"</td><td>\22</td></tr>
388      * <tr><td>&</td><td>\26</td></tr>
389      * <tr><td>'</td><td>\27</td></tr>
390      * <tr><td>/</td><td>\2f</td></tr>
391      * <tr><td>:</td><td>\3a</td></tr>
392      * <tr><td>&lt;</td><td>\3c</td></tr>
393      * <tr><td>&gt;</td><td>\3e</td></tr>
394      * <tr><td>@</td><td>\40</td></tr>
395      * <tr><td>\</td><td>\5c</td></tr>
396      * </table><p>
397      *
398      * This process is useful when the node comes from an external source that doesn't
399      * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
400      * the &lt;space&gt; character isn't a valid part of a node, the username should
401      * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
402      * after case-folding, etc. has been applied).<p>
403      *
404      * All node escaping and un-escaping must be performed manually at the appropriate
405      * time; the JID class will not escape or un-escape automatically.
406      *
407      * @param node the node.
408      * @return the escaped version of the node.
409      */
escapeNode(String node)410     public static String escapeNode(String node) {
411         if (node == null) {
412             return null;
413         }
414         StringBuilder buf = new StringBuilder(node.length() + 8);
415         for (int i=0, n=node.length(); i<n; i++) {
416             char c = node.charAt(i);
417             switch (c) {
418                 case '"': buf.append("\\22"); break;
419                 case '&': buf.append("\\26"); break;
420                 case '\'': buf.append("\\27"); break;
421                 case '/': buf.append("\\2f"); break;
422                 case ':': buf.append("\\3a"); break;
423                 case '<': buf.append("\\3c"); break;
424                 case '>': buf.append("\\3e"); break;
425                 case '@': buf.append("\\40"); break;
426                 case '\\': buf.append("\\5c"); break;
427                 default: {
428                     if (Character.isWhitespace(c)) {
429                         buf.append("\\20");
430                     }
431                     else {
432                         buf.append(c);
433                     }
434                 }
435             }
436         }
437         return buf.toString();
438     }
439 
440     /**
441      * Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).<p>
442      * Escaping replaces characters prohibited by node-prep with escape sequences,
443      * as follows:<p>
444      *
445      * <table border="1">
446      * <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
447      * <tr><td>&lt;space&gt;</td><td>\20</td></tr>
448      * <tr><td>"</td><td>\22</td></tr>
449      * <tr><td>&</td><td>\26</td></tr>
450      * <tr><td>'</td><td>\27</td></tr>
451      * <tr><td>/</td><td>\2f</td></tr>
452      * <tr><td>:</td><td>\3a</td></tr>
453      * <tr><td>&lt;</td><td>\3c</td></tr>
454      * <tr><td>&gt;</td><td>\3e</td></tr>
455      * <tr><td>@</td><td>\40</td></tr>
456      * <tr><td>\</td><td>\5c</td></tr>
457      * </table><p>
458      *
459      * This process is useful when the node comes from an external source that doesn't
460      * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
461      * the &lt;space&gt; character isn't a valid part of a node, the username should
462      * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
463      * after case-folding, etc. has been applied).<p>
464      *
465      * All node escaping and un-escaping must be performed manually at the appropriate
466      * time; the JID class will not escape or un-escape automatically.
467      *
468      * @param node the escaped version of the node.
469      * @return the un-escaped version of the node.
470      */
unescapeNode(String node)471     public static String unescapeNode(String node) {
472         if (node == null) {
473             return null;
474         }
475         char [] nodeChars = node.toCharArray();
476         StringBuilder buf = new StringBuilder(nodeChars.length);
477         for (int i=0, n=nodeChars.length; i<n; i++) {
478             compare: {
479                 char c = node.charAt(i);
480                 if (c == '\\' && i+2<n) {
481                     char c2 = nodeChars[i+1];
482                     char c3 = nodeChars[i+2];
483                     if (c2 == '2') {
484                         switch (c3) {
485                             case '0': buf.append(' '); i+=2; break compare;
486                             case '2': buf.append('"'); i+=2; break compare;
487                             case '6': buf.append('&'); i+=2; break compare;
488                             case '7': buf.append('\''); i+=2; break compare;
489                             case 'f': buf.append('/'); i+=2; break compare;
490                         }
491                     }
492                     else if (c2 == '3') {
493                         switch (c3) {
494                             case 'a': buf.append(':'); i+=2; break compare;
495                             case 'c': buf.append('<'); i+=2; break compare;
496                             case 'e': buf.append('>'); i+=2; break compare;
497                         }
498                     }
499                     else if (c2 == '4') {
500                         if (c3 == '0') {
501                             buf.append("@");
502                             i+=2;
503                             break compare;
504                         }
505                     }
506                     else if (c2 == '5') {
507                         if (c3 == 'c') {
508                             buf.append("\\");
509                             i+=2;
510                             break compare;
511                         }
512                     }
513                 }
514                 buf.append(c);
515             }
516         }
517         return buf.toString();
518     }
519 
520     /**
521      * Escapes all necessary characters in the String so that it can be used
522      * in an XML doc.
523      *
524      * @param string the string to escape.
525      * @return the string with appropriate characters escaped.
526      */
escapeForXML(String string)527     public static String escapeForXML(String string) {
528         if (string == null) {
529             return null;
530         }
531         char ch;
532         int i=0;
533         int last=0;
534         char[] input = string.toCharArray();
535         int len = input.length;
536         StringBuilder out = new StringBuilder((int)(len*1.3));
537         for (; i < len; i++) {
538             ch = input[i];
539             if (ch > '>') {
540             }
541             else if (ch == '<') {
542                 if (i > last) {
543                     out.append(input, last, i - last);
544                 }
545                 last = i + 1;
546                 out.append(LT_ENCODE);
547             }
548             else if (ch == '>') {
549                 if (i > last) {
550                     out.append(input, last, i - last);
551                 }
552                 last = i + 1;
553                 out.append(GT_ENCODE);
554             }
555 
556             else if (ch == '&') {
557                 if (i > last) {
558                     out.append(input, last, i - last);
559                 }
560                 // Do nothing if the string is of the form &#235; (unicode value)
561                 if (!(len > i + 5
562                     && input[i + 1] == '#'
563                     && Character.isDigit(input[i + 2])
564                     && Character.isDigit(input[i + 3])
565                     && Character.isDigit(input[i + 4])
566                     && input[i + 5] == ';')) {
567                         last = i + 1;
568                         out.append(AMP_ENCODE);
569                     }
570             }
571             else if (ch == '"') {
572                 if (i > last) {
573                     out.append(input, last, i - last);
574                 }
575                 last = i + 1;
576                 out.append(QUOTE_ENCODE);
577             }
578             else if (ch == '\'') {
579                 if (i > last) {
580                     out.append(input, last, i - last);
581                 }
582                 last = i + 1;
583                 out.append(APOS_ENCODE);
584             }
585         }
586         if (last == 0) {
587             return string;
588         }
589         if (i > last) {
590             out.append(input, last, i - last);
591         }
592         return out.toString();
593     }
594 
595     /**
596      * Used by the hash method.
597      */
598     private static MessageDigest digest = null;
599 
600     /**
601      * Hashes a String using the SHA-1 algorithm and returns the result as a
602      * String of hexadecimal numbers. This method is synchronized to avoid
603      * excessive MessageDigest object creation. If calling this method becomes
604      * a bottleneck in your code, you may wish to maintain a pool of
605      * MessageDigest objects instead of using this method.
606      * <p>
607      * A hash is a one-way function -- that is, given an
608      * input, an output is easily computed. However, given the output, the
609      * input is almost impossible to compute. This is useful for passwords
610      * since we can store the hash and a hacker will then have a very hard time
611      * determining the original password.
612      *
613      * @param data the String to compute the hash of.
614      * @return a hashed version of the passed-in String
615      */
hash(String data)616     public synchronized static String hash(String data) {
617         if (digest == null) {
618             try {
619                 digest = MessageDigest.getInstance("SHA-1");
620             }
621             catch (NoSuchAlgorithmException nsae) {
622                 System.err.println("Failed to load the SHA-1 MessageDigest. " +
623                 "Jive will be unable to function normally.");
624             }
625         }
626         // Now, compute hash.
627         try {
628             digest.update(data.getBytes("UTF-8"));
629         }
630         catch (UnsupportedEncodingException e) {
631             System.err.println(e);
632         }
633         return encodeHex(digest.digest());
634     }
635 
636     /**
637      * Encodes an array of bytes as String representation of hexadecimal.
638      *
639      * @param bytes an array of bytes to convert to a hex string.
640      * @return generated hex string.
641      */
encodeHex(byte[] bytes)642     public static String encodeHex(byte[] bytes) {
643         StringBuilder hex = new StringBuilder(bytes.length * 2);
644 
645         for (byte aByte : bytes) {
646             if (((int) aByte & 0xff) < 0x10) {
647                 hex.append("0");
648             }
649             hex.append(Integer.toString((int) aByte & 0xff, 16));
650         }
651 
652         return hex.toString();
653     }
654 
655     /**
656      * Encodes a String as a base64 String.
657      *
658      * @param data a String to encode.
659      * @return a base64 encoded String.
660      */
encodeBase64(String data)661     public static String encodeBase64(String data) {
662         byte [] bytes = null;
663         try {
664             bytes = data.getBytes("ISO-8859-1");
665         }
666         catch (UnsupportedEncodingException uee) {
667             uee.printStackTrace();
668         }
669         return encodeBase64(bytes);
670     }
671 
672     /**
673      * Encodes a byte array into a base64 String.
674      *
675      * @param data a byte array to encode.
676      * @return a base64 encode String.
677      */
encodeBase64(byte[] data)678     public static String encodeBase64(byte[] data) {
679         return encodeBase64(data, false);
680     }
681 
682     /**
683      * Encodes a byte array into a bse64 String.
684      *
685      * @param data The byte arry to encode.
686      * @param lineBreaks True if the encoding should contain line breaks and false if it should not.
687      * @return A base64 encoded String.
688      */
encodeBase64(byte[] data, boolean lineBreaks)689     public static String encodeBase64(byte[] data, boolean lineBreaks) {
690         return encodeBase64(data, 0, data.length, lineBreaks);
691     }
692 
693     /**
694      * Encodes a byte array into a bse64 String.
695      *
696      * @param data The byte arry to encode.
697      * @param offset the offset of the bytearray to begin encoding at.
698      * @param len the length of bytes to encode.
699      * @param lineBreaks True if the encoding should contain line breaks and false if it should not.
700      * @return A base64 encoded String.
701      */
encodeBase64(byte[] data, int offset, int len, boolean lineBreaks)702     public static String encodeBase64(byte[] data, int offset, int len, boolean lineBreaks) {
703         return Base64.encodeBytes(data, offset, len, (lineBreaks ?  Base64.NO_OPTIONS : Base64.DONT_BREAK_LINES));
704     }
705 
706     /**
707      * Decodes a base64 String.
708      * Unlike Base64.decode() this method does not try to detect and decompress a gzip-compressed input.
709      *
710      * @param data a base64 encoded String to decode.
711      * @return the decoded String.
712      */
decodeBase64(String data)713     public static byte[] decodeBase64(String data) {
714         byte[] bytes;
715         try {
716             bytes = data.getBytes("UTF-8");
717         } catch (java.io.UnsupportedEncodingException uee) {
718             bytes = data.getBytes();
719         }
720 
721         bytes = Base64.decode(bytes, 0, bytes.length, Base64.NO_OPTIONS);
722         return bytes;
723     }
724 
725     /**
726      * Pseudo-random number generator object for use with randomString().
727      * The Random class is not considered to be cryptographically secure, so
728      * only use these random Strings for low to medium security applications.
729      */
730     private static Random randGen = new Random();
731 
732     /**
733      * Array of numbers and letters of mixed case. Numbers appear in the list
734      * twice so that there is a more equal chance that a number will be picked.
735      * We can use the array to get a random number or letter by picking a random
736      * array index.
737      */
738     private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" +
739                     "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
740 
741     /**
742      * Returns a random String of numbers and letters (lower and upper case)
743      * of the specified length. The method uses the Random class that is
744      * built-in to Java which is suitable for low to medium grade security uses.
745      * This means that the output is only pseudo random, i.e., each number is
746      * mathematically generated so is not truly random.<p>
747      *
748      * The specified length must be at least one. If not, the method will return
749      * null.
750      *
751      * @param length the desired length of the random String to return.
752      * @return a random String of numbers and letters of the specified length.
753      */
randomString(int length)754     public static String randomString(int length) {
755         if (length < 1) {
756             return null;
757         }
758         // Create a char buffer to put random letters and numbers in.
759         char [] randBuffer = new char[length];
760         for (int i=0; i<randBuffer.length; i++) {
761             randBuffer[i] = numbersAndLetters[randGen.nextInt(71)];
762         }
763         return new String(randBuffer);
764     }
765 
StringUtils()766     private StringUtils() {
767         // Not instantiable.
768     }
769 
770     private static class PatternCouplings {
771     	Pattern pattern;
772     	DateFormat formatter;
773     	boolean needToConvertTimeZone = false;
774 
PatternCouplings(Pattern datePattern, DateFormat dateFormat)775     	public PatternCouplings(Pattern datePattern, DateFormat dateFormat) {
776     		pattern = datePattern;
777     		formatter = dateFormat;
778 		}
779 
PatternCouplings(Pattern datePattern, DateFormat dateFormat, boolean shouldConvertToRFC822)780     	public PatternCouplings(Pattern datePattern, DateFormat dateFormat, boolean shouldConvertToRFC822) {
781     		pattern = datePattern;
782     		formatter = dateFormat;
783     		needToConvertTimeZone = shouldConvertToRFC822;
784 		}
785 
convertTime(String dateString)786     	public String convertTime(String dateString) {
787             if (dateString.charAt(dateString.length() - 1) == 'Z') {
788                 return dateString.replace("Z", "+0000");
789             }
790             else {
791             	// If the time zone wasn't specified with 'Z', then it's in
792             	// ISO8601 format (i.e. '(+|-)HH:mm')
793             	// RFC822 needs a similar format just without the colon (i.e.
794             	// '(+|-)HHmm)'), so remove it
795                 return dateString.replaceAll("([\\+\\-]\\d\\d):(\\d\\d)","$1$2");
796     		}
797     	}
798 	}
799 
800 }
801