1bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook/****************************************************************
2bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Licensed to the Apache Software Foundation (ASF) under one   *
3bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * or more contributor license agreements.  See the NOTICE file *
4bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * distributed with this work for additional information        *
5bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * regarding copyright ownership.  The ASF licenses this file   *
6bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * to you under the Apache License, Version 2.0 (the            *
7bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * "License"); you may not use this file except in compliance   *
8bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * with the License.  You may obtain a copy of the License at   *
9bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook *                                                              *
10bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook *   http://www.apache.org/licenses/LICENSE-2.0                 *
11bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook *                                                              *
12bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Unless required by applicable law or agreed to in writing,   *
13bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * software distributed under the License is distributed on an  *
14bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * KIND, either express or implied.  See the License for the    *
16bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * specific language governing permissions and limitations      *
17bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * under the License.                                           *
18bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ****************************************************************/
19bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
20bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookpackage org.apache.james.mime4j.decoder;
21bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
22bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook//BEGIN android-changed: Stubbing out logging
23bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport org.apache.james.mime4j.Log;
24bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport org.apache.james.mime4j.LogFactory;
25bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook//END android-changed
26bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport org.apache.james.mime4j.util.CharsetUtil;
27bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
28bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport java.io.ByteArrayInputStream;
29bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport java.io.ByteArrayOutputStream;
30bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport java.io.IOException;
31bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport java.io.UnsupportedEncodingException;
32bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
33bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook/**
34bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Static methods for decoding strings, byte arrays and encoded words.
35bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook *
36bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook *
37bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * @version $Id: DecoderUtil.java,v 1.3 2005/02/07 15:33:59 ntherning Exp $
38bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */
39bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookpublic class DecoderUtil {
40bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static Log log = LogFactory.getLog(DecoderUtil.class);
41bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
42bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
43bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Decodes a string containing quoted-printable encoded data.
44bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *
45bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param s the string to decode.
46bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @return the decoded bytes.
47bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
48bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public static byte[] decodeBaseQuotedPrintable(String s) {
49bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        ByteArrayOutputStream baos = new ByteArrayOutputStream();
50bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
51bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        try {
52bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            byte[] bytes = s.getBytes("US-ASCII");
53bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
54bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            QuotedPrintableInputStream is = new QuotedPrintableInputStream(
55bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                                               new ByteArrayInputStream(bytes));
56bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
57bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            int b = 0;
58bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            while ((b = is.read()) != -1) {
59bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                baos.write(b);
60bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
61bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } catch (IOException e) {
62bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            /*
63bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook             * This should never happen!
64bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook             */
65bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            log.error(e);
66bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
67bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
68bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return baos.toByteArray();
69bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
70bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
71bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
72bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Decodes a string containing base64 encoded data.
73bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *
74bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param s the string to decode.
75bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @return the decoded bytes.
76bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
77bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public static byte[] decodeBase64(String s) {
78bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        ByteArrayOutputStream baos = new ByteArrayOutputStream();
79bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
80bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        try {
81bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            byte[] bytes = s.getBytes("US-ASCII");
82bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
83bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            Base64InputStream is = new Base64InputStream(
84bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                                        new ByteArrayInputStream(bytes));
85bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
86bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            int b = 0;
87bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            while ((b = is.read()) != -1) {
88bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                baos.write(b);
89bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
90bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } catch (IOException e) {
91bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            /*
92bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook             * This should never happen!
93bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook             */
94bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            log.error(e);
95bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
96bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
97bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return baos.toByteArray();
98bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
99bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
100bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
101bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Decodes an encoded word encoded with the 'B' encoding (described in
102bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * RFC 2047) found in a header field body.
103bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *
104bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param encodedWord the encoded word to decode.
105bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param charset the Java charset to use.
106bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @return the decoded string.
107bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @throws UnsupportedEncodingException if the given Java charset isn't
108bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *         supported.
109bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
110bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public static String decodeB(String encodedWord, String charset)
111bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            throws UnsupportedEncodingException {
112bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
113bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return new String(decodeBase64(encodedWord), charset);
114bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
115bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
116bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
117bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Decodes an encoded word encoded with the 'Q' encoding (described in
118bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * RFC 2047) found in a header field body.
119bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *
120bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param encodedWord the encoded word to decode.
121bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param charset the Java charset to use.
122bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @return the decoded string.
123bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @throws UnsupportedEncodingException if the given Java charset isn't
124bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *         supported.
125bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
126bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public static String decodeQ(String encodedWord, String charset)
127bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            throws UnsupportedEncodingException {
128bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
129bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        /*
130bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook         * Replace _ with =20
131bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook         */
132bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        StringBuffer sb = new StringBuffer();
133bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        for (int i = 0; i < encodedWord.length(); i++) {
134bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            char c = encodedWord.charAt(i);
135bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (c == '_') {
136bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                sb.append("=20");
137bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } else {
138bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                sb.append(c);
139bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
140bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
141bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
142bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return new String(decodeBaseQuotedPrintable(sb.toString()), charset);
143bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
144bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
145bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
146bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Decodes a string containing encoded words as defined by RFC 2047.
147bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Encoded words in have the form
148bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * =?charset?enc?Encoded word?= where enc is either 'Q' or 'q' for
149bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * quoted-printable and 'B' or 'b' for Base64.
150bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *
151bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * ANDROID:  COPIED FROM A NEWER VERSION OF MIME4J
152bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     *
153bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param body the string to decode.
154bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @return the decoded string.
155bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
156bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public static String decodeEncodedWords(String body) {
157bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
158bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        // ANDROID:  Most strings will not include "=?" so a quick test can prevent unneeded
159bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        // object creation.  This could also be handled via lazy creation of the StringBuilder.
160bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (body.indexOf("=?") == -1) {
161bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return body;
162bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
163bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
164bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        int previousEnd = 0;
165bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        boolean previousWasEncoded = false;
166bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
167bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        StringBuilder sb = new StringBuilder();
168bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
169bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        while (true) {
170bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            int begin = body.indexOf("=?", previousEnd);
171bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
172bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // ANDROID:  The mime4j original version has an error here.  It gets confused if
173bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // the encoded string begins with an '=' (just after "?Q?").  This patch seeks forward
174bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // to find the two '?' in the "header", before looking for the final "?=".
175bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            int endScan = begin + 2;
176bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (begin != -1) {
177bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                int qm1 = body.indexOf('?', endScan + 2);
178bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                int qm2 = body.indexOf('?', qm1 + 1);
179bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (qm2 != -1) {
180bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    endScan = qm2 + 1;
181bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
182bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
183bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
184bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            int end = begin == -1 ? -1 : body.indexOf("?=", endScan);
185bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (end == -1) {
186bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (previousEnd == 0)
187bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    return body;
188bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
189bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                sb.append(body.substring(previousEnd));
190bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                return sb.toString();
191bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
192bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            end += 2;
193bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
194bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String sep = body.substring(previousEnd, begin);
195bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
196bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            String decoded = decodeEncodedWord(body, begin, end);
197bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (decoded == null) {
198bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                sb.append(sep);
199bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                sb.append(body.substring(begin, end));
200bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } else {
201bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (!previousWasEncoded || !CharsetUtil.isWhitespace(sep)) {
202bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    sb.append(sep);
203bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
204bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                sb.append(decoded);
205bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
206bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
207bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            previousEnd = end;
208bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            previousWasEncoded = decoded != null;
209bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
210bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
211bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
212bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    // return null on error
213bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private static String decodeEncodedWord(String body, int begin, int end) {
214bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        int qm1 = body.indexOf('?', begin + 2);
215bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (qm1 == end - 2)
216bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
217bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
218bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        int qm2 = body.indexOf('?', qm1 + 1);
219bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (qm2 == end - 2)
220bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
221bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
222bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String mimeCharset = body.substring(begin + 2, qm1);
223bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String encoding = body.substring(qm1 + 1, qm2);
224bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String encodedText = body.substring(qm2 + 1, end - 2);
225bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
226bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        String charset = CharsetUtil.toJavaCharset(mimeCharset);
227bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (charset == null) {
228bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (log.isWarnEnabled()) {
229bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                log.warn("MIME charset '" + mimeCharset + "' in encoded word '"
230bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + body.substring(begin, end) + "' doesn't have a "
231bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + "corresponding Java charset");
232bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
233bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
234bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } else if (!CharsetUtil.isDecodingSupported(charset)) {
235bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (log.isWarnEnabled()) {
236bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                log.warn("Current JDK doesn't support decoding of charset '"
237bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + charset + "' (MIME charset '" + mimeCharset
238bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + "' in encoded word '" + body.substring(begin, end)
239bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + "')");
240bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
241bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
242bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
243bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
244bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (encodedText.length() == 0) {
245bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (log.isWarnEnabled()) {
246bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                log.warn("Missing encoded text in encoded word: '"
247bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + body.substring(begin, end) + "'");
248bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
249bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
250bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
251bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
252bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        try {
253bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (encoding.equalsIgnoreCase("Q")) {
254bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                return DecoderUtil.decodeQ(encodedText, charset);
255bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } else if (encoding.equalsIgnoreCase("B")) {
256bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                return DecoderUtil.decodeB(encodedText, charset);
257bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            } else {
258bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                if (log.isWarnEnabled()) {
259bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                    log.warn("Warning: Unknown encoding in encoded word '"
260bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                            + body.substring(begin, end) + "'");
261bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                }
262bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                return null;
263bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
264bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } catch (UnsupportedEncodingException e) {
265bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            // should not happen because of isDecodingSupported check above
266bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (log.isWarnEnabled()) {
267bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                log.warn("Unsupported encoding in encoded word '"
268bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + body.substring(begin, end) + "'", e);
269bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
270bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
271bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        } catch (RuntimeException e) {
272bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (log.isWarnEnabled()) {
273bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                log.warn("Could not decode encoded word '"
274bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                        + body.substring(begin, end) + "'", e);
275bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            }
276bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            return null;
277bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
278bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
279bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook}
280