1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 import java.io.FilterOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 
25 /**
26  * An OutputStream that does Base64 encoding on the data written to
27  * it, writing the resulting data to another OutputStream.
28  */
29 public class Base64OutputStream extends FilterOutputStream {
30     private final Base64.Coder coder;
31     private final int flags;
32 
33     private byte[] buffer = null;
34     private int bpos = 0;
35 
36     private static byte[] EMPTY = new byte[0];
37 
38     /**
39      * Performs Base64 encoding on the data written to the stream,
40      * writing the encoded data to another OutputStream.
41      *
42      * @param out the OutputStream to write the encoded data to
43      * @param flags bit flags for controlling the encoder; see the
44      *        constants in {@link Base64}
45      */
Base64OutputStream(OutputStream out, int flags)46     public Base64OutputStream(OutputStream out, int flags) {
47         this(out, flags, true);
48     }
49 
50     /**
51      * Performs Base64 encoding or decoding on the data written to the
52      * stream, writing the encoded/decoded data to another
53      * OutputStream.
54      *
55      * @param out the OutputStream to write the encoded data to
56      * @param flags bit flags for controlling the encoder; see the
57      *        constants in {@link Base64}
58      * @param encode true to encode, false to decode
59      *
60      * @hide
61      */
62     @UnsupportedAppUsage
Base64OutputStream(OutputStream out, int flags, boolean encode)63     public Base64OutputStream(OutputStream out, int flags, boolean encode) {
64         super(out);
65         this.flags = flags;
66         if (encode) {
67             coder = new Base64.Encoder(flags, null);
68         } else {
69             coder = new Base64.Decoder(flags, null);
70         }
71     }
72 
write(int b)73     public void write(int b) throws IOException {
74         // To avoid invoking the encoder/decoder routines for single
75         // bytes, we buffer up calls to write(int) in an internal
76         // byte array to transform them into writes of decently-sized
77         // arrays.
78 
79         if (buffer == null) {
80             buffer = new byte[1024];
81         }
82         if (bpos >= buffer.length) {
83             // internal buffer full; write it out.
84             internalWrite(buffer, 0, bpos, false);
85             bpos = 0;
86         }
87         buffer[bpos++] = (byte) b;
88     }
89 
90     /**
91      * Flush any buffered data from calls to write(int).  Needed
92      * before doing a write(byte[], int, int) or a close().
93      */
flushBuffer()94     private void flushBuffer() throws IOException {
95         if (bpos > 0) {
96             internalWrite(buffer, 0, bpos, false);
97             bpos = 0;
98         }
99     }
100 
write(byte[] b, int off, int len)101     public void write(byte[] b, int off, int len) throws IOException {
102         if (len <= 0) return;
103         flushBuffer();
104         internalWrite(b, off, len, false);
105     }
106 
close()107     public void close() throws IOException {
108         IOException thrown = null;
109         try {
110             flushBuffer();
111             internalWrite(EMPTY, 0, 0, true);
112         } catch (IOException e) {
113             thrown = e;
114         }
115 
116         try {
117             if ((flags & Base64.NO_CLOSE) == 0) {
118                 out.close();
119             } else {
120                 out.flush();
121             }
122         } catch (IOException e) {
123             if (thrown == null) {
124                 thrown = e;
125             } else {
126                 thrown.addSuppressed(e);
127             }
128         }
129 
130         if (thrown != null) {
131             throw thrown;
132         }
133     }
134 
135     /**
136      * Write the given bytes to the encoder/decoder.
137      *
138      * @param finish true if this is the last batch of input, to cause
139      *        encoder/decoder state to be finalized.
140      */
internalWrite(byte[] b, int off, int len, boolean finish)141     private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException {
142         coder.output = embiggen(coder.output, coder.maxOutputSize(len));
143         if (!coder.process(b, off, len, finish)) {
144             throw new Base64DataException("bad base-64");
145         }
146         out.write(coder.output, 0, coder.op);
147     }
148 
149     /**
150      * If b.length is at least len, return b.  Otherwise return a new
151      * byte array of length len.
152      */
embiggen(byte[] b, int len)153     private byte[] embiggen(byte[] b, int len) {
154         if (b == null || b.length < len) {
155             return new byte[len];
156         } else {
157             return b;
158         }
159     }
160 }
161