/* 
 * Copyright 1999,2005 The Apache Software Foundation. 
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0 
 *  
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 
 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.io.StringWriter; 
import java.io.Writer; 
import java.lang.reflect.UndeclaredThrowableException; 
 
import org.xml.sax.ContentHandler; 
import org.xml.sax.SAXException; 
 
 
/** Performs Base64 encoding and/or decoding. This is an on-the-fly decoder: Unlike, 
 * for example, the commons-codec classes, it doesn't depend on byte arrays. In 
 * other words, it has an extremely low memory profile. This is well suited even 
 * for very large byte streams. 
 */ 
public class Base64 { 
  /** An exception of this type is thrown, if the decoded 
   * character stream contains invalid input. 
   */ 
  public static class DecodingException extends IOException { 
    private static final long serialVersionUID = 3257006574836135478L; 
    DecodingException(String pMessage) { super(pMessage); } 
  } 
 
  /** An exception of this type is thrown by the {@link SAXEncoder}, 
   * if writing to the target handler causes a SAX exception. 
   * This class is required, because the {@link IOException} 
   * allows no cause until Java 1.3. 
   */ 
  public static class SAXIOException extends IOException { 
    private static final long serialVersionUID = 3258131345216451895L; 
    final SAXException saxException; 
    SAXIOException(SAXException e) { 
      super(); 
      saxException = e; 
    } 
    /** Returns the encapsulated {@link SAXException}. 
     * @return An exception, which was thrown when invoking 
     * {@link ContentHandler#characters(char[], int, int)}. 
     */ 
    public SAXException getSAXException() { return saxException; } 
  } 
 
 
  /** Default line separator: \n 
   */ 
  public static final String LINE_SEPARATOR = "\n"; 
 
  /** Default size for line wrapping. 
   */ 
  public static final int LINE_SIZE = 76; 
 
  /** 
     * This array is a lookup table that translates 6-bit positive integer 
     * index values into their "Base64 Alphabet" equivalents as specified  
     * in Table 1 of RFC 2045. 
     */ 
    private static final char intToBase64[] = { 
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 
    }; 
 
    /** 
     * This array is a lookup table that translates unicode characters 
     * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) 
     * into their 6-bit positive integer equivalents.  Characters that 
     * are not in the Base64 alphabet but fall within the bounds of the 
     * array are translated to -1. 
     */ 
    private static final byte base64ToInt[] = { 
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
        -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 
        55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 
        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 
        24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 
        35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 
    }; 
 
  /** An encoder is an object, which is able to encode byte array 
   * in blocks of three bytes. Any such block is converted into an 
   * array of four bytes. 
   */ 
  public static abstract class Encoder { 
    private int num, numBytes; 
    private final char[] charBuffer; 
    private int charOffset; 
    private final int wrapSize; 
    private final int skipChars; 
    private final String sep; 
    private int lineChars = 0; 
    /** Creates a new instance. 
     * @param pBuffer The encoders buffer. The encoder will 
     * write to the buffer as long as possible. If the 
     * buffer is full or the end of data is signaled, then 
     * the method {@link #writeBuffer(char[], int, int)} 
     * will be invoked. 
     * @param pWrapSize A nonzero value indicates, that a line 
     * wrap should be performed after the given number of 
     * characters. The value must be a multiple of 4. Zero 
     * indicates, that no line wrap should be performed. 
     * @param pSep The eol sequence being used to terminate 
     * a line in case of line wraps. May be null, in which 
     * case the default value {@link Base64#LINE_SEPARATOR} 
     * is being used. 
     */ 
    protected Encoder(char[] pBuffer, int pWrapSize, String pSep) { 
      charBuffer = pBuffer; 
      sep = pSep == null ? null : Base64.LINE_SEPARATOR; 
      skipChars = pWrapSize == 0 ? 4 : 4 + sep.length(); 
      wrapSize = skipChars == 4 ? 0 : pWrapSize; 
      if (wrapSize < 0  ||  wrapSize %4 > 0) { 
        throw new IllegalArgumentException("Illegal argument for wrap size: " + pWrapSize 
                           + "(Expected nonnegative multiple of 4)"); 
      } 
      if (pBuffer.length < skipChars) { 
        throw new IllegalArgumentException("The buffer must contain at least " + skipChars 
                           + " characters, but has " + pBuffer.length); 
      } 
    } 
    /** Called for writing the buffer contents to the target. 
     * @param pChars The buffer being written. 
     * @param pOffset Offset of first character being written. 
     * @param pLen Number of characters being written. 
     * @throws IOException Writing to the destination failed. 
     */ 
    protected abstract void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException; 
 
    private void wrap() { 
      for (int j = 0;  j < sep.length();  j++) { 
        charBuffer[charOffset++] = sep.charAt(j); 
      } 
      lineChars = 0; 
    } 
 
    /** Encodes the given byte array. 
     * @param pBuffer Byte array being encoded. 
     * @param pOffset Offset of first byte being encoded. 
     * @param pLen Number of bytes being encoded. 
     * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)} method 
     * for writing the encoded data failed. 
     */ 
    public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException { 
      for(int i = 0;  i < pLen;  i++) { 
        int b = pBuffer[pOffset++]; 
        if (b < 0) { b += 256; } 
        num = (num << 8) + b; 
        if (++numBytes == 3) { 
          charBuffer[charOffset++] = intToBase64[num >> 18]; 
          charBuffer[charOffset++] = intToBase64[(num >> 12) & 0x3f]; 
          charBuffer[charOffset++] = intToBase64[(num >> 6) & 0x3f]; 
          charBuffer[charOffset++] = intToBase64[num & 0x3f]; 
          if (wrapSize > 0) { 
            lineChars += 4; 
            if (lineChars >= wrapSize) { 
              wrap(); 
            } 
          } 
          num = 0; 
          numBytes = 0; 
          if (charOffset + skipChars > charBuffer.length) { 
            writeBuffer(charBuffer, 0, charOffset); 
            charOffset = 0; 
          } 
        } 
      } 
    } 
    /** Writes any currently buffered data to the destination. 
     * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)} 
     * method for writing the encoded data failed. 
     */ 
    public void flush() throws IOException { 
      if (numBytes > 0) { 
        if (numBytes == 1) { 
          charBuffer[charOffset++] = intToBase64[num >> 2]; 
          charBuffer[charOffset++] = intToBase64[(num << 4) & 0x3f]; 
          charBuffer[charOffset++] = '='; 
          charBuffer[charOffset++] = '='; 
        } else { 
          charBuffer[charOffset++] = intToBase64[num >> 10]; 
          charBuffer[charOffset++] = intToBase64[(num >> 4) & 0x3f]; 
          charBuffer[charOffset++] = intToBase64[(num << 2) & 0x3f]; 
          charBuffer[charOffset++] = '='; 
        } 
        lineChars += 4; 
        num = 0; 
        numBytes = 0; 
      } 
      if (wrapSize > 0  &&  lineChars > 0) { 
        wrap(); 
      } 
      if (charOffset > 0) { 
        writeBuffer(charBuffer, 0, charOffset); 
        charOffset = 0; 
      } 
    } 
  } 
 
  /** An {@link OutputStream}, which is writing to the given 
   * {@link Encoder}. 
   */ 
  public static class EncoderOutputStream extends OutputStream { 
    private final Encoder encoder; 
    /** Creates a new instance, which is creating 
     * output using the given {@link Encoder}. 
     * @param pEncoder The base64 encoder being used. 
     */ 
    public EncoderOutputStream(Encoder pEncoder) { 
      encoder = pEncoder; 
    } 
    private final byte[] oneByte = new byte[1]; 
    public void write(int b) throws IOException { 
      oneByte[0] = (byte) b; 
      encoder.write(oneByte, 0, 1); 
    } 
    public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException { 
      encoder.write(pBuffer, pOffset, pLen); 
    } 
    public void close() throws IOException { 
      encoder.flush(); 
    } 
  } 
 
  /** Returns an {@link OutputStream}, that encodes its input in Base64 
   * and writes it to the given {@link Writer}. If the Base64 stream 
   * ends, then the output streams {@link OutputStream#close()} method 
   * must be invoked. Note, that this will <em>not</em> close the 
   * target {@link Writer}! 
   * @param pWriter Target writer. 
   * @return An output stream, encoding its input in Base64 and writing 
   * the output to the writer <code>pWriter</code>. 
   */ 
  public static OutputStream newEncoder(Writer pWriter) { 
    return newEncoder(pWriter, LINE_SIZE, LINE_SEPARATOR); 
  } 
 
  /** Returns an {@link OutputStream}, that encodes its input in Base64 
   * and writes it to the given {@link Writer}. If the Base64 stream 
   * ends, then the output streams {@link OutputStream#close()} method 
   * must be invoked. Note, that this will <em>not</em> close the 
   * target {@link Writer}! 
   * @param pWriter Target writer. 
   * @param pLineSize Size of one line in characters, must be a multiple 
   * of four. Zero indicates, that no line wrapping should occur. 
   * @param pSeparator Line separator or null, in which case the default value 
   * {@link #LINE_SEPARATOR} is used. 
   * @return An output stream, encoding its input in Base64 and writing 
   * the output to the writer <code>pWriter</code>. 
   */ 
  public static OutputStream newEncoder(final Writer pWriter, int pLineSize, String pSeparator) { 
    final Encoder encoder = new Encoder(new char[4096], pLineSize, pSeparator){ 
      protected void writeBuffer(char[] pBuffer, int pOffset, int pLen) throws IOException { 
        pWriter.write(pBuffer, pOffset, pLen); 
      } 
    }; 
    return new EncoderOutputStream(encoder); 
  } 
 
  /** An {@link Encoder}, which is writing to a SAX content handler. 
   * This is typically used for embedding a base64 stream into an 
   * XML document. 
   */ 
  public static class SAXEncoder extends Encoder { 
    private final ContentHandler handler; 
    /** Creates a new instance. 
     * @param pBuffer The encoders buffer. 
     * @param pWrapSize A nonzero value indicates, that a line 
     * wrap should be performed after the given number of 
     * characters. The value must be a multiple of 4. Zero 
     * indicates, that no line wrap should be performed. 
     * @param pSep The eol sequence being used to terminate 
     * a line in case of line wraps. May be null, in which 
     * case the default value {@link Base64#LINE_SEPARATOR} 
     * is being used. 
     * @param pHandler The target handler. 
     */ 
    public SAXEncoder(char[] pBuffer, int pWrapSize, String pSep, 
              ContentHandler pHandler) { 
      super(pBuffer, pWrapSize, pSep); 
      handler = pHandler; 
    } 
    /** Writes to the content handler. 
     * @throws SAXIOException Writing to the content handler 
     * caused a SAXException. 
     */ 
    protected void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException { 
      try { 
        handler.characters(pChars, pOffset, pLen); 
      } catch (SAXException e) { 
        throw new SAXIOException(e); 
      } 
    } 
  } 
 
  /** Converts the given byte array into a base64 encoded character 
   * array. 
   * @param pBuffer The buffer being encoded. 
   * @param pOffset Offset in buffer, where to begin encoding. 
   * @param pLength Number of bytes being encoded. 
   * @return Character array of encoded bytes. 
   */ 
  public static String encode(byte[] pBuffer, int pOffset, int pLength) { 
    return encode(pBuffer, pOffset, pLength, LINE_SIZE, LINE_SEPARATOR); 
  } 
 
  /** Converts the given byte array into a base64 encoded character 
   * array. 
   * @param pBuffer The buffer being encoded. 
   * @param pOffset Offset in buffer, where to begin encoding. 
   * @param pLength Number of bytes being encoded. 
   * @param pLineSize Size of one line in characters, must be a multiple 
   * of four. Zero indicates, that no line wrapping should occur. 
   * @param pSeparator Line separator or null, in which case the default value 
   * {@link #LINE_SEPARATOR} is used. 
   * @return Character array of encoded bytes. 
   */ 
  public static String encode(byte[] pBuffer, int pOffset, int pLength, 
                int pLineSize, String pSeparator) { 
    StringWriter sw = new StringWriter(); 
    OutputStream ostream = newEncoder(sw, pLineSize, pSeparator); 
    try { 
      ostream.write(pBuffer, pOffset, pLength); 
      ostream.close(); 
    } catch (IOException e) { 
      throw new UndeclaredThrowableException(e); 
    } 
    return sw.toString(); 
  } 
 
  /** Converts the given byte array into a base64 encoded character 
   * array with the line size {@link #LINE_SIZE} and the separator 
   * {@link #LINE_SEPARATOR}. 
   * @param pBuffer The buffer being encoded. 
   * @return Character array of encoded bytes. 
   */ 
  public static String encode(byte[] pBuffer) { 
    return encode(pBuffer, 0, pBuffer.length); 
  } 
 
  /** An encoder is an object, which is able to decode char arrays 
   * in blocks of four bytes. Any such block is converted into a 
   * array of three bytes. 
   */ 
  public static abstract class Decoder { 
    private final byte[] byteBuffer; 
    private int byteBufferOffset; 
    private int num, numBytes; 
    private int eofBytes; 
    /** Creates a new instance. 
     * @param pBufLen The decoders buffer size. The decoder will 
     * store up to this number of decoded bytes before invoking 
     * {@link #writeBuffer(byte[],int,int)}. 
     */ 
    protected Decoder(int pBufLen) { 
      byteBuffer = new byte[pBufLen]; 
    } 
    /** Called for writing the decoded bytes to the destination. 
     * @param pBuffer The byte array being written. 
     * @param pOffset Offset of the first byte being written. 
     * @param pLen Number of bytes being written. 
     * @throws IOException Writing to the destination failed. 
     */ 
    protected abstract void writeBuffer(byte[] pBuffer, int pOffset, int pLen) throws IOException; 
    /** Converts the Base64 encoded character array. 
     * @param pData The character array being decoded. 
     * @param pOffset Offset of first character being decoded. 
     * @param pLen Number of characters being decoded. 
     * @throws DecodingException Decoding failed. 
     * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)} 
     * method failed. 
     */ 
    public void write(char[] pData, int pOffset, int pLen) throws IOException { 
      for (int i = 0;  i < pLen;  i++) { 
        char c = pData[pOffset++]; 
        if (Character.isWhitespace(c)) { 
          continue; 
        } 
        if (c == '=') { 
          ++eofBytes; 
          num = num << 6; 
          switch(++numBytes) { 
            case 1: 
            case 2: 
              throw new DecodingException("Unexpected end of stream character (=)"); 
            case 3: 
              // Wait for the next '=' 
              break; 
            case 4: 
              byteBuffer[byteBufferOffset++] = (byte) (num >> 16); 
              if (eofBytes == 1) { 
                byteBuffer[byteBufferOffset++] = (byte) (num >> 8); 
              } 
              writeBuffer(byteBuffer, 0, byteBufferOffset); 
              byteBufferOffset = 0; 
              break; 
            case 5: 
              throw new DecodingException("Trailing garbage detected"); 
            default: 
              throw new IllegalStateException("Invalid value for numBytes"); 
          } 
        } else { 
          if (eofBytes > 0) { 
            throw new DecodingException("Base64 characters after end of stream character (=) detected."); 
          } 
          int result; 
          if (c >= 0  &&  c < base64ToInt.length) { 
            result = base64ToInt[c]; 
            if (result >= 0) { 
              num = (num << 6) + result; 
              if (++numBytes == 4) { 
                byteBuffer[byteBufferOffset++] = (byte) (num >> 16); 
                byteBuffer[byteBufferOffset++] = (byte) ((num >> 8) & 0xff); 
                byteBuffer[byteBufferOffset++] = (byte) (num & 0xff); 
                if (byteBufferOffset + 3 > byteBuffer.length) { 
                  writeBuffer(byteBuffer, 0, byteBufferOffset); 
                  byteBufferOffset = 0; 
                } 
                num = 0; 
                numBytes = 0; 
              } 
              continue; 
            } 
            } 
          if (!Character.isWhitespace(c)) { 
            throw new DecodingException("Invalid Base64 character: " + (int) c); 
          } 
        } 
      } 
    } 
    /** Indicates, that no more data is being expected. Writes all currently 
     * buffered data to the destination by invoking {@link #writeBuffer(byte[],int,int)}. 
     * @throws DecodingException Decoding failed (Unexpected end of file). 
     * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)} method failed. 
     */ 
    public void flush() throws IOException { 
      if (numBytes != 0  &&  numBytes != 4) { 
        throw new DecodingException("Unexpected end of file"); 
      } 
      if (byteBufferOffset > 0) { 
        writeBuffer(byteBuffer, 0, byteBufferOffset); 
        byteBufferOffset = 0; 
      } 
    } 
  } 
 
  /** Returns a {@link Writer}, that decodes its Base64 encoded 
   * input and writes it to the given {@link OutputStream}. 
   * Note, that the writers {@link Writer#close()} method will 
   * <em>not</em> close the output stream <code>pStream</code>! 
   * @param pStream Target output stream. 
   * @return An output stream, encoding its input in Base64 and writing 
   * the output to the writer <code>pWriter</code>. 
   */ 
  public Writer newDecoder(final OutputStream pStream) { 
    return new Writer(){ 
      private final Decoder decoder = new Decoder(1024){ 
        protected void writeBuffer(byte[] pBytes, int pOffset, int pLen) throws IOException { 
          pStream.write(pBytes, pOffset, pLen); 
        } 
      }; 
      public void close() throws IOException { 
        flush(); 
      } 
      public void flush() throws IOException { 
        decoder.flush(); 
        pStream.flush(); 
      } 
      public void write(char[] cbuf, int off, int len) throws IOException { 
        decoder.write(cbuf, off, len); 
      } 
    }; 
  } 
 
  /** Converts the given base64 encoded character buffer into a byte array. 
   * @param pBuffer The character buffer being decoded. 
   * @param pOffset Offset of first character being decoded. 
   * @param pLength Number of characters being decoded. 
   * @return Converted byte array 
   * @throws DecodingException The input character stream contained invalid data. 
   */ 
  public static byte[] decode(char[] pBuffer, int pOffset, int pLength) throws DecodingException { 
    final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    Decoder d = new Decoder(1024){ 
      protected void writeBuffer(byte[] pBuf, int pOff, int pLen) throws IOException { 
        baos.write(pBuf, pOff, pLen); 
      } 
    }; 
    try { 
      d.write(pBuffer, pOffset, pLength); 
      d.flush(); 
    } catch (DecodingException e) { 
      throw e; 
    } catch (IOException e) { 
      throw new UndeclaredThrowableException(e); 
    } 
    return baos.toByteArray(); 
  } 
 
  /** Converts the given base64 encoded character buffer into a byte array. 
   * @param pBuffer The character buffer being decoded. 
   * @return Converted byte array 
   * @throws DecodingException The input character stream contained invalid data. 
   */ 
  public static byte[] decode(char[] pBuffer) throws DecodingException { 
    return decode(pBuffer, 0, pBuffer.length); 
  } 
 
  /** Converts the given base64 encoded String into a byte array. 
   * @param pBuffer The string being decoded. 
   * @return Converted byte array 
   * @throws DecodingException The input character stream contained invalid data. 
   */ 
  public static byte[] decode(String pBuffer) throws DecodingException { 
    return decode(pBuffer.toCharArray()); 
  } 
} 
 
    
     
     
     
     
     
  
  |