1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.geronimo.mail.util; 21 22 import java.io.IOException; 23 import java.io.OutputStream; 24 import java.io.FilterOutputStream; 25 26 /** 27 * An implementation of a FilterOutputStream that encodes the 28 * stream data in BASE64 encoding format. This version does the 29 * encoding "on the fly" rather than encoding a single block of 30 * data. Since this version is intended for use by the MimeUtilty class, 31 * it also handles line breaks in the encoded data. 32 */ 33 public class Base64EncoderStream extends FilterOutputStream { 34 35 // our Filtered stream writes everything out as byte data. This allows the CRLF sequence to 36 // be written with a single call. 37 protected static final byte[] CRLF = { '\r', '\n' }; 38 39 // our hex encoder utility class. 40 protected Base64Encoder encoder = new Base64Encoder(); 41 42 // our default for line breaks 43 protected static final int DEFAULT_LINEBREAK = 76; 44 45 // Data can only be written out in complete units of 3 bytes encoded as 4. Therefore, we need to buffer 46 // as many as 2 bytes to fill out an encoding unit. 47 48 // the buffered byte count 49 protected int bufferedBytes = 0; 50 51 // we'll encode this part once it is filled up. 52 protected byte[] buffer = new byte[3]; 53 54 55 // the size we process line breaks at. If this is Integer.MAX_VALUE, no line breaks are handled. 56 protected int lineBreak; 57 58 // the number of encoded characters we've written to the stream, which determines where we 59 // insert line breaks. 60 protected int outputCount; 61 62 /** 63 * Create a Base64 encoder stream that wraps a specifed stream 64 * using the default line break size. 65 * 66 * @param out The wrapped output stream. 67 */ 68 public Base64EncoderStream(OutputStream out) { 69 this(out, DEFAULT_LINEBREAK); 70 } 71 72 73 public Base64EncoderStream(OutputStream out, int lineBreak) { 74 super(out); 75 // lines are processed only in multiple of 4, so round this down. 76 this.lineBreak = (lineBreak / 4) * 4 ; 77 } 78 79 // in order for this to work, we need to override the 3 different signatures for write 80 81 public void write(int ch) throws IOException { 82 // store this in the buffer. 83 buffer[bufferedBytes++] = (byte)ch; 84 // if the buffer is filled, encode these bytes 85 if (bufferedBytes == 3) { 86 // check for room in the current line for this character 87 checkEOL(4); 88 // write these directly to the stream. 89 encoder.encode(buffer, 0, 3, out); 90 bufferedBytes = 0; 91 // and update the line length checkers 92 updateLineCount(4); 93 } 94 } 95 96 public void write(byte [] data) throws IOException { 97 write(data, 0, data.length); 98 } 99 100 public void write(byte [] data, int offset, int length) throws IOException { 101 // if we have something in the buffer, we need to write enough bytes out to flush 102 // those into the output stream AND continue on to finish off a line. Once we're done there 103 // we can write additional data out in complete blocks. 104 while ((bufferedBytes > 0 || outputCount > 0) && length > 0) { 105 write(data[offset++]); 106 length--; 107 } 108 109 if (length > 0) { 110 // no linebreaks requested? YES!!!!!, we can just dispose of the lot with one call. 111 if (lineBreak == Integer.MAX_VALUE) { 112 encoder.encode(data, offset, length, out); 113 } 114 else { 115 // calculate the size of a segment we can encode directly as a line. 116 int segmentSize = (lineBreak / 4) * 3; 117 118 // write this out a block at a time, with separators between. 119 while (length > segmentSize) { 120 // encode a segment 121 encoder.encode(data, offset, segmentSize, out); 122 // write an EOL marker 123 out.write(CRLF); 124 offset += segmentSize; 125 length -= segmentSize; 126 } 127 128 // any remainder we write out a byte at a time to manage the groupings and 129 // the line count appropriately. 130 if (length > 0) { 131 while (length > 0) { 132 write(data[offset++]); 133 length--; 134 } 135 } 136 } 137 } 138 } 139 140 public void close() throws IOException { 141 flush(); 142 out.close(); 143 } 144 145 public void flush() throws IOException { 146 if (bufferedBytes > 0) { 147 encoder.encode(buffer, 0, bufferedBytes, out); 148 bufferedBytes = 0; 149 } 150 } 151 152 153 /** 154 * Check for whether we're about the reach the end of our 155 * line limit for an update that's about to occur. If we will 156 * overflow, then a line break is inserted. 157 * 158 * @param required The space required for this pending write. 159 * 160 * @exception IOException 161 */ 162 private void checkEOL(int required) throws IOException { 163 if (lineBreak != Integer.MAX_VALUE) { 164 // if this write would exceed the line maximum, add a linebreak to the stream. 165 if (outputCount + required > lineBreak) { 166 out.write(CRLF); 167 outputCount = 0; 168 } 169 } 170 } 171 172 /** 173 * Update the counter of characters on the current working line. 174 * This is conditional if we're not working with a line limit. 175 * 176 * @param added The number of characters just added. 177 */ 178 private void updateLineCount(int added) { 179 if (lineBreak != Integer.MAX_VALUE) { 180 outputCount += added; 181 } 182 } 183 } 184