| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 1999 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * 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 the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "<WebSig>" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * 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 THE APACHE SOFTWARE FOUNDATION 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. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation and was |
| * originally based on software copyright (c) 2001, Institute for |
| * Data Communications Systems, <http://www.nue.et-inf.uni-siegen.de/>. |
| * The development of this software was partly funded by the European |
| * Commission in the <WebSig> project in the ISIS Programme. |
| * For more information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| package org.apache.xml.security.utils; |
| |
| |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.math.BigInteger; |
| import java.util.StringTokenizer; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| import org.apache.xml.security.exceptions.Base64DecodingException; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| import org.xml.sax.InputSource; |
| |
| |
| /** |
| * Implementation of MIME's Base64 encoding and decoding conversions. |
| * Optimized code. (raw version taken from oreilly.jonathan.util) |
| * |
| * @author Anli Shundi |
| * @author Christian Geuer-Pollmann |
| * @see <A HREF="ftp://ftp.isi.edu/in-notes/rfc2045.txt">RFC 2045</A> |
| * @see org.apache.xml.security.transforms.implementations.TransformBase64Decode |
| */ |
| public class Base64 { |
| |
| /** {@link org.apache.commons.logging} logging facility */ |
| static org.apache.commons.logging.Log log = |
| org.apache.commons.logging.LogFactory.getLog(Base64.class.getName()); |
| |
| /** Field LINE_SEPARATOR */ |
| public static final String LINE_SEPARATOR = "\n"; |
| |
| /** Field BASE64DEFAULTLENGTH */ |
| public static final int BASE64DEFAULTLENGTH = 76; |
| |
| /** Field _base64length */ |
| static int _base64length = Base64.BASE64DEFAULTLENGTH; |
| |
| private Base64() { |
| // we don't allow instantiation |
| } |
| |
| /** |
| * Method setBase64WrapLength |
| * |
| * @param length |
| */ |
| public static void setBase64WrapLength(int length) { |
| Base64._base64length = length; |
| } |
| |
| /** |
| * Method getBase64WrapLength |
| * |
| * |
| */ |
| public static int getBase64WrapLength() { |
| return Base64._base64length; |
| } |
| |
| /** |
| * Returns a byte-array representation of a <code>{@link BigInteger}<code>. |
| * No sign-bit is outputed. |
| * |
| * <b>N.B.:</B> <code>{@link BigInteger}<code>'s toByteArray |
| * retunrs eventually longer arrays because of the leading sign-bit. |
| * |
| * @param big <code>BigInteger<code> to be converted |
| * @param bitlen <code>int<code> the desired length in bits of the representation |
| * @return a byte array with <code>bitlen</code> bits of <code>big</code> |
| */ |
| static byte[] getBytes(BigInteger big, int bitlen) { |
| |
| //round bitlen |
| bitlen = ((bitlen + 7) >> 3) << 3; |
| |
| if (bitlen < big.bitLength()) { |
| throw new IllegalArgumentException(I18n |
| .translate("utils.Base64.IllegalBitlength")); |
| } |
| |
| byte[] bigBytes = big.toByteArray(); |
| |
| if (((big.bitLength() % 8) != 0) |
| && (((big.bitLength() / 8) + 1) == (bitlen / 8))) { |
| return bigBytes; |
| } else { |
| |
| // some copying needed |
| int startSrc = 0; // no need to skip anything |
| int bigLen = bigBytes.length; //valid length of the string |
| |
| if ((big.bitLength() % 8) == 0) { // correct values |
| startSrc = 1; // skip sign bit |
| |
| bigLen--; // valid length of the string |
| } |
| |
| int startDst = bitlen / 8 - bigLen; //pad with leading nulls |
| byte[] resizedBytes = new byte[bitlen / 8]; |
| |
| System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen); |
| |
| return resizedBytes; |
| } |
| } |
| |
| /** |
| * Encode in Base64 the given <code>{@link BigInteger}<code>. |
| * |
| * @param big |
| * @return String with Base64 encoding |
| */ |
| public static String encode(BigInteger big) { |
| return encode(getBytes(big, big.bitLength())); |
| } |
| |
| /** |
| * Returns a byte-array representation of a <code>{@link BigInteger}<code>. |
| * No sign-bit is outputed. |
| * |
| * <b>N.B.:</B> <code>{@link BigInteger}<code>'s toByteArray |
| * retunrs eventually longer arrays because of the leading sign-bit. |
| * |
| * @param big <code>BigInteger<code> to be converted |
| * @param bitlen <code>int<code> the desired length in bits of the representation |
| * @return a byte array with <code>bitlen</code> bits of <code>big</code> |
| */ |
| public static byte[] encode(BigInteger big, int bitlen) { |
| |
| //round bitlen |
| bitlen = ((bitlen + 7) >> 3) << 3; |
| |
| if (bitlen < big.bitLength()) { |
| throw new IllegalArgumentException(I18n |
| .translate("utils.Base64.IllegalBitlength")); |
| } |
| |
| byte[] bigBytes = big.toByteArray(); |
| |
| if (((big.bitLength() % 8) != 0) |
| && (((big.bitLength() / 8) + 1) == (bitlen / 8))) { |
| return bigBytes; |
| } else { |
| |
| // some copying needed |
| int startSrc = 0; // no need to skip anything |
| int bigLen = bigBytes.length; //valid length of the string |
| |
| if ((big.bitLength() % 8) == 0) { // correct values |
| startSrc = 1; // skip sign bit |
| |
| bigLen--; // valid length of the string |
| } |
| |
| int startDst = bitlen / 8 - bigLen; //pad with leading nulls |
| byte[] resizedBytes = new byte[bitlen / 8]; |
| |
| System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen); |
| |
| return resizedBytes; |
| } |
| } |
| |
| /** |
| * Method decodeBigIntegerFromElement |
| * |
| * @param element |
| * |
| * @throws Base64DecodingException |
| */ |
| public static BigInteger decodeBigIntegerFromElement(Element element) |
| throws Base64DecodingException { |
| return new BigInteger(1, Base64.decode(element)); |
| } |
| |
| /** |
| * Method decodeBigIntegerFromText |
| * |
| * @param text |
| * |
| * @throws Base64DecodingException |
| */ |
| public static BigInteger decodeBigIntegerFromText(Text text) |
| throws Base64DecodingException { |
| return new BigInteger(1, Base64.decode(text.getData())); |
| } |
| |
| /** |
| * This method takes an (empty) Element and a BigInteger and adds the |
| * base64 encoded BigInteger to the Element. |
| * |
| * @param element |
| * @param biginteger |
| */ |
| public static void fillElementWithBigInteger(Element element, |
| BigInteger biginteger) { |
| |
| String encodedInt = encode(biginteger); |
| |
| if (encodedInt.length() > 76) { |
| encodedInt = "\n" + encodedInt + "\n"; |
| } |
| |
| Document doc = element.getOwnerDocument(); |
| Text text = doc.createTextNode(encodedInt); |
| |
| element.appendChild(text); |
| } |
| |
| /** |
| * Method decode |
| * |
| * Takes the <CODE>Text</CODE> children of the Element and interprets |
| * them as input for the <CODE>Base64.decode()</CODE> function. |
| * |
| * @param element |
| * |
| * $todo$ not tested yet |
| * @throws Base64DecodingException |
| */ |
| public static byte[] decode(Element element) throws Base64DecodingException { |
| |
| NodeList nl = element.getChildNodes(); |
| StringBuffer sb = new StringBuffer(); |
| |
| for (int i = 0; i < nl.getLength(); i++) { |
| if (nl.item(i).getNodeType() == Node.TEXT_NODE) { |
| Text t = (Text) nl.item(i); |
| |
| sb.append(t.getData()); |
| } |
| } |
| |
| return decode(sb.toString()); |
| } |
| |
| /** |
| * Method encodeToElement |
| * |
| * @param doc |
| * @param localName |
| * @param bytes |
| * |
| */ |
| public static Element encodeToElement(Document doc, String localName, |
| byte[] bytes) { |
| |
| Element el = XMLUtils.createElementInSignatureSpace(doc, localName); |
| Text text = doc.createTextNode(encode(bytes)); |
| |
| el.appendChild(text); |
| |
| return el; |
| } |
| |
| /** |
| * Method decode |
| * |
| * |
| * @param base64 |
| * |
| * |
| * @throws Base64DecodingException |
| */ |
| public static byte[] decode(byte[] base64) throws Base64DecodingException { |
| |
| try { |
| return decode(new String(base64, "UTF-8")); |
| } catch (java.io.UnsupportedEncodingException ex) { |
| |
| // should never be reached because Encoding is valid and fixed |
| return new byte[0]; |
| } |
| } |
| |
| /** |
| * Decode a Base64-encoded string to a byte array. |
| * |
| * @param base64 <code>String</code> encoded string (single line only !!) |
| * @return Decoded data in a byte array |
| * @throws Base64DecodingException |
| */ |
| public static byte[] decode(String base64) throws Base64DecodingException { |
| |
| try { |
| if (base64.length() < 30) { |
| log.debug("I was asked to decode \"" + base64 + "\""); |
| } else { |
| log.debug("I was asked to decode \"" + base64.substring(0, 20) |
| + "...\""); |
| } |
| |
| //strip whitespace from anywhere in the string. Not the most memory |
| //efficient solution but elegant anyway :-) |
| StringTokenizer tok = new StringTokenizer(base64, " \n\r\t", false); |
| StringBuffer buf = new StringBuffer(base64.length()); |
| |
| while (tok.hasMoreElements()) { |
| buf.append(tok.nextToken()); |
| } |
| |
| base64 = buf.toString(); |
| |
| int pad = 0; |
| |
| for (int i = base64.length() - 1; |
| (i > 0) && (base64.charAt(i) == '='); i--) { |
| pad++; |
| } |
| |
| int length = base64.length() / 4 * 3 - pad; |
| byte[] raw = new byte[length]; |
| |
| for (int i = 0, rawIndex = 0; i < base64.length(); |
| i += 4, rawIndex += 3) { |
| int block = (getValue(base64.charAt(i)) << 18) |
| + (getValue(base64.charAt(i + 1)) << 12) |
| + (getValue(base64.charAt(i + 2)) << 6) |
| + (getValue(base64.charAt(i + 3))); |
| |
| for (int j = 2; j >= 0; j--) { |
| if (rawIndex + j < raw.length) { |
| raw[rawIndex + j] = (byte) (block & 0xff); |
| } |
| |
| block >>= 8; |
| } |
| } |
| |
| return raw; |
| } catch (IndexOutOfBoundsException ex) { |
| throw new Base64DecodingException("utils.Base64.IllegalBitlength", ex); |
| } |
| } |
| |
| /** |
| * Encode a byte array in Base64 format and return an optionally |
| * wrapped line. |
| * |
| * @param raw <code>byte[]</code> data to be encoded |
| * @param wrap <code>int<code> length of wrapped lines; No wrapping if less than 4. |
| * @return a <code>String</code> with encoded data |
| */ |
| public static String encode(byte[] raw, int wrap) { |
| |
| //calculate length of encoded string |
| int encLen = ((raw.length + 2) / 3) * 4; |
| |
| //adjust for newlines |
| if (wrap > 3) { |
| wrap -= wrap % 4; |
| encLen += 2 * (encLen / wrap); |
| } else { //disable wrapping |
| wrap = Integer.MAX_VALUE; |
| } |
| |
| StringBuffer encoded = new StringBuffer(encLen); |
| int len3 = (raw.length / 3) * 3; |
| int outLen = 0; //length of output line |
| |
| for (int i = 0; i < len3; i += 3, outLen += 4) { |
| if (outLen + 4 > wrap) { |
| encoded.append(LINE_SEPARATOR); |
| |
| outLen = 0; |
| } |
| |
| encoded.append(encodeFullBlock(raw, i)); |
| } |
| |
| if (outLen >= wrap) { //this will produce an extra newline if needed !? Sun had it this way... |
| encoded.append(LINE_SEPARATOR); |
| } |
| |
| if (len3 < raw.length) { |
| encoded.append(encodeBlock(raw, len3)); |
| } |
| |
| return encoded.toString(); |
| } |
| |
| /** |
| * Encode a byte array and fold lines at the standard 76th character. |
| * |
| * @param raw <code>byte[]<code> to be base64 encoded |
| * @return the <code>String<code> with encoded data |
| */ |
| public static String encode(byte[] raw) { |
| return encode(raw, Base64.getBase64WrapLength()); |
| } |
| |
| /** |
| * Base64 decode the lines from the reader and return an InputStream |
| * with the bytes. |
| * |
| * |
| * @param reader |
| * @return InputStream with the decoded bytes |
| * @exception IOException passes what the reader throws |
| * @throws Base64DecodingException |
| * @throws IOException |
| */ |
| public static byte[] decode(BufferedReader reader) |
| throws IOException, Base64DecodingException { |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| String line; |
| |
| while (null != (line = reader.readLine())) { |
| byte[] bytes = decode(line); |
| |
| baos.write(bytes); |
| } |
| |
| return baos.toByteArray(); |
| } |
| |
| /** |
| * Method encodeBlock |
| * |
| * @param raw |
| * @param offset |
| * |
| */ |
| protected static char[] encodeBlock(byte[] raw, int offset) { |
| |
| int block = 0; |
| int slack = raw.length - offset - 1; |
| int end = (slack >= 2) |
| ? 2 |
| : slack; |
| |
| for (int i = 0; i < 3; i++) { |
| byte b = (offset + i < raw.length) |
| ? raw[offset + i] |
| : 0; |
| int neuter = (b < 0) |
| ? b + 256 |
| : b; |
| |
| block <<= 8; |
| block += neuter; |
| } |
| |
| char[] base64 = new char[4]; |
| |
| for (int i = 3; i >= 0; i--) { |
| int sixBit = block & 0x3f; |
| |
| base64[i] = getChar(sixBit); |
| block >>= 6; |
| } |
| |
| if (slack < 1) { |
| base64[2] = '='; |
| } |
| |
| if (slack < 2) { |
| base64[3] = '='; |
| } |
| |
| return base64; |
| } |
| |
| /** |
| * Method encodeFullBlock |
| * |
| * @param raw |
| * @param offset |
| * |
| */ |
| protected static char[] encodeFullBlock(byte[] raw, int offset) { |
| |
| int block = 0; |
| |
| for (int i = 0; i < 3; i++) { |
| |
| //byte b = raw[offset + i]; |
| //int neuter = (b < 0) ? b + 256 : b; |
| block <<= 8; |
| block += (0xff & raw[offset + i]); |
| } |
| |
| block = ((raw[offset] & 0xff) << 16) + ((raw[offset + 1] & 0xff) << 8) |
| + (raw[offset + 2] & 0xff); |
| |
| char[] base64 = new char[4]; |
| |
| for (int i = 3; i >= 0; i--) { |
| int sixBit = block & 0x3f; |
| |
| base64[i] = getChar(sixBit); |
| block >>= 6; |
| } |
| |
| return base64; |
| } |
| |
| /** |
| * Method getChar |
| * |
| * @param sixBit |
| * |
| */ |
| protected static char getChar(int sixBit) { |
| |
| if ((sixBit >= 0) && (sixBit < 26)) { |
| return (char) ('A' + sixBit); |
| } |
| |
| if ((sixBit >= 26) && (sixBit < 52)) { |
| return (char) ('a' + (sixBit - 26)); |
| } |
| |
| if ((sixBit >= 52) && (sixBit < 62)) { |
| return (char) ('0' + (sixBit - 52)); |
| } |
| |
| if (sixBit == 62) { |
| return '+'; |
| } |
| |
| if (sixBit == 63) { |
| return '/'; |
| } |
| |
| return '?'; |
| } |
| |
| /** |
| * Method getValue |
| * |
| * @param c |
| * |
| */ |
| protected static int getValue(char c) { |
| |
| if ((c >= 'A') && (c <= 'Z')) { |
| return c - 'A'; |
| } |
| |
| if ((c >= 'a') && (c <= 'z')) { |
| return c - 'a' + 26; |
| } |
| |
| if ((c >= '0') && (c <= '9')) { |
| return c - '0' + 52; |
| } |
| |
| if (c == '+') { |
| return 62; |
| } |
| |
| if (c == '/') { |
| return 63; |
| } |
| |
| if (c == '=') { |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| // boolean bInWSpace = false;//? |
| // for(int i=0, j=0, len=base64.length(); i < len; i++) { |
| // if( bInWSpace ) { |
| // if( Character.isWhitespace(base64.charAt(i)) ) { |
| // skipLen++; |
| // } else { |
| // //copy here & reset |
| // } |
| // } else { |
| // if( Character.isWhitespace(base64.charAt(i)) ) { |
| // bInWSpace = true; |
| // skipLen++; |
| // } else { |
| // //copy here & reset |
| // } |
| // } |
| // } |
| |
| /** |
| * Method main |
| * |
| * |
| * @param args |
| * |
| * @throws Exception |
| */ |
| public static void main(String[] args) throws Exception { |
| |
| DocumentBuilderFactory docBuilderFactory = |
| DocumentBuilderFactory.newInstance(); |
| DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); |
| String testString1 = |
| "<container><base64 value=\"Should be 'Hallo'\">SGFsbG8=</base64></container>"; |
| InputSource inputSource = new InputSource(new StringReader(testString1)); |
| Document doc = docBuilder.parse(inputSource); |
| Element base64Elem = |
| (Element) doc.getDocumentElement().getChildNodes().item(0); |
| |
| System.out.println(new String(decode(base64Elem))); |
| } |
| } |