001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.geronimo.mail.util; 021 022 import java.io.IOException; 023 import java.io.OutputStream; 024 import java.io.FilterOutputStream; 025 026 /** 027 * An implementation of a FilterOutputStream that encodes the 028 * stream data in BASE64 encoding format. This version does the 029 * encoding "on the fly" rather than encoding a single block of 030 * data. Since this version is intended for use by the MimeUtilty class, 031 * it also handles line breaks in the encoded data. 032 */ 033 public class Base64EncoderStream extends FilterOutputStream { 034 035 // our Filtered stream writes everything out as byte data. This allows the CRLF sequence to 036 // be written with a single call. 037 protected static final byte[] CRLF = { '\r', '\n' }; 038 039 // our hex encoder utility class. 040 protected Base64Encoder encoder = new Base64Encoder(); 041 042 // our default for line breaks 043 protected static final int DEFAULT_LINEBREAK = 76; 044 045 // Data can only be written out in complete units of 3 bytes encoded as 4. Therefore, we need to buffer 046 // as many as 2 bytes to fill out an encoding unit. 047 048 // the buffered byte count 049 protected int bufferedBytes = 0; 050 051 // we'll encode this part once it is filled up. 052 protected byte[] buffer = new byte[3]; 053 054 055 // the size we process line breaks at. If this is Integer.MAX_VALUE, no line breaks are handled. 056 protected int lineBreak; 057 058 // the number of encoded characters we've written to the stream, which determines where we 059 // insert line breaks. 060 protected int outputCount; 061 062 /** 063 * Create a Base64 encoder stream that wraps a specifed stream 064 * using the default line break size. 065 * 066 * @param out The wrapped output stream. 067 */ 068 public Base64EncoderStream(OutputStream out) { 069 this(out, DEFAULT_LINEBREAK); 070 } 071 072 073 public Base64EncoderStream(OutputStream out, int lineBreak) { 074 super(out); 075 // lines are processed only in multiple of 4, so round this down. 076 this.lineBreak = (lineBreak / 4) * 4 ; 077 } 078 079 // in order for this to work, we need to override the 3 different signatures for write 080 081 public void write(int ch) throws IOException { 082 // store this in the buffer. 083 buffer[bufferedBytes++] = (byte)ch; 084 // if the buffer is filled, encode these bytes 085 if (bufferedBytes == 3) { 086 // check for room in the current line for this character 087 checkEOL(4); 088 // write these directly to the stream. 089 encoder.encode(buffer, 0, 3, out); 090 bufferedBytes = 0; 091 // and update the line length checkers 092 updateLineCount(4); 093 } 094 } 095 096 public void write(byte [] data) throws IOException { 097 write(data, 0, data.length); 098 } 099 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