/******************************************************************************* * Copyright (c) 2005, 2009 committers of openArchitectureWare and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * committers of openArchitectureWare - initial API and implementation *******************************************************************************/ package org.eclipse.internal.xpand2.pr.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class BASE64 { /** * Creates an <tt>OutputStream</tt> that writes base64 encoded bytes to * the given <tt>OutputStream</tt>. * * It writes a line separator every 54 bytes. * * @param out * <tt>OutputStream</tt> to write to. * * @return BASE64 encoding OutputStream */ public static OutputStream createOutputStream(final OutputStream out) { return new BASE64OutputStream(out); } /** * Creates an <tt>OutputStream</tt> that writes base64 encoded bytes to * the given <tt>OutputStream</tt> * * @param out * <tt>OutputStream</tt> to write to. * @param linebreak * iff true write a line separator every 54 bytes. * * @return BASE64 encoding OutputStream */ public static OutputStream createOutputStream(final OutputStream out, final boolean linebreak) { return new BASE64OutputStream(out, linebreak); } public static String toString(final String raw) throws IOException { return toString(raw.getBytes()); } public static String toString(final byte[] raw) throws IOException { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final OutputStream os = new BASE64OutputStream(bos, false); final InputStream is = new ByteArrayInputStream(raw); int c = 0; while ((c = is.read()) != -1) { os.write(c); } is.close(); os.close(); return new String(bos.toByteArray()); } public static byte[] toByteArray(final String b64) throws IOException { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final InputStream is = new BASE64InputStream(new ByteArrayInputStream(b64.getBytes())); int c = 0; while ((c = is.read()) != -1) { bos.write(c); } is.close(); bos.close(); return bos.toByteArray(); } private static class BASE64OutputStream extends FilterOutputStream { public BASE64OutputStream(final OutputStream out) { this(out, true); } public BASE64OutputStream(final OutputStream out, final boolean linebreak) { super(out); this.linebreak = linebreak; } // Close the stream. @Override public void close() throws IOException { // write padding and buffered bytes // System.out.println("CLOSE: "+Integer.toString(stack,2)); switch (pos % 3) { case 0: break; case 1: out.write(base64[(stack >> 18) & 0x3F]); // 18-23 out.write(base64[(stack >> 12) & 0x3F]); // 12-17 out.write(padding); out.write(padding); break; case 2: out.write(base64[(stack >> 18) & 0x3F]); // 18-23 out.write(base64[(stack >> 12) & 0x3F]); // 12-17 out.write(base64[(stack >> 6) & 0x3F]); // 6-11 out.write(padding); break; } stack = 0; pos = 0; super.close(); } private int pos = 0; private int stack = 0; private boolean linebreak = false; // Writes the specified byte to this output stream. @Override public void write(final int c) throws IOException { // DEBUG System.out.println("WRITEB64: "+c+" // ("+pos+","+Integer.toString(c,2)+","+(byte) c+")"); switch (pos % 3) { case 0: // 0-7 stack |= (c << 16) & 0xFF0000; // DEBUG System.out.println("WRITEB64 STACK: "+stack); break; case 1: // 8-15 stack |= (c << 8) & 0x00FF00; // DEBUG System.out.println("WRITEB64 STACK: "+stack); break; case 2: // 16-23 stack |= (c << 0) & 0x0000FF; // DEBUG System.out.println("WRITEB64 STACK: "+stack+ // "("+Integer.toString(stack,2)+")"); // DEBUG System.out.println("WRITEB64: WRITING!"); // write out.write(base64[(stack >> 18) & 0x3F]); // 18-23 out.write(base64[(stack >> 12) & 0x3F]); // 12-17 out.write(base64[(stack >> 6) & 0x3F]); // 6-11 out.write(base64[(stack >> 0) & 0x3F]); // 0-5 // reset stack after writing stack = 0; break; } // don't let pos increase to much. In fact we only need to know // if we have just written a chunk of 54 original bytes for line // breaking pos = (pos + 1) % 54; if (linebreak && pos == 0) { out.write(System.getProperty("line.separator").getBytes()); } } } private static final int[] base64 = { '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', '+', '/' }; private static final int padding = '='; private static final int[] bin64 = { -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, 64, -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, -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, -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, -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, -1, -1, -1, -1 }; private static class BASE64InputStream extends InputStream { public BASE64InputStream(final InputStream in) { this.in = in; } private InputStream in; // pos is the current position (mod 3) private int pos = 0; // keeps 3 current bytes and returns it on read() private int stack = 0; private int eof = -1; private int readAndSkipNonBase64() throws IOException { int c; while ((c = in.read()) != -1) { if (bin64[c] != -1) return c; } return c; } // Reads the next byte of data from this input stream. // TODO: think about abort condition (eof != -1) @Override public int read() throws IOException { // System.out.println("READ // (eof="+eof+",pos="+pos+",stack="+Integer.toString(stack,2)+")"); if (eof == -1 && pos == 0) { // read new chunk stack = 0; int count = 0; int c = 0; while (count != 4 && (c = readAndSkipNonBase64()) != -1) { if (c == padding) { if (eof == -1) { eof = count; } count++; continue; } if (bin64[c] != -1) { stack |= (bin64[c] & 0x3F) << (18 - count * 6); count++; } } // System.out.println("READ AFTER CHUNK // (eof="+eof+",pos="+pos+",stack="+Integer.toString(stack,2)+","+count+")"); if (count == 0) { eof = 0; } else { if (count != 4) throw new IOException("Invalid base64 data. Found chunk of size " + count); } } if (eof == 0) return -1; if (eof == 1) return -1; if (eof == 2 && pos > 0) return -1; if (eof == 3 && pos > 1) return -1; // return pos'th byte from stack final int ret = (stack >> (16 - pos * 8)) & 0xFF; pos = (pos + 1) % 3; return ret; } // Closes this input stream and releases any system resources @Override public void close() throws IOException { in.close(); } } }