All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.mail.internet.HeaderTokenizer Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package javax.mail.internet;

/**
 * @version $Rev: 729233 $ $Date: 2008-12-24 00:08:45 -0500 (Wed, 24 Dec 2008) $
 */
public class HeaderTokenizer {
    public static class Token {
        // Constant values from J2SE 1.4 API Docs (Constant values)
        public static final int ATOM = -1;
        public static final int COMMENT = -3;
        public static final int EOF = -4;
        public static final int QUOTEDSTRING = -2;
        private int _type;
        private String _value;

        public Token(int type, String value) {
            _type = type;
            _value = value;
        }

        public int getType() {
            return _type;
        }

        public String getValue() {
            return _value;
        }
    }

    private static final Token EOF = new Token(Token.EOF, null);
    // characters not allowed in MIME
    public static final String MIME = "()<>@,;:\\\"\t []/?=";
    // charaters not allowed in RFC822
    public static final String RFC822 = "()<>@,;:\\\"\t .[]";
    private static final String WHITE = " \t\n\r";
    private String _delimiters;
    private String _header;
    private boolean _skip;
    private int pos;

    public HeaderTokenizer(String header) {
        this(header, RFC822);
    }

    public HeaderTokenizer(String header, String delimiters) {
        this(header, delimiters, true);
    }

    public HeaderTokenizer(String header,
                           String delimiters,
                           boolean skipComments) {
        _skip = skipComments;
        _header = header;
        _delimiters = delimiters;
    }

    public String getRemainder() {
        return _header.substring(pos);
    }

    public Token next() throws ParseException {
        return readToken();
    }

    public Token peek() throws ParseException {
        int start = pos;
        try {
            return readToken();
        } finally {
            pos = start;
        }
    }

    /**
     * Read an ATOM token from the parsed header.
     *
     * @return A token containing the value of the atom token.
     */
    private Token readAtomicToken() {
        // skip to next delimiter
        int start = pos;
        while (++pos < _header.length()) {
            // break on the first non-atom character.
            char ch = _header.charAt(pos);
            if (_delimiters.indexOf(_header.charAt(pos)) != -1 || ch < 32 || ch >= 127) {
                break;
            }
        }

        return new Token(Token.ATOM, _header.substring(start, pos));
    }

    /**
     * Read the next token from the header.
     *
     * @return The next token from the header.  White space is skipped, and comment
     *         tokens are also skipped if indicated.
     * @exception ParseException
     */
    private Token readToken() throws ParseException {
        if (pos >= _header.length()) {
            return EOF;
        } else {
            char c = _header.charAt(pos);
            // comment token...read and skip over this
            if (c == '(') {
                Token comment = readComment();
                if (_skip) {
                    return readToken();
                } else {
                    return comment;
                }
                // quoted literal
            } else if (c == '\"') {
                return readQuotedString();
            // white space, eat this and find a real token.
            } else if (WHITE.indexOf(c) != -1) {
                eatWhiteSpace();
                return readToken();
            // either a CTL or special.  These characters have a self-defining token type.
            } else if (c < 32 || c >= 127 || _delimiters.indexOf(c) != -1) {
                pos++;
                return new Token((int)c, String.valueOf(c));
            } else {
                // start of an atom, parse it off.
                return readAtomicToken();
            }
        }
    }

    /**
     * Extract a substring from the header string and apply any
     * escaping/folding rules to the string.
     *
     * @param start  The starting offset in the header.
     * @param end    The header end offset + 1.
     *
     * @return The processed string value.
     * @exception ParseException
     */
    private String getEscapedValue(int start, int end) throws ParseException {
        StringBuffer value = new StringBuffer();

        for (int i = start; i < end; i++) {
            char ch = _header.charAt(i);
            // is this an escape character?
            if (ch == '\\') {
                i++;
                if (i == end) {
                    throw new ParseException("Invalid escape character");
                }
                value.append(_header.charAt(i));
            }
            // line breaks are ignored, except for naked '\n' characters, which are consider
            // parts of linear whitespace.
            else if (ch == '\r') {
                // see if this is a CRLF sequence, and skip the second if it is.
                if (i < end - 1 && _header.charAt(i + 1) == '\n') {
                    i++;
                }
            }
            else {
                // just append the ch value.
                value.append(ch);
            }
        }
        return value.toString();
    }

    /**
     * Read a comment from the header, applying nesting and escape
     * rules to the content.
     *
     * @return A comment token with the token value.
     * @exception ParseException
     */
    private Token readComment() throws ParseException {
        int start = pos + 1;
        int nesting = 1;

        boolean requiresEscaping = false;

        // skip to end of comment/string
        while (++pos < _header.length()) {
            char ch = _header.charAt(pos);
            if (ch == ')') {
                nesting--;
                if (nesting == 0) {
                    break;
                }
            }
            else if (ch == '(') {
                nesting++;
            }
            else if (ch == '\\') {
                pos++;
                requiresEscaping = true;
            }
            // we need to process line breaks also
            else if (ch == '\r') {
                requiresEscaping = true;
            }
        }

        if (nesting != 0) {
            throw new ParseException("Unbalanced comments");
        }

        String value;
        if (requiresEscaping) {
            value = getEscapedValue(start, pos);
        }
        else {
            value = _header.substring(start, pos++);
        }
        return new Token(Token.COMMENT, value);
    }

    /**
     * Parse out a quoted string from the header, applying escaping
     * rules to the value.
     *
     * @return The QUOTEDSTRING token with the value.
     * @exception ParseException
     */
    private Token readQuotedString() throws ParseException {
        int start = pos+1;
        boolean requiresEscaping = false;

        // skip to end of comment/string
        while (++pos < _header.length()) {
            char ch = _header.charAt(pos);
            if (ch == '"') {
                String value;
                if (requiresEscaping) {
                    value = getEscapedValue(start, pos++);
                }
                else {
                    value = _header.substring(start, pos++);
                }
                return new Token(Token.QUOTEDSTRING, value);
            }
            else if (ch == '\\') {
                pos++;
                requiresEscaping = true;
            }
            // we need to process line breaks also
            else if (ch == '\r') {
                requiresEscaping = true;
            }
        }

        throw new ParseException("Missing '\"'");
    }

    /**
     * Skip white space in the token string.
     */
    private void eatWhiteSpace() {
        // skip to end of whitespace
        while (++pos < _header.length()
                && WHITE.indexOf(_header.charAt(pos)) != -1)
            ;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy