14fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy/****************************************************************
24fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * Licensed to the Apache Software Foundation (ASF) under one   *
34fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * or more contributor license agreements.  See the NOTICE file *
44fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * distributed with this work for additional information        *
54fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * regarding copyright ownership.  The ASF licenses this file   *
64fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * to you under the Apache License, Version 2.0 (the            *
74fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * "License"); you may not use this file except in compliance   *
84fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * with the License.  You may obtain a copy of the License at   *
94fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy *                                                              *
104fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy *   http://www.apache.org/licenses/LICENSE-2.0                 *
114fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy *                                                              *
124fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * Unless required by applicable law or agreed to in writing,   *
134fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * software distributed under the License is distributed on an  *
144fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
154fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * KIND, either express or implied.  See the License for the    *
164fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * specific language governing permissions and limitations      *
174fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * under the License.                                           *
184fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy ****************************************************************/
194fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
204fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedypackage org.apache.james.mime4j.decoder;
214fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
224fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy//BEGIN android-changed: Stubbing out logging
234fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport org.apache.james.mime4j.Log;
244fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport org.apache.james.mime4j.LogFactory;
254fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy//END android-changed
264fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport org.apache.james.mime4j.util.CharsetUtil;
274fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
284fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport java.io.ByteArrayInputStream;
294fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport java.io.ByteArrayOutputStream;
304fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport java.io.IOException;
314fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedyimport java.io.UnsupportedEncodingException;
324fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
334fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy/**
344fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * Static methods for decoding strings, byte arrays and encoded words.
354fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy *
364fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy *
374fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy * @version $Id: DecoderUtil.java,v 1.3 2005/02/07 15:33:59 ntherning Exp $
384fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy */
394fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedypublic class DecoderUtil {
404fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    private static Log log = LogFactory.getLog(DecoderUtil.class);
414fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
424fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    /**
434fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * Decodes a string containing quoted-printable encoded data.
444fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *
454fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param s the string to decode.
464fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @return the decoded bytes.
474fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     */
484fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    public static byte[] decodeBaseQuotedPrintable(String s) {
494fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        ByteArrayOutputStream baos = new ByteArrayOutputStream();
504fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
514fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        try {
524fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            byte[] bytes = s.getBytes("US-ASCII");
534fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
544fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            QuotedPrintableInputStream is = new QuotedPrintableInputStream(
554fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                                               new ByteArrayInputStream(bytes));
564fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
574fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            int b = 0;
584fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            while ((b = is.read()) != -1) {
594fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                baos.write(b);
604fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
614fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        } catch (IOException e) {
624fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            /*
634fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy             * This should never happen!
644fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy             */
654fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            log.error(e);
664fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
674fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
684fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        return baos.toByteArray();
694fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    }
704fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
714fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    /**
724fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * Decodes a string containing base64 encoded data.
734fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *
744fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param s the string to decode.
754fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @return the decoded bytes.
764fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     */
774fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    public static byte[] decodeBase64(String s) {
784fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        ByteArrayOutputStream baos = new ByteArrayOutputStream();
794fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
804fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        try {
814fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            byte[] bytes = s.getBytes("US-ASCII");
824fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
834fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            Base64InputStream is = new Base64InputStream(
844fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                                        new ByteArrayInputStream(bytes));
854fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
864fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            int b = 0;
874fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            while ((b = is.read()) != -1) {
884fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                baos.write(b);
894fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
904fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        } catch (IOException e) {
914fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            /*
924fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy             * This should never happen!
934fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy             */
944fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            log.error(e);
954fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
964fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
974fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        return baos.toByteArray();
984fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    }
994fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1004fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    /**
1014fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * Decodes an encoded word encoded with the 'B' encoding (described in
1024fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * RFC 2047) found in a header field body.
1034fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *
1044fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param encodedWord the encoded word to decode.
1054fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param charset the Java charset to use.
1064fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @return the decoded string.
1074fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @throws UnsupportedEncodingException if the given Java charset isn't
1084fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *         supported.
1094fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     */
1104fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    public static String decodeB(String encodedWord, String charset)
1114fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            throws UnsupportedEncodingException {
1124fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1134fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        return new String(decodeBase64(encodedWord), charset);
1144fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    }
1154fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1164fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    /**
1174fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * Decodes an encoded word encoded with the 'Q' encoding (described in
1184fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * RFC 2047) found in a header field body.
1194fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *
1204fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param encodedWord the encoded word to decode.
1214fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param charset the Java charset to use.
1224fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @return the decoded string.
1234fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @throws UnsupportedEncodingException if the given Java charset isn't
1244fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *         supported.
1254fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     */
1264fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    public static String decodeQ(String encodedWord, String charset)
1274fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            throws UnsupportedEncodingException {
1284fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1294fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        /*
1304fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy         * Replace _ with =20
1314fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy         */
1324fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        StringBuffer sb = new StringBuffer();
1334fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        for (int i = 0; i < encodedWord.length(); i++) {
1344fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            char c = encodedWord.charAt(i);
1354fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (c == '_') {
1364fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                sb.append("=20");
1374fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            } else {
1384fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                sb.append(c);
1394fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
1404fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
1414fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1424fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        return new String(decodeBaseQuotedPrintable(sb.toString()), charset);
1434fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    }
1444fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1454fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    /**
1464fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * Decodes a string containing encoded words as defined by RFC 2047.
1474fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * Encoded words in have the form
1484fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * =?charset?enc?Encoded word?= where enc is either 'Q' or 'q' for
1494fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * quoted-printable and 'B' or 'b' for Base64.
1504fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *
1514fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * ANDROID:  COPIED FROM A NEWER VERSION OF MIME4J
1524fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     *
1534fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @param body the string to decode.
1544fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     * @return the decoded string.
1554fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy     */
1564fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    public static String decodeEncodedWords(String body) {
1574fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1584fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        // ANDROID:  Most strings will not include "=?" so a quick test can prevent unneeded
1594fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        // object creation.  This could also be handled via lazy creation of the StringBuilder.
1604fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        if (body.indexOf("=?") == -1) {
1614fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return body;
1624fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
1639c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner
1644fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        int previousEnd = 0;
1654fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        boolean previousWasEncoded = false;
1664fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1674fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        StringBuilder sb = new StringBuilder();
1684fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1694fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        while (true) {
1704fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            int begin = body.indexOf("=?", previousEnd);
1719c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner
1724fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            // ANDROID:  The mime4j original version has an error here.  It gets confused if
1734fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            // the encoded string begins with an '=' (just after "?Q?").  This patch seeks forward
1744fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            // to find the two '?' in the "header", before looking for the final "?=".
1759c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            if (begin == -1) {
1769c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner                break;
1774fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
1789c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            int qm1 = body.indexOf('?', begin + 2);
1799c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            if (qm1 == -1) {
1809c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner                break;
1819c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            }
1829c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            int qm2 = body.indexOf('?', qm1 + 1);
1839c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            if (qm2 == -1) {
1849c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner                break;
1859c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            }
1869c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            int end = body.indexOf("?=", qm2 + 1);
1874fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (end == -1) {
1889c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner                break;
1894fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
1904fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            end += 2;
1914fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1924fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            String sep = body.substring(previousEnd, begin);
1934fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
1944fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            String decoded = decodeEncodedWord(body, begin, end);
1954fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (decoded == null) {
1964fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                sb.append(sep);
1974fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                sb.append(body.substring(begin, end));
1984fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            } else {
1994fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                if (!previousWasEncoded || !CharsetUtil.isWhitespace(sep)) {
2004fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                    sb.append(sep);
2014fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                }
2024fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                sb.append(decoded);
2034fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2044fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2054fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            previousEnd = end;
2064fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            previousWasEncoded = decoded != null;
2074fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
2089c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner
2099c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner        if (previousEnd == 0)
2109c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner            return body;
2119c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner
2129c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner        sb.append(body.substring(previousEnd));
2139c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner        return sb.toString();
2144fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    }
2154fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2169c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner    // return null on error. Begin is index of '=?' in body.
2179c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner    public static String decodeEncodedWord(String body, int begin, int end) {
2189c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner        // Skip the '?=' chars in body and scan forward from there for next '?'
2194fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        int qm1 = body.indexOf('?', begin + 2);
2209c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner        if (qm1 == -1 || qm1 == end - 2)
2214fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2224fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2234fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        int qm2 = body.indexOf('?', qm1 + 1);
2249c4bcbc46a3530c596b6c2bade048f2971a8c69fJay Shrauner        if (qm2 == -1 || qm2 == end - 2)
2254fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2264fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2274fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        String mimeCharset = body.substring(begin + 2, qm1);
2284fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        String encoding = body.substring(qm1 + 1, qm2);
2294fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        String encodedText = body.substring(qm2 + 1, end - 2);
2304fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2314fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        String charset = CharsetUtil.toJavaCharset(mimeCharset);
2324fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        if (charset == null) {
2334fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (log.isWarnEnabled()) {
2344fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                log.warn("MIME charset '" + mimeCharset + "' in encoded word '"
2354fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + body.substring(begin, end) + "' doesn't have a "
2364fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + "corresponding Java charset");
2374fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2384fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2394fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        } else if (!CharsetUtil.isDecodingSupported(charset)) {
2404fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (log.isWarnEnabled()) {
2414fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                log.warn("Current JDK doesn't support decoding of charset '"
2424fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + charset + "' (MIME charset '" + mimeCharset
2434fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + "' in encoded word '" + body.substring(begin, end)
2444fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + "')");
2454fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2464fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2474fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
2484fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2494fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        if (encodedText.length() == 0) {
2504fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (log.isWarnEnabled()) {
2514fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                log.warn("Missing encoded text in encoded word: '"
2524fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + body.substring(begin, end) + "'");
2534fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2544fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2554fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
2564fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy
2574fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        try {
2584fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (encoding.equalsIgnoreCase("Q")) {
2594fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                return DecoderUtil.decodeQ(encodedText, charset);
2604fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            } else if (encoding.equalsIgnoreCase("B")) {
2614fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                return DecoderUtil.decodeB(encodedText, charset);
2624fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            } else {
2634fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                if (log.isWarnEnabled()) {
2644fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                    log.warn("Warning: Unknown encoding in encoded word '"
2654fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                            + body.substring(begin, end) + "'");
2664fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                }
2674fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                return null;
2684fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2694fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        } catch (UnsupportedEncodingException e) {
2704fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            // should not happen because of isDecodingSupported check above
2714fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (log.isWarnEnabled()) {
2724fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                log.warn("Unsupported encoding in encoded word '"
2734fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + body.substring(begin, end) + "'", e);
2744fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2754fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2764fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        } catch (RuntimeException e) {
2774fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            if (log.isWarnEnabled()) {
2784fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                log.warn("Could not decode encoded word '"
2794fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy                        + body.substring(begin, end) + "'", e);
2804fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            }
2814fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy            return null;
2824fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy        }
2834fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy    }
2844fa0a3295bcacbdcd6a9e7709cf17aa5adb90356Scott Kennedy}
285