1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
114
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 }