Package org.apache.james.imapserver.commands

Source Code of org.apache.james.imapserver.commands.CommandParser$CompoundIdSet

/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by the
*        Apache Software Foundation (http://www.apache.org/)."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache", "Jakarta", "JAMES" and "Apache Software Foundation"
*    must not be used to endorse or promote products derived from this
*    software without prior written permission. For written
*    permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
*    nor may "Apache" appear in their name, without prior written
*    permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation.  For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/

package org.apache.james.imapserver.commands;

import org.apache.james.imapserver.ProtocolException;
import org.apache.james.imapserver.ImapRequestLineReader;
import org.apache.james.imapserver.ImapConstants;
import org.apache.james.imapserver.store.MessageFlags;
import org.apache.james.util.Assert;

import java.util.Date;
import java.util.TimeZone;
import java.util.List;
import java.util.ArrayList;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;

/**
*
* @author  Darrell DeBoer <darrell@apache.org>
*
* @version $Revision: 1.5.2.2 $
*/
public class CommandParser
{
    private static final char[] EMPTY_CHAR_ARRAY = new char[0];

    /**
     * Reads an argument of type "atom" from the request.
     */
    public String atom( ImapRequestLineReader request ) throws ProtocolException
    {
        return consumeWord( request, new ATOM_CHARValidator() );
    }

    /**
     * Reads a command "tag" from the request.
     */
    public String tag(ImapRequestLineReader request) throws ProtocolException
    {
        CharacterValidator validator = new TagCharValidator();
        return consumeWord( request, validator );
    }

    /**
     * Reads an argument of type "astring" from the request.
     */
    public String astring(ImapRequestLineReader request) throws ProtocolException
    {
        char next = request.nextWordChar();
        switch ( next ) {
            case '"':
                return consumeQuoted( request );
            case '{':
                return consumeLiteral( request );
            default:
                return atom( request );
        }
    }

    /**
     * Reads an argument of type "nstring" from the request.
     */
    public String nstring( ImapRequestLineReader request ) throws ProtocolException
    {
        char next = request.nextWordChar();
        switch ( next ) {
            case '"':
                return consumeQuoted( request );
            case '{':
                return consumeLiteral( request );
            default:
                String value = atom( request );
                if ( "NIL".equals( value ) ) {
                    return null;
                }
                else {
                    throw new ProtocolException( "Invalid nstring value: valid values are '\"...\"', '{12} CRLF *CHAR8', and 'NIL'." );
                }
        }
    }

    /**
     * Reads a "mailbox" argument from the request. Not implemented *exactly* as per spec,
     * since a quoted or literal "inbox" still yeilds "INBOX"
     * (ie still case-insensitive if quoted or literal). I think this makes sense.
     *
     * mailbox         ::= "INBOX" / astring
     *              ;; INBOX is case-insensitive.  All case variants of
     *              ;; INBOX (e.g. "iNbOx") MUST be interpreted as INBOX
     *              ;; not as an astring.
     */
    public String mailbox( ImapRequestLineReader request ) throws ProtocolException
    {
        String mailbox = astring( request );
        if ( mailbox.equalsIgnoreCase( ImapConstants.INBOX_NAME ) ) {
            return ImapConstants.INBOX_NAME;
        }
        else {
            return mailbox;
        }
    }

    /**
     * Reads a "date-time" argument from the request.
     * TODO handle timezones properly
     */
    public Date dateTime( ImapRequestLineReader request ) throws ProtocolException
    {
        char next = request.nextWordChar();
        String dateString;
        if ( next == '"' ) {
            dateString = consumeQuoted( request );
        }
        else {
            throw new ProtocolException( "DateTime values must be quoted." );
        }

        DateFormat dateFormat = new SimpleDateFormat( "dd-MMM-yyyy hh:mm:ss zzzz" );
        try {
            return dateFormat.parse( dateString );
        }
        catch ( ParseException e ) {
            throw new ProtocolException( "Invalid date format." );
        }
    }

    /**
     * Reads a "date" argument from the request.
     * TODO handle timezones properly
     */
    public Date date( ImapRequestLineReader request ) throws ProtocolException
    {
        char next = request.nextWordChar();
        String dateString;
        if ( next == '"' ) {
            dateString = consumeQuoted( request );
        }
        else {
            dateString = atom( request );
        }

        DateFormat dateFormat = new SimpleDateFormat( "dd-MMM-yyyy" );
        try {
            return dateFormat.parse( dateString );
        }
        catch ( ParseException e ) {
            throw new ProtocolException( "Invalid date format." );
        }
    }

    /**
     * Reads the next "word from the request, comprising all characters up to the next SPACE.
     * Characters are tested by the supplied CharacterValidator, and an exception is thrown
     * if invalid characters are encountered.
     */
    protected String consumeWord( ImapRequestLineReader request,
                                  CharacterValidator validator )
            throws ProtocolException
    {
        StringBuffer atom = new StringBuffer();

        char next = request.nextWordChar();
        while( ! isWhitespace( next ) ) {
            if ( validator.isValid( next ) )
            {
                atom.append( next );
                request.consume();
            }
            else {
                throw new ProtocolException( "Invalid character: '" + next + "'" );
            }
            next = request.nextChar();
        }
        return atom.toString();
    }

    private boolean isWhitespace( char next )
    {
        return ( next == ' ' || next == '\n' || next == '\r' || next == '\t' );
    }

    /**
     * Reads an argument of type "literal" from the request, in the format:
     *      "{" charCount "}" CRLF *CHAR8
     * Note before calling, the request should be positioned so that nextChar
     * is '{'. Leading whitespace is not skipped in this method.
     */
    protected String consumeLiteral( ImapRequestLineReader request )
            throws ProtocolException
    {
        // The 1st character must be '{'
        consumeChar( request, '{' );

        StringBuffer digits = new StringBuffer();
        char next = request.nextChar();
        while ( next != '}' && next != '+' )
        {
            digits.append( next );
            request.consume();
            next = request.nextChar();
        }

        // If the number is *not* suffixed with a '+', we *are* using a synchronized literal,
        // and we need to send command continuation request before reading data.
        boolean synchronizedLiteral = true;
        // '+' indicates a non-synchronized literal (no command continuation request)
        if ( next == '+' ) {
            synchronizedLiteral = false;
            consumeChar(request, '+' );
        }

        // Consume the '}' and the newline
        consumeChar( request, '}' );
        consumeCRLF( request );

        if ( synchronizedLiteral ) {
            request.commandContinuationRequest();
        }

        int size = Integer.parseInt( digits.toString() );
        byte[] buffer = new byte[size];
        request.read( buffer );

        return new String( buffer );
    }

    /**
     * Consumes a CRLF from the request.
     * TODO we're being liberal, the spec insists on \r\n for new lines.
     * @param request
     * @throws ProtocolException
     */
    private void consumeCRLF( ImapRequestLineReader request )
            throws ProtocolException
    {
        char next = request.nextChar();
        if ( next != '\n' ) {
            consumeChar( request, '\r' );
        }
        consumeChar( request, '\n' );
    }

    /**
     * Consumes the next character in the request, checking that it matches the
     * expected one. This method should be used when the
     */
    protected void consumeChar( ImapRequestLineReader request, char expected )
            throws ProtocolException
    {
        char consumed = request.consume();
        if ( consumed != expected ) {
            throw new ProtocolException( "Expected:'" + expected + "' found:'" + consumed + "'" );
        }
    }

    /**
     * Reads a quoted string value from the request.
     */
    protected String consumeQuoted( ImapRequestLineReader request )
            throws ProtocolException
    {
        // The 1st character must be '"'
        consumeChar(request, '"' );

        StringBuffer quoted = new StringBuffer();
        char next = request.nextChar();
        while( next != '"' ) {
            if ( next == '\\' ) {
                request.consume();
                next = request.nextChar();
                if ( ! isQuotedSpecial( next ) ) {
                    throw new ProtocolException( "Invalid escaped character in quote: '" +
                                                 next + "'" );
                }
            }
            quoted.append( next );
            request.consume();
            next = request.nextChar();
        }

        consumeChar( request, '"' );

        return quoted.toString();
    }

    /**
     * Reads a base64 argument from the request.
     */
    public byte[] base64( ImapRequestLineReader request ) throws ProtocolException
    {
        return null;
    }

    /**
     * Reads a "flags" argument from the request.
     */
    public MessageFlags flagList( ImapRequestLineReader request ) throws ProtocolException
    {
        MessageFlags flags = new MessageFlags();
        request.nextWordChar();
        consumeChar( request, '(' );
        CharacterValidator validator = new NoopCharValidator();
        String nextWord = consumeWord( request, validator );
        while ( ! nextWord.endsWith(")" ) ) {
            setFlag( nextWord, flags );
            nextWord = consumeWord( request, validator );
        }
        // Got the closing ")", may be attached to a word.
        if ( nextWord.length() > 1 ) {
            setFlag( nextWord.substring(0, nextWord.length() - 1 ), flags );
        }

        return flags;
        }

    public void setFlag( String flagString, MessageFlags flags ) throws ProtocolException
    {
        if ( flagString.equalsIgnoreCase( MessageFlags.ANSWERED ) ) {
            flags.setAnswered( true );
        }
        else if ( flagString.equalsIgnoreCase( MessageFlags.DELETED ) ) {
            flags.setDeleted( true );
        }
        else if ( flagString.equalsIgnoreCase( MessageFlags.DRAFT ) ) {
            flags.setDraft( true );
        }
        else if ( flagString.equalsIgnoreCase( MessageFlags.FLAGGED ) ) {
            flags.setFlagged( true );
        }
        else if ( flagString.equalsIgnoreCase( MessageFlags.SEEN ) ) {
            flags.setSeen( true );
        }
        else {
            throw new ProtocolException( "Invalid flag string." );
        }
    }

     /**
     * Reads an argument of type "number" from the request.
     */
    public long number( ImapRequestLineReader request ) throws ProtocolException
    {
        String digits = consumeWord( request, new DigitCharValidator() );
        return Long.parseLong( digits );
    }

    /**
     * Reads an argument of type "nznumber" (a non-zero number)
     * (NOTE this isn't strictly as per the spec, since the spec disallows
     * numbers such as "0123" as nzNumbers (although it's ok as a "number".
     * I think the spec is a bit shonky.)
     */
    public long nzNumber( ImapRequestLineReader request ) throws ProtocolException
    {
        long number = number( request );
        if ( number == 0 ) {
            throw new ProtocolException( "Zero value not permitted." );
        }
        return number;
    }

    private boolean isCHAR( char chr )
    {
        return ( chr >= 0x01 && chr <= 0x7f );
    }

    private boolean isCHAR8( char chr )
    {
        return ( chr >= 0x01 && chr <= 0xff );
    }

    protected boolean isListWildcard( char chr )
    {
        return ( chr == '*' || chr == '%' );
    }

    private boolean isQuotedSpecial( char chr )
    {
        return ( chr == '"' || chr == '\\' );
    }

    /**
     * Consumes the request up to and including the eno-of-line.
     * @param request The request
     * @throws ProtocolException If characters are encountered before the endLine.
     */
    public void endLine( ImapRequestLineReader request ) throws ProtocolException
    {
        request.eol();
    }

    /**
     * Reads a "message set" argument, and parses into an IdSet.
     * Currently only supports a single range of values.
     */
    public IdSet set( ImapRequestLineReader request )
            throws ProtocolException
    {
        CharacterValidator validator = new MessageSetCharValidator();
        String nextWord = consumeWord( request, validator );

        int commaPos = nextWord.indexOf( ',' );
        if ( commaPos == -1 ) {
            return singleRangeSet( nextWord );
        }

        CompoundIdSet compoundSet = new CompoundIdSet();
        int pos = 0;
        while ( commaPos != -1 ) {
            String range = nextWord.substring( pos, commaPos );
            IdSet set = singleRangeSet( range );
            compoundSet.addIdSet( set );

            pos = commaPos + 1;
            commaPos = nextWord.indexOf( ',', pos );
        }
        String range = nextWord.substring( pos );
        compoundSet.addIdSet( singleRangeSet( range ) );
        return compoundSet;
    }

    private IdSet singleRangeSet( String range ) throws ProtocolException
    {
        int pos = range.indexOf( ':' );
        try {
            if ( pos == -1 ) {
                long value = parseLong( range );
                return new HighLowIdSet( value, value );
            }
            else {
                long lowVal = parseLong( range.substring(0, pos ) );
                long highVal = parseLong( range.substring( pos + 1 ) );
                return new HighLowIdSet( lowVal, highVal );
            }
        }
        catch ( NumberFormatException e ) {
            throw new ProtocolException( "Invalid message set.");
        }
    }

    private long parseLong( String value ) {
        if ( value.length() == 1 && value.charAt(0) == '*' ) {
            return Long.MAX_VALUE;
        }
        return Long.parseLong( value );
    }
    /**
     * Provides the ability to ensure characters are part of a permitted set.
     */
    protected interface CharacterValidator
    {
        /**
         * Validates the supplied character.
         * @param chr The character to validate.
         * @return <code>true</code> if chr is valid, <code>false</code> if not.
         */
        boolean isValid( char chr );
    }

    protected class NoopCharValidator implements CharacterValidator
    {
        public boolean isValid( char chr )
        {
            return true;
        }
    }

    protected class ATOM_CHARValidator implements CharacterValidator
    {
        public boolean isValid( char chr )
        {
            return ( isCHAR( chr ) && !isAtomSpecial( chr ) &&
                     !isListWildcard( chr ) && !isQuotedSpecial( chr ) );
        }

        private boolean isAtomSpecial( char chr )
        {
            return ( chr == '(' ||
                    chr == ')' ||
                    chr == '{' ||
                    chr == ' ' ||
                    chr == Character.CONTROL );
        }
    }

    protected class DigitCharValidator implements CharacterValidator
    {
        public boolean isValid( char chr )
        {
            return ( ( chr >= '0' && chr <= '9' ) ||
                     chr == '*' );
        }
    }

    private class TagCharValidator extends ATOM_CHARValidator
    {
        public boolean isValid( char chr )
        {
            if ( chr == '+' ) return false;
            return super.isValid( chr );
        }
    }

    private class MessageSetCharValidator implements CharacterValidator
    {
        public boolean isValid( char chr )
        {
            return ( isDigit( chr ) ||
                    chr == ':' ||
                    chr == '*' ||
                    chr == ',' );
        }

        private boolean isDigit( char chr )
        {
            return '0' <= chr && chr <= '9';
        }
    }

    private class HighLowIdSet implements IdSet
    {
        private long lowVal;
        private long highVal;

        public HighLowIdSet( long lowVal, long highVal )
        {
            this.lowVal = lowVal;
            this.highVal = highVal;
        }

        public boolean includes( long value ) {
            return ( lowVal <= value ) && ( value <= highVal );
        }
    }

    private class CompoundIdSet implements IdSet
    {
        private List idSets = new ArrayList();

        void addIdSet( IdSet set ) {
            idSets.add( set );
        }

        public boolean includes( long value )
        {
            for ( int i = 0; i < idSets.size(); i++ ) {
                IdSet idSet = ( IdSet ) idSets.get( i );
                if ( idSet.includes( value ) ) {
                    return true;
                }
            }
            return false;
        }
    }


}
TOP

Related Classes of org.apache.james.imapserver.commands.CommandParser$CompoundIdSet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.