View Javadoc

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       * set up the decoding table.
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              // character tha must be encoded?  Prefix with a '%' and encode in hex.
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                 // add unchanged.
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             // a percent is a hex character marker, need to decode a hex value.
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                 // copied over unchanged.
135                 out.write(v);
136             }
137             // always just one byte added
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             // the charset we're adding is the MIME-defined name.  We need the java version
189             // in order to extract the bytes.
190             bytes = data.getBytes(MimeUtility.javaCharset(charset));
191         } catch (UnsupportedEncodingException e) {
192             // we have a translation problem here.
193             return null;
194         }
195 
196         StringBuffer result = new StringBuffer();
197 
198         // append the character set, if we have it.
199         if (charset != null) {
200             result.append(charset);
201         }
202         // the field marker is required.
203         result.append("'");
204 
205         // and the same for the language.
206         if (language != null) {
207             result.append(language);
208         }
209         // the field marker is required.
210         result.append("'");
211 
212         // wrap an output stream around our buffer for the decoding
213         OutputStream out = new StringBufferOutputStream(result);
214 
215         // encode the data stream
216         encode(bytes, 0, bytes.length, out);
217 
218         // finis!
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         // get the end of the language field
234         int charsetEnd = data.indexOf('\'');
235         // uh oh, might not be there
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         // now pull out the language the same way
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         // decode the data
253         decode(data.substring(languageEnd + 1), out);
254 
255         byte[] bytes = out.toByteArray();
256         // build a new string from this using the java version of the encoded charset.
257         return new String(bytes, 0, bytes.length, MimeUtility.javaCharset(charset));
258     }
259 }