001    /*
002     * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
003     *
004     * http://uio.imagero.com
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions are met:
008     *
009     *  o Redistributions of source code must retain the above copyright notice,
010     *    this list of conditions and the following disclaimer.
011     *
012     *  o Redistributions in binary form must reproduce the above copyright notice,
013     *    this list of conditions and the following disclaimer in the documentation
014     *    and/or other materials provided with the distribution.
015     *
016     *  o Neither the name of Andrey Kuznetsov nor the names of
017     *    its contributors may be used to endorse or promote products derived
018     *    from this software without specific prior written permission.
019     *
020     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
022     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
023     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
027     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
028     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
029     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
030     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031     */
032    
033    package com.imagero.uio.io;
034    
035    import java.io.ByteArrayInputStream;
036    import java.io.ByteArrayOutputStream;
037    import java.io.IOException;
038    import java.io.InputStream;
039    import java.io.OutputStream;
040    import java.io.Reader;
041    import java.io.Writer;
042    
043    /**
044     * This class shows how simple and straightforward can be implementation
045     * of base 64 codec using BitInputStream and BitOutputStream
046     * @author Andrey Kuznetsov
047     */
048    public class Base64 {
049    
050        private static final char encodeTable[] = {
051            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
052            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
053            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
054            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
055        };
056    
057        static int decodeTable [] = new int[0x100];
058        static int lineLength = 72;
059    
060        public static int getLineLength() {
061            return lineLength;
062        }
063    
064        public static void setLineLength(int lineLength) {
065            Base64.lineLength = lineLength;
066        }
067    
068    
069        static {
070            for (int i = 0; i < decodeTable.length; i++) {
071                decodeTable[i] = -1;
072            }
073            for (int j = 0; j < encodeTable.length; j++) {
074                decodeTable[encodeTable[j]] = j;
075            }
076        }
077    
078        private static final char [] crlf = "\n".toCharArray();
079    
080        /**
081         * base 64 encode
082         * @param b data to encode
083         * @return base 64 encoded String
084         * @throws IOException
085         */
086        public static String base64Encode(byte[] b) throws IOException {
087            BitInputStream bis = new BitInputStream(new ByteArrayInputStream(b));
088            bis.setBitsToRead(6);
089            StringBuffer sb = new StringBuffer();
090            int cnt = 0;
091            while (true) {
092                int c = bis.read();
093                //EOF reached
094                if (c == -1) {
095                    break;
096                }
097                sb.append(encodeTable[c]);
098                if(++cnt == lineLength) {
099                    sb.append("\n");
100                    cnt = 0;
101                }
102            }
103            while ((sb.length() % 4) != 0) {
104                //padding
105                sb.append('=');
106            }
107            return sb.toString();
108        }
109    
110        /**
111         * base64 encode data from InputStream and write ict to given character stream
112         * @param in InputStream
113         * @param out Writer
114         * @throws IOException
115         */
116        public static void base64Encode(InputStream in, Writer out) throws IOException {
117            BitInputStream bis = new BitInputStream(in);
118            char[] buffer = new char[4000];
119            bis.setBitsToRead(6);
120            int cnt = 0;
121            int bufferPtr = 0;
122            while (true) {
123                int c = bis.read();
124                //EOF reached
125                if (c == -1) {
126                    break;
127                }
128                buffer[bufferPtr++] = encodeTable[c];
129                if(++cnt == lineLength) {
130                    if(bufferPtr + crlf.length >= buffer.length) {
131                        out.write(buffer, 0, bufferPtr);
132                        bufferPtr = 0;
133                    }
134                    for (int i = 0; i < crlf.length; i++) {
135                       buffer[bufferPtr++] = crlf[i];
136                    }
137                    cnt = 0;
138                }
139                if (bufferPtr == buffer.length) {
140                    out.write(buffer);
141                    bufferPtr = 0;
142                }
143            }
144            while ((bufferPtr % 4) != 0) {
145                //padding
146                buffer[bufferPtr++] ='=';
147            }
148            if (bufferPtr > 0) {
149                out.write(buffer, 0, bufferPtr);
150            }
151        }
152    
153        /**
154         * decode base 64 encoded string
155         * @param s String to decode
156         * @return byte array
157         * @throws IOException
158         */
159        public static byte[] base64Decode(String s) throws IOException {
160            char[] chrs = s.toCharArray();
161            ByteArrayOutputStream bout = new ByteArrayOutputStream();
162            BitOutputStream bos = new BitOutputStream(bout);
163            bos.setBitsToWrite(6);
164    
165            boolean flush = true;
166    
167            for (int i = 0; i < chrs.length; i++) {
168                char c = chrs[i];
169                //start of padding
170                if (c == '=') {
171                    flush = false;
172                    break;
173                }
174                int c0 = decodeTable[c];
175                if(c0 != -1) {
176                    bos.write(c0);
177                }
178            }
179            if (flush) {
180                bos.flush();
181            }
182            return bout.toByteArray();
183        }
184    
185    
186        /**
187         * decode base64 encoded character stream and write it to given OutputStream
188         * @param in Reader character stream
189         * @param out OutputStream
190         * @throws IOException
191         */
192        public static void base64Decode(Reader in, OutputStream out) throws IOException {
193            BitOutputStream bos = new BitOutputStream(out);
194            bos.setBitsToWrite(6);
195    
196            boolean flush = true;
197    
198            while (true) {
199                int c = in.read();
200                if (c == -1) {
201                    break;
202                }
203                //start of padding
204                if (c == '=') {
205                    flush = false;
206                    break;
207                }
208                final int b = decodeTable[c];
209                if(b != -1) {
210                    bos.write(b);
211                }
212            }
213            if (flush) {
214                bos.flush();
215            }
216        }
217    }