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 @android.ravenwood.annotation.RavenwoodKeepWholeClass
30 public class Base64OutputStream extends FilterOutputStream {
31     private final Base64.Coder coder;
32     private final int flags;
33 
34     private byte[] buffer = null;
35     private int bpos = 0;
36 
37     private static byte[] EMPTY = new byte[0];
38 
39     /**
40      * Performs Base64 encoding on the data written to the stream,
41      * writing the encoded data to another OutputStream.
42      *
43      * @param out the OutputStream to write the encoded data to
44      * @param flags bit flags for controlling the encoder; see the
45      *        constants in {@link Base64}
46      */
Base64OutputStream(OutputStream out, int flags)47     public Base64OutputStream(OutputStream out, int flags) {
48         this(out, flags, true);
49     }
50 
51     /**
52      * Performs Base64 encoding or decoding on the data written to the
53      * stream, writing the encoded/decoded data to another
54      * OutputStream.
55      *
56      * @param out the OutputStream to write the encoded data to
57      * @param flags bit flags for controlling the encoder; see the
58      *        constants in {@link Base64}
59      * @param encode true to encode, false to decode
60      *
61      * @hide
62      */
63     @UnsupportedAppUsage
Base64OutputStream(OutputStream out, int flags, boolean encode)64     public Base64OutputStream(OutputStream out, int flags, boolean encode) {
65         super(out);
66         this.flags = flags;
67         if (encode) {
68             coder = new Base64.Encoder(flags, null);
69         } else {
70             coder = new Base64.Decoder(flags, null);
71         }
72     }
73 
write(int b)74     public void write(int b) throws IOException {
75         // To avoid invoking the encoder/decoder routines for single
76         // bytes, we buffer up calls to write(int) in an internal
77         // byte array to transform them into writes of decently-sized
78         // arrays.
79 
80         if (buffer == null) {
81             buffer = new byte[1024];
82         }
83         if (bpos >= buffer.length) {
84             // internal buffer full; write it out.
85             internalWrite(buffer, 0, bpos, false);
86             bpos = 0;
87         }
88         buffer[bpos++] = (byte) b;
89     }
90 
91     /**
92      * Flush any buffered data from calls to write(int).  Needed
93      * before doing a write(byte[], int, int) or a close().
94      */
flushBuffer()95     private void flushBuffer() throws IOException {
96         if (bpos > 0) {
97             internalWrite(buffer, 0, bpos, false);
98             bpos = 0;
99         }
100     }
101 
write(byte[] b, int off, int len)102     public void write(byte[] b, int off, int len) throws IOException {
103         if (len <= 0) return;
104         flushBuffer();
105         internalWrite(b, off, len, false);
106     }
107 
close()108     public void close() throws IOException {
109         IOException thrown = null;
110         try {
111             flushBuffer();
112             internalWrite(EMPTY, 0, 0, true);
113         } catch (IOException e) {
114             thrown = e;
115         }
116 
117         try {
118             if ((flags & Base64.NO_CLOSE) == 0) {
119                 out.close();
120             } else {
121                 out.flush();
122             }
123         } catch (IOException e) {
124             if (thrown == null) {
125                 thrown = e;
126             } else {
127                 thrown.addSuppressed(e);
128             }
129         }
130 
131         if (thrown != null) {
132             throw thrown;
133         }
134     }
135 
136     /**
137      * Write the given bytes to the encoder/decoder.
138      *
139      * @param finish true if this is the last batch of input, to cause
140      *        encoder/decoder state to be finalized.
141      */
internalWrite(byte[] b, int off, int len, boolean finish)142     private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException {
143         coder.output = embiggen(coder.output, coder.maxOutputSize(len));
144         if (!coder.process(b, off, len, finish)) {
145             throw new Base64DataException("bad base-64");
146         }
147         out.write(coder.output, 0, coder.op);
148     }
149 
150     /**
151      * If b.length is at least len, return b.  Otherwise return a new
152      * byte array of length len.
153      */
embiggen(byte[] b, int len)154     private byte[] embiggen(byte[] b, int len) {
155         if (b == null || b.length < len) {
156             return new byte[len];
157         } else {
158             return b;
159         }
160     }
161 }
162