package org.owasp.webgoat.lessons;

import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEParameterSpec;

import org.apache.ecs.Element;
import org.apache.ecs.ElementContainer;
import org.apache.ecs.html.B;
import org.apache.ecs.html.Input;
import org.apache.ecs.html.P;
import org.apache.ecs.html.TD;
import org.apache.ecs.html.TR;
import org.apache.ecs.html.Table;
import org.owasp.webgoat.session.ECSFactory;
import org.owasp.webgoat.session.WebSession;
import org.owasp.webgoat.util.HtmlEncoder;


/**
 *  Copyright (c) 2002 Free Software Foundation developed under the custody of the Open Web
 *  Application Security Project (http://www.owasp.org) This software package org.owasp.webgoat.is published by OWASP
 *  under the GPL. You should read and accept the LICENSE before you use, modify and/or redistribute
 *  this software.
 *
 * @author     Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @created    October 28, 2003
 */

public class Encoding extends LessonAdapter
{

	private final static String INPUT = "input";

	private final static String KEY = "key";

	// local encoders

	private static sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();

	static Map e2i = new HashMap();

	private static sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();

	// html entity list

	private static Object[][] entities =
			{
			{"quot", new Integer( 34 )}, // " - double-quote

			{"amp", new Integer( 38 )}, // & - ampersand

			{"lt", new Integer( 60 )}, // < - less-than

			{"gt", new Integer( 62 )}, // > - greater-than

			{"nbsp", new Integer( 160 )}, // non-breaking space

			{"copy", new Integer( 169 )}, // © - copyright

			{"reg", new Integer( 174 )}, // ® - registered trademark

			{"Agrave", new Integer( 192 )}, // À - uppercase A, grave accent

			{"Aacute", new Integer( 193 )}, // Á - uppercase A, acute accent

			{"Acirc", new Integer( 194 )}, // Â - uppercase A, circumflex accent

			{"Atilde", new Integer( 195 )}, // Ã - uppercase A, tilde

			{"Auml", new Integer( 196 )}, // Ä - uppercase A, umlaut

			{"Aring", new Integer( 197 )}, // Å - uppercase A, ring

			{"AElig", new Integer( 198 )}, // Æ - uppercase AE

			{"Ccedil", new Integer( 199 )}, // Ç - uppercase C, cedilla

			{"Egrave", new Integer( 200 )}, // È - uppercase E, grave accent

			{"Eacute", new Integer( 201 )}, // É - uppercase E, acute accent

			{"Ecirc", new Integer( 202 )}, // Ê - uppercase E, circumflex accent

			{"Euml", new Integer( 203 )}, // Ë - uppercase E, umlaut

			{"Igrave", new Integer( 204 )}, // Ì - uppercase I, grave accent

			{"Iacute", new Integer( 205 )}, // Í - uppercase I, acute accent

			{"Icirc", new Integer( 206 )}, // Î - uppercase I, circumflex accent

			{"Iuml", new Integer( 207 )}, // Ï - uppercase I, umlaut

			{"ETH", new Integer( 208 )}, // Ð - uppercase Eth, Icelandic

			{"Ntilde", new Integer( 209 )}, // Ñ - uppercase N, tilde

			{"Ograve", new Integer( 210 )}, // Ò - uppercase O, grave accent

			{"Oacute", new Integer( 211 )}, // Ó - uppercase O, acute accent

			{"Ocirc", new Integer( 212 )}, // Ô - uppercase O, circumflex accent

			{"Otilde", new Integer( 213 )}, // Õ - uppercase O, tilde

			{"Ouml", new Integer( 214 )}, // Ö - uppercase O, umlaut

			{"Oslash", new Integer( 216 )}, // Ø - uppercase O, slash

			{"Ugrave", new Integer( 217 )}, // Ù - uppercase U, grave accent

			{"Uacute", new Integer( 218 )}, // Ú - uppercase U, acute accent

			{"Ucirc", new Integer( 219 )}, // Û - uppercase U, circumflex accent

			{"Uuml", new Integer( 220 )}, // Ü - uppercase U, umlaut

			{"Yacute", new Integer( 221 )}, // Ý - uppercase Y, acute accent

			{"THORN", new Integer( 222 )}, // Þ - uppercase THORN, Icelandic

			{"szlig", new Integer( 223 )}, // ß - lowercase sharps, German

			{"agrave", new Integer( 224 )}, // à - lowercase a, grave accent

			{"aacute", new Integer( 225 )}, // á - lowercase a, acute accent

			{"acirc", new Integer( 226 )}, // â - lowercase a, circumflex accent

			{"atilde", new Integer( 227 )}, // ã - lowercase a, tilde

			{"auml", new Integer( 228 )}, // ä - lowercase a, umlaut

			{"aring", new Integer( 229 )}, // å - lowercase a, ring

			{"aelig", new Integer( 230 )}, // æ - lowercase ae

			{"ccedil", new Integer( 231 )}, // ç - lowercase c, cedilla

			{"egrave", new Integer( 232 )}, // è - lowercase e, grave accent

			{"eacute", new Integer( 233 )}, // é - lowercase e, acute accent

			{"ecirc", new Integer( 234 )}, // ê - lowercase e, circumflex accent

			{"euml", new Integer( 235 )}, // ë - lowercase e, umlaut

			{"igrave", new Integer( 236 )}, // ì - lowercase i, grave accent

			{"iacute", new Integer( 237 )}, // í - lowercase i, acute accent

			{"icirc", new Integer( 238 )}, // î - lowercase i, circumflex accent

			{"iuml", new Integer( 239 )}, // ï - lowercase i, umlaut

			{"igrave", new Integer( 236 )}, // ì - lowercase i, grave accent

			{"iacute", new Integer( 237 )}, // í - lowercase i, acute accent

			{"icirc", new Integer( 238 )}, // î - lowercase i, circumflex accent

			{"iuml", new Integer( 239 )}, // ï - lowercase i, umlaut

			{"eth", new Integer( 240 )}, // ð - lowercase eth, Icelandic

			{"ntilde", new Integer( 241 )}, // ñ - lowercase n, tilde

			{"ograve", new Integer( 242 )}, // ò - lowercase o, grave accent

			{"oacute", new Integer( 243 )}, // ó - lowercase o, acute accent

			{"ocirc", new Integer( 244 )}, // ô - lowercase o, circumflex accent

			{"otilde", new Integer( 245 )}, // õ - lowercase o, tilde

			{"ouml", new Integer( 246 )}, // ö - lowercase o, umlaut

			{"oslash", new Integer( 248 )}, // ø - lowercase o, slash

			{"ugrave", new Integer( 249 )}, // ù - lowercase u, grave accent

			{"uacute", new Integer( 250 )}, // ú - lowercase u, acute accent

			{"ucirc", new Integer( 251 )}, // û - lowercase u, circumflex accent

			{"uuml", new Integer( 252 )}, // ü - lowercase u, umlaut

			{"yacute", new Integer( 253 )}, // ý - lowercase y, acute accent

			{"thorn", new Integer( 254 )}, // þ - lowercase thorn, Icelandic

			{"yuml", new Integer( 255 )}, // ÿ - lowercase y, umlaut

			{"euro", new Integer( 8364 )},// Euro symbol

			};

	static Map i2e = new HashMap();

	// encryption constant

	private static byte[] salt =
			{
			(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
			(byte) 0x00
			};



	/**
	 *  Returns the base 64 decoding of a string.
	 *
	 * @param  str              Description of the Parameter
	 * @return                  Description of the Return Value
	 * @exception  IOException  Description of the Exception
	 */

	public static String base64Decode( String str ) throws IOException
	{

		byte[] b = decoder.decodeBuffer( str );

		return ( new String( b ) );
	}



	/**
	 *  Description of the Method
	 *
	 * @param  c                Description of the Parameter
	 * @return                  Description of the Return Value
	 * @exception  IOException  Description of the Exception
	 */

	public static String base64Decode( char[] c ) throws IOException
	{

		return base64Decode( new String( c ) );
	}



	/**
	 *  Description of the Method
	 *
	 * @param  c  Description of the Parameter
	 * @return    Description of the Return Value
	 */

	public static String base64Encode( char[] c )
	{

		return base64Encode( new String( c ) );
	}



	/**
	 *  Returns the base 64 encoding of a string.
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String base64Encode( String str )
	{

		byte[] b = str.getBytes();

		return ( encoder.encode( b ) );
	}



	/**
	 *  Description of the Method
	 *
	 * @param  b  Description of the Parameter
	 * @return    Description of the Return Value
	 */

	public static String base64Encode( byte[] b )
	{

		return ( encoder.encode( b ) );
	}



	/**
	 *  Description of the Method
	 *
	 * @param  s  Description of the Parameter
	 * @return    Description of the Return Value
	 */

	protected Element createContent( WebSession s )
	{

		ElementContainer ec = new ElementContainer();

		try
		{

			String userInput = s.getParser().getRawParameter( INPUT, "" );

			String userKey = s.getParser().getStringParameter( KEY, "" );

			Table table = new Table();

			TR tr = new TR();

			tr.addElement( new TD( "Enter a string: " ) );

			Input input = new Input( Input.TEXT, INPUT, userInput );

			tr.addElement( new TD().addElement( input ) );

			table.addElement( tr );

			tr = new TR();

			tr.addElement( new TD( "Enter a password (optional): " ) );

			Input key = new Input( Input.TEXT, KEY, userKey );

			tr.addElement( new TD().addElement( key ) );

			table.addElement( tr );

			tr = new TR();

			Element b = ECSFactory.makeButton( "Go!" );

			tr.addElement( new TD().setAlign( "center" ).setColSpan( 2 ).addElement( b ) );

			table.addElement( tr );

			ec.addElement( table );

			ec.addElement( new P() );

			Table t = new Table();

			t.setWidth( "100%" );

			t.setBorder( 0 );

			t.setCellSpacing( 1 );

			t.setCellPadding( 4 );

			String description;

			t.addElement( makeTitleRow( "Description", "Encoded", "Decoded" ) );

			description = "Base64 encoding is a simple reversable encoding used to encode bytes into ASCII characters. Useful for making bytes into a printable string, but provides no security.";

//			t.addElement( makeDescriptionRow( description ) );
			t.addElement( makeRow( description, base64Encode( userInput ), base64Decode( userInput ) ) );
//			t.addElement( makeSpacerRow() );

			description = "Entity encoding uses special sequences like &amp;amp; for special characters. This prevents these characters from being interpreted by most interpreters.";

			t.addElement( makeRow( description, HtmlEncoder.encode( userInput ), HtmlEncoder.decode( userInput ) ) );

			description = "Password based encryption (PBE) is strong encryption with a text password. Cannot be decrypted without the password";

			t.addElement( makeRow( description, encryptString( userInput, userKey ), decryptString( userInput, userKey ) ) );
			description = "MD5 hash is a checksum that can be used to validate a string or byte array, but cannot be reversed to find the original string or bytes. For obscure cryptographic reasons, it is better to use SHA-256 if you have a choice.";

			t.addElement( makeRow( description, hashMD5( userInput ), "Cannot reverse a hash" ) );
			
			description = "SHA-256 hash is a checksum that can be used to validate a string or byte array, but cannot be reversed to find the original string or bytes.";

			t.addElement( makeRow( description, hashSHA( userInput ), "N/A" ) );
			
			description = "Unicode encoding is...";

			t.addElement( makeRow( description, "Not Implemented", "Not Implemented" ) );
			
			description = "URL encoding is...";

			t.addElement( makeRow( description, urlEncode( userInput ), urlDecode( userInput ) ) );
			
			description = "Hex encoding simply encodes bytes into %xx format.";

			t.addElement( makeRow( description, hexEncode( userInput ), hexDecode( userInput ) ) );
			
			description = "Rot13 encoding is a way to make text unreadable, but is easily reversed and provides no security.";

			t.addElement( makeRow( description, rot13( userInput ), rot13( userInput ) ) );
			
			description = "XOR with password encoding is a weak encryption scheme that mixes a password into data.";

			t.addElement( makeRow( description, xorEncode( userInput, userKey ), xorDecode( userInput, userKey ) ) );
			
			description = "Double unicode encoding is...";

			t.addElement( makeRow( description, "Not Implemented", "Not Implemented" ) );
			
			description = "Double URL encoding is...";

			t.addElement( makeRow( description, urlEncode( urlEncode( userInput ) ), urlDecode( urlDecode( userInput ) ) ) );
			
			ec.addElement( t );

		}

		catch ( Exception e )
		{

			s.setMessage( "Error generating " + this.getClass().getName() );

			e.printStackTrace();

		}

		if ( getLessonTracker(  s ).getNumVisits() > 3 )
		{
			makeSuccess( s );
		}

		return ( ec );
	}



	/**
	 *  Convenience method for encrypting a string.
	 *
	 * @param  str  Description of the Parameter
	 * @param  pw   Description of the Parameter
	 * @return      String the encrypted string.
	 */

	public static synchronized String decryptString( String str, String pw )
	{

		try
		{

			PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec( salt, 20 );

			SecretKeyFactory kf = SecretKeyFactory.getInstance( "PBEWithMD5AndDES" );

			Cipher passwordDecryptCipher = Cipher.getInstance( "PBEWithMD5AndDES/CBC/PKCS5Padding" );

			char[] pass = pw.toCharArray();

			SecretKey k = kf.generateSecret( new javax.crypto.spec.PBEKeySpec( pass ) );

			passwordDecryptCipher.init( Cipher.DECRYPT_MODE, k, ps );

			byte[] dec = decoder.decodeBuffer( str );

			byte[] utf8 = passwordDecryptCipher.doFinal( dec );

			return new String( utf8, "UTF-8" );
		}

		catch ( Exception e )
		{

			return ( "This is not an encrypted string" );
		}

	}



	/**
	 *  Convenience method for encrypting a string.
	 *
	 * @param  str                    Description of the Parameter
	 * @param  pw                     Description of the Parameter
	 * @return                        String the encrypted string.
	 * @exception  SecurityException  Description of the Exception
	 */

	public static synchronized String encryptString( String str, String pw ) throws SecurityException
	{

		try
		{

			PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec( salt, 20 );

			SecretKeyFactory kf = SecretKeyFactory.getInstance( "PBEWithMD5AndDES" );

			Cipher passwordEncryptCipher = Cipher.getInstance( "PBEWithMD5AndDES/CBC/PKCS5Padding" );

			char[] pass = pw.toCharArray();

			SecretKey k = kf.generateSecret( new javax.crypto.spec.PBEKeySpec( pass ) );

			passwordEncryptCipher.init( Cipher.ENCRYPT_MODE, k, ps );

			byte[] utf8 = str.getBytes( "UTF-8" );

			byte[] enc = passwordEncryptCipher.doFinal( utf8 );

			return encoder.encode( enc );
		}

		catch ( Exception e )
		{

			return ( "Encryption error" );
		}

	}



	/**
	 *  Gets the category attribute of the Encoding object
	 *
	 * @return    The category value
	 */

	protected Category getDefaultCategory()
	{
		return AbstractLesson.A8;
	}


	/**
	 *  Gets the hints attribute of the HelloScreen object
	 *
	 * @return    The hints value
	 */

	public List getHints()
	{

		List hints = new ArrayList();

		hints.add( "Enter a string and press 'go'" );

		hints.add( "Enter 'abc' and notice the rot13 encoding is 'nop' ( increase each letter by 13 characters )." );

		hints.add( "Enter 'a c' and notice the url encoding is 'a+c' ( ' ' is converted to '+' )." );

		return hints;
	}



	/**
	 *  Gets the instructions attribute of the Encoding object
	 *
	 * @return    The instructions value
	 */

	public String getInstructions(WebSession s)
	{

		return "This lesson will familiarize the user with different encoding schemes.  ";
	}





	private final static Integer DEFAULT_RANKING = new Integer(15);

	protected Integer getDefaultRanking()
	{
		return DEFAULT_RANKING;
	}

	/**
	 *  Gets the title attribute of the HelloScreen object
	 *
	 * @return    The title value
	 */

	public String getTitle()
	{

		return ( "Encoding Basics" );
	}



	/**
	 *  Returns the MD5 hash of a String.
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String hashMD5( String str )
	{

		byte[] b = str.getBytes();

		MessageDigest md = null;

		try
		{

			md = MessageDigest.getInstance( "MD5" );

			md.update( b );

		}

		catch ( NoSuchAlgorithmException e )
		{

			// it's got to be there

			e.printStackTrace();

		}

		return ( base64Encode( md.digest() ) );
	}



	/**
	 *  Returns the SHA hash of a String.
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String hashSHA( String str )
	{

		byte[] b = str.getBytes();

		MessageDigest md = null;

		try
		{

			md = MessageDigest.getInstance( "SHA-256" );

			md.update( b );

		}

		catch ( NoSuchAlgorithmException e )
		{

			// it's got to be there

			e.printStackTrace();

		}

		return ( base64Encode( md.digest() ) );
	}



	/**
	 *  Description of the Method
	 *
	 * @param  hexString  Description of the Parameter
	 * @return            Description of the Return Value
	 */

	public static String hexDecode( String hexString )
	{

		try
		{

			if ( ( hexString.length() % 3 ) != 0 )
			{

				return ( "String not comprised of Hex digit pairs." );
			}

			char[] chars = new char[hexString.length()];

			char[] convChars = new char[hexString.length() / 3];

			hexString.getChars( 0, hexString.length(), chars, 0 );

			for ( int i = 1; i < hexString.length(); i += 3 )
			{

				String hexToken = new String( chars, i, 2 );

				convChars[i / 3] = (char) Integer.parseInt( hexToken, 16 );

			}

			return new String( convChars );
		}

		catch ( NumberFormatException nfe )
		{

			return ( "String not comprised of Hex digits" );
		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  asciiString  Description of the Parameter
	 * @return              Description of the Return Value
	 */

	public static String hexEncode( String asciiString )
	{

		char[] ascii = new char[asciiString.length()];

		asciiString.getChars( 0, asciiString.length(), ascii, 0 );

		StringBuffer hexBuff = new StringBuffer();

		for ( int i = 0; i < asciiString.length(); i++ )
		{

			hexBuff.append( "%" );

			hexBuff.append( Integer.toHexString( ascii[i] ) );

		}

		return hexBuff.toString().toUpperCase();
	}



	/**
	 *  The main program for the Encoding class
	 *
	 * @param  args  The command line arguments
	 */

	public static void main( String[] args )
	{

		try
		{

			String userInput = args[0];

			String userKey = args[1];

			System.out.println( "Working with: " + userInput );

			System.out.print( "Base64 encoding: " );

			System.out.println( base64Encode( userInput ) + " : " + base64Decode( userInput ) );

			System.out.print( "Entity encoding: " );

			System.out.println( HtmlEncoder.encode( userInput ) + " : " + HtmlEncoder.decode( userInput ) );

			System.out.print( "Password based encryption (PBE): " );

			System.out.println( encryptString( userInput, userKey ) + " : " + decryptString( userInput, userKey ) );

			System.out.print( "MD5 hash: " );

			System.out.println( hashMD5( userInput ) + " : " + "Cannot reverse a hash" );

			System.out.print( "SHA-256 hash: " );

			System.out.println( hashSHA( userInput ) + " : " + "Cannot reverse a hash" );

			System.out.print( "Unicode encoding: " );

			System.out.println( "Not Implemented" + " : " + "Not Implemented" );

			System.out.print( "URL encoding: " );

			System.out.println( urlEncode( userInput ) + " : " + urlDecode( userInput ) );

			System.out.print( "Hex encoding: " );

			System.out.println( hexEncode( userInput ) + " : " + hexDecode( userInput ) );

			System.out.print( "Rot13 encoding: " );

			System.out.println( rot13( userInput ) + " : " + rot13( userInput ) );

			System.out.print( "XOR with password: " );

			System.out.println( xorEncode( userInput, userKey ) + " : " + xorDecode( userInput, userKey ) );

			System.out.print( "Double unicode encoding is..." );

			System.out.println( "Not Implemented" + " : " + "Not Implemented" );

			System.out.print( "Double URL encoding: " );

			System.out.println( urlEncode( urlEncode( userInput ) ) + " : " + urlDecode( urlDecode( userInput ) ) );

		}

		catch ( Exception e )
		{

			e.printStackTrace();

		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  value1       Description of the Parameter
	 * @param  value2       Description of the Parameter
	 * @param  description  Description of the Parameter
	 * @return              Description of the Return Value
	 */

	private TR makeRow( String description, String value1, String value2 )
	{

		TD desc = new TD().addElement( description ).setBgColor( "#bbbbbb" );
		TD val1 = new TD().addElement( value1 ).setBgColor( "#dddddd" );
		TD val2 = new TD().addElement( value2 ).setBgColor( "#dddddd" );
		TR tr = new TR();

		tr.addElement( desc );
		tr.addElement( val1 );
		tr.addElement( val2 );

		return tr;
	}


	/**
	 *  Description of the Method
	 *
	 * @param  value1       Description of the Parameter
	 * @param  value2       Description of the Parameter
	 * @param  description  Description of the Parameter
	 * @return              Description of the Return Value
	 */

	private TR makeTitleRow( String description, String value1, String value2 )
	{

		TD desc = new TD().addElement( new B().addElement( description ) );

		TD val1 = new TD().addElement( new B().addElement( value1 ) );
		TD val2 = new TD().addElement( new B().addElement( value2 ) );

		desc.setAlign( "center" );

		val1.setAlign( "center" );

		val2.setAlign( "center" );

		TR tr = new TR();

		tr.addElement( desc );

		tr.addElement( val1 );

		tr.addElement( val2 );

		return ( tr );
	}



	/**
	 *  Description of the Method
	 *
	 * @param  input  Description of the Parameter
	 * @return        Description of the Return Value
	 */

	public static synchronized String rot13( String input )
	{

		StringBuffer output = new StringBuffer();

		if ( input != null )
		{

			for ( int i = 0; i < input.length(); i++ )
			{

				char inChar = input.charAt( i );

				if ( ( inChar >= 'A' ) & ( inChar <= 'Z' ) )
				{

					inChar += 13;

					if ( inChar > 'Z' )
					{

						inChar -= 26;

					}

				}

				if ( ( inChar >= 'a' ) & ( inChar <= 'z' ) )
				{

					inChar += 13;

					if ( inChar > 'z' )
					{

						inChar -= 26;

					}

				}

				output.append( inChar );

			}

		}

		return output.toString();
	}



	/**
	 *  Description of the Method
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String unicodeDecode( String str )
	{

		// FIXME: TOTALLY EXPERIMENTAL

		try
		{

			ByteBuffer bbuf = ByteBuffer.allocate( str.length() );

			bbuf.put( str.getBytes() );

			Charset charset = Charset.forName( "ISO-8859-1" );

			CharsetDecoder decoder = charset.newDecoder();

			CharBuffer cbuf = decoder.decode( bbuf );

			return ( cbuf.toString() );
		}

		catch ( Exception e )
		{

			return ( "Encoding problem" );
		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String unicodeEncode( String str )
	{

		// FIXME: TOTALLY EXPERIMENTAL

		try
		{

			Charset charset = Charset.forName( "ISO-8859-1" );

			CharsetEncoder encoder = charset.newEncoder();

			ByteBuffer bbuf = encoder.encode( CharBuffer.wrap( str ) );

			return ( new String( bbuf.array() ) );
		}

		catch ( Exception e )
		{

			return ( "Encoding problem" );
		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String urlDecode( String str )
	{

		try
		{

			return ( URLDecoder.decode( str, "UTF-8" ) );
		}

		catch ( Exception e )
		{

			return ( "Decoding error" );
		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  str  Description of the Parameter
	 * @return      Description of the Return Value
	 */

	public static String urlEncode( String str )
	{

		try
		{

			return ( URLEncoder.encode( str, "UTF-8" ) );
		}

		catch ( Exception e )
		{

			return ( "Encoding error" );
		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  input    Description of the Parameter
	 * @param  userKey  Description of the Parameter
	 * @return          Description of the Return Value
	 */

	public static synchronized char[] xor( String input, String userKey )
	{

		if ( ( userKey == null ) || ( userKey.trim().length() == 0 ) )
		{

			userKey = "Goober";

		}

		char[] xorChars = userKey.toCharArray();

		int keyLen = xorChars.length;

		char[] inputChars = null;

		char[] outputChars = null;

		if ( input != null )
		{

			inputChars = input.toCharArray();

			outputChars = new char[inputChars.length];

			for ( int i = 0; i < inputChars.length; i++ )
			{

				outputChars[i] = (char) ( inputChars[i] ^ xorChars[i % keyLen] );

			}

		}

		return outputChars;
	}



	/**
	 *  Description of the Method
	 *
	 * @param  input    Description of the Parameter
	 * @param  userKey  Description of the Parameter
	 * @return          Description of the Return Value
	 */

	public static synchronized String xorDecode( String input, String userKey )
	{

		try
		{

			String decoded = base64Decode( input );

			return new String( xor( decoded, userKey ) );
		}

		catch ( Exception e )
		{

			return "String not XOR encoded.";
		}

	}



	/**
	 *  Description of the Method
	 *
	 * @param  input    Description of the Parameter
	 * @param  userKey  Description of the Parameter
	 * @return          Description of the Return Value
	 */

	public static synchronized String xorEncode( String input, String userKey )
	{

		return base64Encode( xor( input, userKey ) );
	}

	static
	{

		for ( int i = 0; i < entities.length; ++i )
		{

			e2i.put( entities[i][0], entities[i][1] );

			i2e.put( entities[i][1], entities[i][0] );

		}

	}

}


