View Javadoc

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.james.mime4j.codec;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  
26  final class QuotedPrintableEncoder {
27      private static final byte TAB = 0x09;
28      private static final byte SPACE = 0x20;
29      private static final byte EQUALS = 0x3D;
30      private static final byte CR = 0x0D;
31      private static final byte LF = 0x0A;
32      private static final byte QUOTED_PRINTABLE_LAST_PLAIN = 0x7E;
33      private static final int QUOTED_PRINTABLE_MAX_LINE_LENGTH = 76;
34      private static final int QUOTED_PRINTABLE_OCTETS_PER_ESCAPE = 3;
35      private static final byte[] HEX_DIGITS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
36  
37      private final byte[] inBuffer;
38      private final byte[] outBuffer;
39      private final boolean binary;
40      
41      private boolean pendingSpace;
42      private boolean pendingTab;
43      private boolean pendingCR;
44      private int nextSoftBreak;
45      private int outputIndex;
46      private OutputStream out;
47      
48      
49      public QuotedPrintableEncoder(int bufferSize, boolean binary) {
50          inBuffer = new byte[bufferSize];
51          outBuffer = new byte[3*bufferSize];
52          outputIndex = 0;
53          nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
54          out = null;
55          this.binary = binary;
56          pendingSpace = false;
57          pendingTab = false;
58          pendingCR = false;
59      }
60      
61      void initEncoding(final OutputStream out) {
62          this.out = out;
63          pendingSpace = false;
64          pendingTab = false;
65          pendingCR = false;
66          nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
67      }
68      
69      void encodeChunk(byte[] buffer, int off, int len) throws IOException {
70          for (int inputIndex = off; inputIndex < len + off; inputIndex++) {
71              encode(buffer[inputIndex]);
72          }
73      }
74      
75      void completeEncoding() throws IOException {
76          writePending();
77          flushOutput();
78      }
79      
80      public void encode(final InputStream in, final OutputStream out) throws IOException {
81          initEncoding(out);
82          int inputLength;
83          while((inputLength = in.read(inBuffer)) > -1) {
84              encodeChunk(inBuffer, 0, inputLength);
85          }
86          completeEncoding();
87      }
88      
89      private void writePending() throws IOException {
90          if (pendingSpace) {
91              plain(SPACE);
92          } else if (pendingTab) {
93              plain(TAB);
94          } else if (pendingCR) {
95              plain(CR);
96          }
97          clearPending();
98      }
99      
100     private void clearPending() throws IOException {
101         pendingSpace  = false;
102         pendingTab = false;
103         pendingCR = false;
104     }
105     
106     private void encode(byte next) throws IOException {
107         if (next == LF) {
108             if (binary) {
109                 writePending();
110                 escape(next);
111             } else {
112                 if (pendingCR) {
113                     // Expect either space or tab pending 
114                     // but not both
115                     if (pendingSpace) {
116                         escape(SPACE);
117                     } else if (pendingTab) {
118                         escape(TAB);
119                     }
120                     lineBreak();
121                     clearPending();
122                 } else {
123                     writePending();
124                     plain(next);
125                 }
126             }
127         } else if (next == CR) {
128             if (binary)  {
129                 escape(next);
130             } else {
131                 pendingCR = true;
132             }
133         } else {
134             writePending();
135             if (next == SPACE) {
136                 if (binary)  {
137                     escape(next);
138                 } else {
139                     pendingSpace = true;
140                 }
141             } else if (next == TAB) {
142                 if (binary)  {
143                     escape(next);
144                 } else {
145                     pendingTab = true;
146                 }
147             } else if (next < SPACE) {
148                 escape(next);
149             } else if (next > QUOTED_PRINTABLE_LAST_PLAIN) {
150                 escape(next);
151             } else if (next == EQUALS) {
152                 escape(next);
153             } else {
154                 plain(next);
155             }
156         }
157     }
158     
159     private void plain(byte next) throws IOException {
160         if (--nextSoftBreak <= 1) {
161             softBreak();
162         }
163         write(next);
164     }
165     
166     private void escape(byte next) throws IOException {
167         if (--nextSoftBreak <= QUOTED_PRINTABLE_OCTETS_PER_ESCAPE) {
168             softBreak();
169         }
170         
171         int nextUnsigned = next & 0xff;
172         
173         write(EQUALS);
174         --nextSoftBreak;
175         write(HEX_DIGITS[nextUnsigned >> 4]);
176         --nextSoftBreak;
177         write(HEX_DIGITS[nextUnsigned % 0x10]);
178     }
179     
180     private void write(byte next) throws IOException {
181         outBuffer[outputIndex++] = next;
182         if (outputIndex >= outBuffer.length) {
183             flushOutput();
184         }
185     }
186     
187     private void softBreak() throws IOException {
188         write(EQUALS);
189         lineBreak();
190     }
191 
192     private void lineBreak() throws IOException {
193         write(CR);
194         write(LF);
195         nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH;
196     }
197     
198     void flushOutput() throws IOException {
199         if (outputIndex < outBuffer.length) {
200             out.write(outBuffer, 0, outputIndex);
201         } else {
202             out.write(outBuffer);
203         }
204         outputIndex = 0;
205     }
206 }