1 /**
2 *
3 * Copyright 2003-2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.mail.util;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.io.UnsupportedEncodingException;
24
25 import javax.mail.internet.MimeUtility;
26
27 /**
28 * Encoder for RFC2231 encoded parameters
29 *
30 * RFC2231 string are encoded as
31 *
32 * charset'language'encoded-text
33 *
34 * and
35 *
36 * encoded-text = *(char / hexchar)
37 *
38 * where
39 *
40 * char is any ASCII character in the range 33-126, EXCEPT
41 * the characters "%" and " ".
42 *
43 * hexchar is an ASCII "%" followed by two upper case
44 * hexadecimal digits.
45 */
46
47 public class RFC2231Encoder implements Encoder
48 {
49 protected final byte[] encodingTable =
50 {
51 (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
52 (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'
53 };
54
55 protected String DEFAULT_SPECIALS = " *'%";
56 protected String specials = DEFAULT_SPECIALS;
57
58
59
60
61 protected final byte[] decodingTable = new byte[128];
62
63 protected void initialiseDecodingTable()
64 {
65 for (int i = 0; i < encodingTable.length; i++)
66 {
67 decodingTable[encodingTable[i]] = (byte)i;
68 }
69 }
70
71 public RFC2231Encoder()
72 {
73 this(null);
74 }
75
76 public RFC2231Encoder(String specials)
77 {
78 if (specials != null) {
79 this.specials = DEFAULT_SPECIALS + specials;
80 }
81 initialiseDecodingTable();
82 }
83
84
85 /**
86 * encode the input data producing an RFC2231 output stream.
87 *
88 * @return the number of bytes produced.
89 */
90 public int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
91
92 int bytesWritten = 0;
93 for (int i = off; i < (off + length); i++)
94 {
95 int ch = data[i] & 0xff;
96
97 if (ch <= 32 || ch >= 127 || specials.indexOf(ch) != -1) {
98 out.write((byte)'%');
99 out.write(encodingTable[ch >> 4]);
100 out.write(encodingTable[ch & 0xf]);
101 bytesWritten += 3;
102 }
103 else {
104
105 out.write((byte)ch);
106 bytesWritten++;
107 }
108 }
109
110 return bytesWritten;
111 }
112
113
114 /**
115 * decode the RFC2231 encoded byte data writing it to the given output stream
116 *
117 * @return the number of bytes produced.
118 */
119 public int decode(byte[] data, int off, int length, OutputStream out) throws IOException {
120 int outLen = 0;
121 int end = off + length;
122
123 int i = off;
124 while (i < end)
125 {
126 byte v = data[i++];
127
128 if (v == '%') {
129 byte b1 = decodingTable[data[i++]];
130 byte b2 = decodingTable[data[i++]];
131 out.write((b1 << 4) | b2);
132 }
133 else {
134
135 out.write(v);
136 }
137
138 outLen++;
139 }
140
141 return outLen;
142 }
143
144 /**
145 * decode the RFC2231 encoded String data writing it to the given output stream.
146 *
147 * @return the number of bytes produced.
148 */
149 public int decode(String data, OutputStream out) throws IOException
150 {
151 int length = 0;
152 int end = data.length();
153
154 int i = 0;
155 while (i < end)
156 {
157 char v = data.charAt(i++);
158 if (v == '%') {
159 byte b1 = decodingTable[data.charAt(i++)];
160 byte b2 = decodingTable[data.charAt(i++)];
161
162 out.write((b1 << 4) | b2);
163 }
164 else {
165 out.write((byte)v);
166 }
167 length++;
168 }
169
170 return length;
171 }
172
173
174 /**
175 * Encode a string as an RFC2231 encoded parameter, using the
176 * given character set and language.
177 *
178 * @param charset The source character set (the MIME version).
179 * @param language The encoding language.
180 * @param data The data to encode.
181 *
182 * @return The encoded string.
183 */
184 public String encode(String charset, String language, String data) throws IOException {
185
186 byte[] bytes = null;
187 try {
188
189
190 bytes = data.getBytes(MimeUtility.javaCharset(charset));
191 } catch (UnsupportedEncodingException e) {
192
193 return null;
194 }
195
196 StringBuffer result = new StringBuffer();
197
198
199 if (charset != null) {
200 result.append(charset);
201 }
202
203 result.append("'");
204
205
206 if (language != null) {
207 result.append(language);
208 }
209
210 result.append("'");
211
212
213 OutputStream out = new StringBufferOutputStream(result);
214
215
216 encode(bytes, 0, bytes.length, out);
217
218
219 return result.toString();
220 }
221
222
223 /**
224 * Decode an RFC2231 encoded string.
225 *
226 * @param data The data to decode.
227 *
228 * @return The decoded string.
229 * @exception IOException
230 * @exception UnsupportedEncodingException
231 */
232 public String decode(String data) throws IOException, UnsupportedEncodingException {
233
234 int charsetEnd = data.indexOf('\'');
235
236 if (charsetEnd == -1) {
237 throw new IOException("Missing charset in RFC2231 encoded value");
238 }
239
240 String charset = data.substring(0, charsetEnd);
241
242
243 int languageEnd = data.indexOf('\'', charsetEnd + 1);
244 if (languageEnd == -1) {
245 throw new IOException("Missing language in RFC2231 encoded value");
246 }
247
248 String language = data.substring(charsetEnd + 1, languageEnd);
249
250 ByteArrayOutputStream out = new ByteArrayOutputStream(data.length());
251
252
253 decode(data.substring(languageEnd + 1), out);
254
255 byte[] bytes = out.toByteArray();
256
257 return new String(bytes, 0, bytes.length, MimeUtility.javaCharset(charset));
258 }
259 }