/** * $RCSfile$ * $Revision$ * $Date$ * * Copyright (C) 2002-2003 Jive Software. All rights reserved. * ==================================================================== * The Jive Software License (based on Apache Software License, Version 1.1) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by * Jive Software (http://www.jivesoftware.com)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Smack" and "Jive Software" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact webmaster@jivesoftware.com. * * 5. Products derived from this software may not be called "Smack", * nor may "Smack" appear in their name, without prior written * permission of Jive Software. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== */ package org.jivesoftware.smack.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.io.UnsupportedEncodingException; import java.util.Random; /** * A collection of utility methods for String objects. */ public class StringUtils { private static final char[] QUOTE_ENCODE = """.toCharArray(); private static final char[] AMP_ENCODE = "&".toCharArray(); private static final char[] LT_ENCODE = "<".toCharArray(); private static final char[] GT_ENCODE = ">".toCharArray(); /** * Returns the name portion of a XMPP ID. For example, for the * ID "matt@jivesoftware.com/Smack", "matt" would be returned. If no * username is present in the ID, the empty string will be returned. * * @param ID the XMPP ID. * @return the name portion of the XMPP ID. */ public static String parseName(String ID) { if (ID == null) { return null; } int atIndex = ID.indexOf("@"); if (atIndex < 0) { return ID.substring(0); } else { return ID.substring(0, atIndex); } } /** * Returns the name portion of a XMPP ID. For example, for the * ID "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned. * If no server is present in the ID, the empty string will be returned. * * @param ID the XMPP ID. * @return the resource portion of the XMPP ID. */ public static String parseServer(String ID) { if (ID == null) { return null; } int atIndex = ID.indexOf("@"); // If the String ends with '@', return the empty string. if (atIndex + 1 > ID.length() || atIndex < 0) { return ""; } int slashIndex = ID.indexOf("/"); if (slashIndex > 0) { return ID.substring(atIndex + 1, slashIndex); } else { return ID.substring(atIndex + 1); } } /** * Returns the name portion of a XMPP ID. For example, for the * ID "matt@jivesoftware.com/Smack", "Smack" would be returned. If no * resource is present in the ID, the empty string will be returned. * * @param ID the XMPP ID. * @return the resource portion of the XMPP ID. */ public static String parseResource(String ID) { if (ID == null) { return null; } int slashIndex = ID.indexOf("/"); if (slashIndex + 1 > ID.length() || slashIndex < 0) { return ""; } else { return ID.substring(slashIndex + 1); } } /** * Escapes all necessary characters in the String so that it can be used * in an XML doc. * * @param string the string to escape. * @return the string with appropriate characters escaped. */ public static final String escapeForXML(String string) { if (string == null) { return null; } char ch; int i=0; int last=0; char[] input = string.toCharArray(); int len = input.length; StringBuffer out = new StringBuffer((int)(len*1.3)); for (; i < len; i++) { ch = input[i]; if (ch > '>') { continue; } else if (ch == '<') { if (i > last) { out.append(input, last, i - last); } last = i + 1; out.append(LT_ENCODE); } else if (ch == '>') { if (i > last) { out.append(input, last, i - last); } last = i + 1; out.append(GT_ENCODE); } else if (ch == '&') { if (i > last) { out.append(input, last, i - last); } last = i + 1; out.append(AMP_ENCODE); } else if (ch == '"') { if (i > last) { out.append(input, last, i - last); } last = i + 1; out.append(QUOTE_ENCODE); } } if (last == 0) { return string; } if (i > last) { out.append(input, last, i - last); } return out.toString(); } /** * Used by the hash method. */ private static MessageDigest digest = null; /** * Hashes a String using the SHA-1 algorithm and returns the result as a * String of hexadecimal numbers. This method is synchronized to avoid * excessive MessageDigest object creation. If calling this method becomes * a bottleneck in your code, you may wish to maintain a pool of * MessageDigest objects instead of using this method. *
* A hash is a one-way function -- that is, given an * input, an output is easily computed. However, given the output, the * input is almost impossible to compute. This is useful for passwords * since we can store the hash and a hacker will then have a very hard time * determining the original password. * * @param data the String to compute the hash of. * @return a hashed version of the passed-in String */ public synchronized static final String hash(String data) { if (digest == null) { try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException nsae) { System.err.println("Failed to load the SHA-1 MessageDigest. " + "Jive will be unable to function normally."); } } // Now, compute hash. try { digest.update(data.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { System.err.println(e); } return encodeHex(digest.digest()); } /** * Turns an array of bytes into a String representing each byte as an * unsigned hex number. *
* Method by Santeri Paavolainen, Helsinki Finland 1996
* (c) Santeri Paavolainen, Helsinki Finland 1996
* Distributed under LGPL.
*
* @param bytes an array of bytes to convert to a hex-string
* @return generated hex string
*/
public static final String encodeHex(byte[] bytes) {
StringBuffer buf = new StringBuffer(bytes.length * 2);
int i;
for (i = 0; i < bytes.length; i++) {
if (((int) bytes[i] & 0xff) < 0x10) {
buf.append("0");
}
buf.append(Long.toString((int) bytes[i] & 0xff, 16));
}
return buf.toString();
}
//*********************************************************************
//* Base64 - a simple base64 encoder and decoder.
//*
//* Copyright (c) 1999, Bob Withers - bwit@pobox.com
//*
//* This code may be freely used for any purpose, either personal
//* or commercial, provided the authors copyright notice remains
//* intact.
//*********************************************************************
private static final int fillchar = '=';
private static final String cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
/**
* Encodes a String as a base64 String.
*
* @param data a String to encode.
* @return a base64 encoded String.
*/
public static String encodeBase64(String data) {
byte [] bytes = null;
try {
bytes = data.getBytes("UTF8");
}
catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return encodeBase64(bytes);
}
/**
* Encodes a byte array into a base64 String.
*
* @param data a byte array to encode.
* @return a base64 encode String.
*/
public static String encodeBase64(byte[] data) {
int c;
int len = data.length;
StringBuffer ret = new StringBuffer(((len / 3) + 1) * 4);
for (int i = 0; i < len; ++i) {
c = (data[i] >> 2) & 0x3f;
ret.append(cvt.charAt(c));
c = (data[i] << 4) & 0x3f;
if (++i < len)
c |= (data[i] >> 4) & 0x0f;
ret.append(cvt.charAt(c));
if (i < len) {
c = (data[i] << 2) & 0x3f;
if (++i < len)
c |= (data[i] >> 6) & 0x03;
ret.append(cvt.charAt(c));
}
else {
++i;
ret.append((char) fillchar);
}
if (i < len) {
c = data[i] & 0x3f;
ret.append(cvt.charAt(c));
}
else {
ret.append((char) fillchar);
}
}
return ret.toString();
}
/**
* Decodes a base64 String.
*
* @param data a base64 encoded String to decode.
* @return the decoded String.
*/
public static byte[] decodeBase64(String data) {
byte [] bytes = null;
try {
bytes = data.getBytes("UTF8");
return decodeBase64(bytes).getBytes("UTF8");
}
catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return new byte[] { };
}
/**
* Decodes a base64 aray of bytes.
*
* @param data a base64 encode byte array to decode.
* @return the decoded String.
*/
private static String decodeBase64(byte[] data) {
int c, c1;
int len = data.length;
StringBuffer ret = new StringBuffer((len * 3) / 4);
for (int i = 0; i < len; ++i) {
c = cvt.indexOf(data[i]);
++i;
c1 = cvt.indexOf(data[i]);
c = ((c << 2) | ((c1 >> 4) & 0x3));
ret.append((char) c);
if (++i < len) {
c = data[i];
if (fillchar == c)
break;
c = cvt.indexOf(c);
c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf);
ret.append((char) c1);
}
if (++i < len) {
c1 = data[i];
if (fillchar == c1)
break;
c1 = cvt.indexOf(c1);
c = ((c << 6) & 0xc0) | c1;
ret.append((char) c);
}
}
return ret.toString();
}
/**
* Pseudo-random number generator object for use with randomString().
* The Random class is not considered to be cryptographically secure, so
* only use these random Strings for low to medium security applications.
*/
private static Random randGen = new Random();
/**
* Array of numbers and letters of mixed case. Numbers appear in the list
* twice so that there is a more equal chance that a number will be picked.
* We can use the array to get a random number or letter by picking a random
* array index.
*/
private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" +
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
/**
* Returns a random String of numbers and letters (lower and upper case)
* of the specified length. The method uses the Random class that is
* built-in to Java which is suitable for low to medium grade security uses.
* This means that the output is only pseudo random, i.e., each number is
* mathematically generated so is not truly random.
*
* The specified length must be at least one. If not, the method will return
* null.
*
* @param length the desired length of the random String to return.
* @return a random String of numbers and letters of the specified length.
*/
public static final String randomString(int length) {
if (length < 1) {
return null;
}
// Create a char buffer to put random letters and numbers in.
char [] randBuffer = new char[length];
for (int i=0; i