<commit_before>/*
 * Copyright (c) 2014 <NAME> / Icegreen Technologies. All Rights Reserved.
 * This software is released under the Apache license 2.0
 * This file has been modified by the copyright holder.
 * Original file can be found at http://james.apache.org
 */
package com.icegreen.greenmail.imap.commands;

import com.icegreen.greenmail.imap.ImapConstants;
import com.icegreen.greenmail.imap.ImapRequestLineReader;
import com.icegreen.greenmail.imap.ProtocolException;
import com.icegreen.greenmail.store.MessageFlags;

import javax.mail.Flags;
import javax.mail.internet.MailDateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;

/**
 * @author <NAME> <<EMAIL>>
 * @version $Revision: 109034 $
 */
public class CommandParser {
    /**
     * 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.
     * <p/>
     * 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.
     */
    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.");
        }

        MailDateFormat rfc2822Format = new MailDateFormat();
        try {
            return rfc2822Format.parse(dateString);
        } catch (ParseException e) {
            throw new ProtocolException("Invalid date format <"+dateString+">, should be rfc2822 compliant.");
        }
    }

    /**
     * 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 {
        StringBuilder atom = new StringBuilder();

        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();
    }
<commit_msg>Fixes #53 Imap does not correctly handle APPEND date format. Previous impl already correctly handled the date format.<commit_after>/*
 * Copyright (c) 2014 <NAME> / Icegreen Technologies. All Rights Reserved.
 * This software is released under the Apache license 2.0
 * This file has been modified by the copyright holder.
 * Original file can be found at http://james.apache.org
 */
package com.icegreen.greenmail.imap.commands;

import com.icegreen.greenmail.imap.ImapConstants;
import com.icegreen.greenmail.imap.ImapRequestLineReader;
import com.icegreen.greenmail.imap.ProtocolException;
import com.icegreen.greenmail.store.MessageFlags;

import javax.mail.Flags;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

/**
 * @author <NAME> <<EMAIL>>
 * @version $Revision: 109034 $
 */
public class CommandParser {
    /**
     * 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.
     * <p/>
     * 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.
     */
    public Date dateTime(ImapRequestLineReader request) throws ProtocolException {
        char next = request.nextWordChar();
        String dateString;
        // From https://tools.ietf.org/html/rfc3501 :
        // date-time       = DQUOTE date-day-fixed "-" date-month "-" date-year
        //                   SP time SP zone DQUOTE
        // zone            = ("+" / "-") 4DIGIT
        if (next == '"') {
            dateString = consumeQuoted(request);
        } else {
            throw new ProtocolException("DateTime values must be quoted.");
        }

        try {
            // You can use Z or zzzz
            return new SimpleDateFormat("dd-MMM-yyyy hh:mm:ss Z", Locale.US).parse(dateString);
        } catch (ParseException e) {
            throw new ProtocolException("Invalid date format <" + dateString + ">, should comply to dd-MMM-yyyy hh:mm:ss Z");
        }
    }

    /**
     * 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 {
        StringBuilder atom = new StringBuilder();

        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();
    }

{'fraction_non_alphanumeric': 0.09801539084649656, 'fraction_numerical': 0.0049615228837586066, 'mean_word_length': 2.1505582137161086, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7664121', 'n_tokens_mistral': 2638, 'n_tokens_neox': 2539, 'n_words': 773}