geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rickmcgu...@apache.org
Subject svn commit: r594520 [5/10] - in /geronimo/javamail/trunk/geronimo-javamail_1.4: ./ geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/ geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handl...
Date Tue, 13 Nov 2007 12:57:53 GMT
Added: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java?rev=594520&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java Tue Nov 13 04:57:39 2007
@@ -0,0 +1,1462 @@
+/**
+ * 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 org.apache.geronimo.javamail.store.imap.connection;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Vector;
+
+import javax.mail.FetchProfile; 
+import javax.mail.Flags;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Quota;
+import javax.mail.UIDFolder;
+
+import javax.mail.search.AddressTerm;
+import javax.mail.search.AndTerm;
+import javax.mail.search.BodyTerm;
+import javax.mail.search.ComparisonTerm;
+import javax.mail.search.DateTerm;
+import javax.mail.search.FlagTerm;
+import javax.mail.search.FromTerm;
+import javax.mail.search.FromStringTerm;
+import javax.mail.search.HeaderTerm;
+import javax.mail.search.MessageIDTerm;
+import javax.mail.search.MessageNumberTerm;
+import javax.mail.search.NotTerm;
+import javax.mail.search.OrTerm;
+import javax.mail.search.ReceivedDateTerm;
+import javax.mail.search.RecipientTerm;
+import javax.mail.search.RecipientStringTerm;
+import javax.mail.search.SearchException;
+import javax.mail.search.SearchTerm;
+import javax.mail.search.SentDateTerm;
+import javax.mail.search.SizeTerm;
+import javax.mail.search.StringTerm;
+import javax.mail.search.SubjectTerm;
+
+import org.apache.geronimo.javamail.store.imap.ACL; 
+import org.apache.geronimo.javamail.store.imap.IMAPFolder;
+import org.apache.geronimo.javamail.store.imap.Rights; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+
+import org.apache.geronimo.javamail.util.CommandFailedException;
+
+
+/**
+ * Utility class for building up what might be complex arguments
+ * to a command.  This includes the ability to directly write out
+ * binary arrays of data and have them constructed as IMAP
+ * literals.
+ */
+public class IMAPCommand {
+
+    // digits table for encoding IMAP modified Base64.  Note that this differs
+    // from "normal" base 64 by using ',' instead of '/' for the last digit.
+    public static final char[] encodingTable = {
+        'A', 'B', 'C', 'D', 'E', 'F', 'G',
+        'H', 'I', 'J', 'K', 'L', 'M', 'N',
+        'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+        'V', 'W', 'X', 'Y', 'Z',
+        'a', 'b', 'c', 'd', 'e', 'f', 'g',
+        'h', 'i', 'j', 'k', 'l', 'm', 'n',
+        'o', 'p', 'q', 'r', 's', 't', 'u',
+        'v', 'w', 'x', 'y', 'z',
+        '0', '1', '2', '3', '4', '5', '6',
+        '7', '8', '9',
+        '+', ','
+    };
+    
+    protected boolean needWhiteSpace = false;
+
+    // our utility writer stream
+    protected DataOutputStream out;
+    // the real output target
+    protected ByteArrayOutputStream sink;
+    // our command segment set.  If the command contains literals, then the literal     
+    // data must be sent after receiving an continue response back from the server. 
+    protected List segments = null; 
+    // the append tag for the response 
+    protected String tag; 
+    
+    // our counter used to generate command tags.
+    static protected int tagCounter = 0;
+
+    /**
+     * Create an empty command. 
+     */
+    public IMAPCommand() {
+        try {
+            sink = new ByteArrayOutputStream();
+            out = new DataOutputStream(sink);
+
+            // write the tag data at the beginning of the command. 
+            out.writeBytes(getTag()); 
+            // need a blank separator 
+            out.write(' '); 
+        } catch (IOException e ) {
+        }
+    }
+
+    /**
+     * Create a command with an initial command string.
+     * 
+     * @param command The command string used to start this command.
+     */
+    public IMAPCommand(String command) {
+        this(); 
+        append(command); 
+    }
+    
+    public String getTag() {
+        if (tag == null) {
+            // the tag needs to be non-numeric, so tack a convenient alpha character on the front.
+            tag = "a" + tagCounter++;
+        }
+        return tag; 
+    }
+    
+    
+    /**
+     * Save the current segment of the command we've accumulated.  This 
+     * generally occurs because we have a literal element in the command 
+     * that's going to require a continuation response from the server before 
+     * we can send it. 
+     */
+    private void saveCurrentSegment() 
+    {
+        try {
+            out.flush();     // make sure everything is written 
+                             // get the data so far and reset the sink 
+            byte[] segment = sink.toByteArray(); 
+            sink.reset(); 
+            // most commands don't have segments, so don't create the list until we do. 
+            if (segments == null) {
+                segments = new ArrayList(); 
+            }
+            // ok, we need to issue this command as a conversation. 
+            segments.add(segment);
+        } catch (IOException e) {
+        }
+    }
+    
+    
+    /**
+     * Write all of the command data to the stream.  This includes the 
+     * leading tag data. 
+     * 
+     * @param outStream
+     * @param connection
+     * 
+     * @exception IOException
+     * @exception MessagingException
+     */
+    public void writeTo(OutputStream outStream, IMAPConnection connection) throws IOException, MessagingException
+    {
+        
+        // just a simple, single string-encoded command?
+        if (segments == null) {
+            // make sure the output stream is flushed
+            out.flush(); 
+            // just copy the command data to the output stream 
+            sink.writeTo(outStream); 
+            // we need to end the command with a CRLF sequence. 
+            outStream.write('\r');
+            outStream.write('\n');
+        }
+        // multiple-segment mode, which means we need to deal with continuation responses at 
+        // each of the literal boundaries. 
+        else { 
+            // at this point, we have a list of command pieces that must be written out, then a 
+            // continuation response checked for after each write.  Once each of these pieces is 
+            // written out, we still have command stuff pending in the out stream, which we'll tack  
+            // on to the end. 
+            for (int i = 0; i < segments.size(); i++) {
+                outStream.write((byte [])segments.get(i)); 
+                // now wait for a response from the connection.  We should be getting a  
+                // continuation response back (and might have also received some asynchronous 
+                // replies, which we'll leave in the queue for now.  If we get some status back 
+                // other than than a continue, we've got an error in our command somewhere. 
+                IMAPTaggedResponse response = connection.receiveResponse(); 
+                if (!response.isContinuation()) {
+                    throw new CommandFailedException("Error response received on a IMAP continued command:  " + response); 
+                }
+            }
+            out.flush();
+            // all leading segments written with the appropriate continuation received in reply. 
+            // just copy the command data to the output stream 
+            sink.writeTo(outStream); 
+            // we need to end the command with a CRLF sequence. 
+            outStream.write('\r');
+            outStream.write('\n');
+        }
+    }
+
+
+    /**
+     * Directly append a value to the buffer without attempting
+     * to insert whitespace or figure out any format encodings.
+     *
+     * @param value  The value to append.
+     */
+    public void append(String value) {
+        try {
+            // add the bytes direcly 
+            out.writeBytes(value);
+            // assume we're needing whitespace after this (pretty much unknown).
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a string value to a command buffer.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendString(String value) {
+        // work off the byte values
+        appendString(value.getBytes());
+    }
+
+
+    /**
+     * Append a string value to a command buffer.  This always appends as
+     * a QUOTEDSTRING
+     * 
+     * @param value  The value to append.
+     */
+    public void appendQuotedString(String value) {
+        // work off the byte values
+        appendQuotedString(value.getBytes());
+    }
+
+
+    /**
+     * Append a string value to a command buffer, with encoding.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendEncodedString(String value) {
+        // encode first.
+        value = encode(value);
+        // work off the byte values
+        appendString(value.getBytes());
+    }
+
+    
+    /**
+     * Encode a string using the modified UTF-7 encoding.
+     *
+     * @param original The original string.
+     *
+     * @return The original string encoded with modified UTF-7 encoding.
+     */
+    public String encode(String original) {
+
+        // buffer for encoding sections of data
+        byte[] buffer = new byte[4];
+        int bufferCount = 0;
+
+        StringBuffer result = new StringBuffer();
+
+        // state flag for the type of section we're in.
+        boolean encoding = false;
+
+        for (int i = 0; i < original.length(); i++) {
+            char ch = original.charAt(i);
+
+            // processing an encoded section?
+            if (encoding) {
+                // is this a printable character?
+                if (ch > 31 && ch < 127) {
+                    // encode anything in the buffer
+                    encode(buffer, bufferCount, result);
+                    // add the section terminator char
+                    result.append('-');
+                    encoding = false;
+                    // we now fall through to the printable character section.
+                }
+                // still an unprintable
+                else {
+                    // add this char to the working buffer?
+                    buffer[++bufferCount] = (byte)(ch >> 8);
+                    buffer[++bufferCount] = (byte)(ch & 0xff);
+                    // if we have enough to encode something, do it now.
+                    if (bufferCount >= 3) {
+                        bufferCount = encode(buffer, bufferCount, result);
+                    }
+                    // go back to the top of the loop.
+                    continue;
+                }
+            }
+            // is this the special printable?
+            if (ch == '&') {
+                // this is the special null escape sequence
+                result.append('&');
+                result.append('-');
+            }
+            // is this a printable character?
+            else if (ch > 31 && ch < 127) {
+                // just add to the result
+                result.append(ch);
+            }
+            else {
+                // write the escape character
+                result.append('&');
+
+                // non-printable ASCII character, we need to switch modes
+                // both bytes of this character need to be encoded.  Each
+                // encoded digit will basically be a "character-and-a-half".
+                buffer[0] = (byte)(ch >> 8);
+                buffer[1] = (byte)(ch & 0xff);
+                bufferCount = 2;
+                encoding = true;
+            }
+        }
+        // were we in a non-printable section at the end?
+        if (encoding) {
+            // take care of any remaining characters
+            encode(buffer, bufferCount, result);
+            // add the section terminator char
+            result.append('-');
+        }
+        // convert the encoded string.
+        return result.toString();
+    }
+    
+
+    /**
+     * Encode a single buffer of characters.  This buffer will have
+     * between 0 and 4 bytes to encode.
+     *
+     * @param buffer The buffer to encode.
+     * @param count  The number of characters in the buffer.
+     * @param result The accumulator for appending the result.
+     *
+     * @return The remaining number of bytes remaining in the buffer (return 0
+     *         unless the count was 4 at the beginning).
+     */
+    protected static int encode(byte[] buffer, int count, StringBuffer result) {
+        byte b1 = 0;
+        byte b2 = 0;
+        byte b3 = 0;
+
+        // different processing based on how much we have in the buffer
+        switch (count) {
+            // ended at a boundary.  This is cool, not much to do.
+            case 0:
+                // no residual in the buffer
+                return 0;
+
+            // just a single left over byte from the last encoding op.
+            case 1:
+                b1 = buffer[0];
+                result.append(encodingTable[(b1 >>> 2) & 0x3f]);
+                result.append(encodingTable[(b1 << 4) & 0x30]);
+                return 0;
+
+            // one complete char to encode
+            case 2:
+                b1 = buffer[0];
+                b2 = buffer[1];
+                result.append(encodingTable[(b1 >>> 2) & 0x3f]);
+                result.append(encodingTable[((b1 << 4) & 0x30) + ((b2 >>>4) & 0x0f)]);
+                result.append(encodingTable[((b2 << 2) & (0x3c))]);
+                return 0;
+
+            // at least a full triplet of bytes to encode
+            case 3:
+            case 4:
+                b1 = buffer[0];
+                b2 = buffer[1];
+                b3 = buffer[2];
+                result.append(encodingTable[(b1 >>> 2) & 0x3f]);
+                result.append(encodingTable[((b1 << 4) & 0x30) + ((b2 >>>4) & 0x0f)]);
+                result.append(encodingTable[((b2 << 2) & 0x3c) + ((b3 >>> 6) & 0x03)]);
+                result.append(encodingTable[b3 & 0x3f]);
+
+                // if we have more than the triplet, we need to move the extra one into the first
+                // position and return the residual indicator
+                if (count == 4) {
+                    buffer[0] = buffer[4];
+                    return 1;
+                }
+                return 0;
+        }
+        return 0;
+    }
+
+
+    /**
+     * Append a string value to a command buffer.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendString(String value, String charset) throws MessagingException {
+        if (charset == null) {
+            // work off the byte values
+            appendString(value.getBytes());
+        }
+        else {
+            try {
+                // use the charset to extract the bytes
+                appendString(value.getBytes(charset));
+            } catch (UnsupportedEncodingException e) {
+                throw new MessagingException("Invalid text encoding");
+            }
+        }
+    }
+
+
+    /**
+     * Append a value in a byte array to a command buffer.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendString(byte[] value) {
+        // sort out how we need to append this
+        switch (IMAPResponseTokenizer.getEncoding(value)) {
+            case Token.LITERAL:
+                appendLiteral(value);
+                break;
+            case Token.QUOTEDSTRING:
+                appendQuotedString(value);
+                break;
+            case Token.ATOM:
+                appendAtom(value);
+                break;
+        }
+    }
+
+
+    /**
+     * Append an integer value to the command, converting 
+     * the integer into string form.
+     * 
+     * @param value  The value to append.
+     */
+    public void appendInteger(int value) {
+        appendAtom(Integer.toString(value));
+    }
+
+    
+    /**
+     * Append a long value to the command, converting 
+     * the integer into string form.
+     * 
+     * @param value  The value to append.
+     */
+    public void appendLong(long value) {
+        appendAtom(Long.toString(value));
+    }
+
+
+    /**
+     * Append an atom value to the command.  Atoms are directly 
+     * appended without using literal encodings. 
+     * 
+     * @param value  The value to append.
+     */
+    public void appendAtom(String value) {
+        appendAtom(value.getBytes());
+    }
+
+
+
+    /**
+     * Append an atom to the command buffer.  Atoms are directly 
+     * appended without using literal encodings.  White space is  
+     * accounted for with the append operation. 
+     * 
+     * @param value  The value to append.
+     */
+    public void appendAtom(byte[] value) {
+        try {
+            // give a token separator
+            conditionalWhitespace();
+            // ATOMs are easy
+            out.write(value);
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append an IMAP literal values to the command.  
+     * literals are written using a header with the length 
+     * specified, followed by a CRLF sequence, followed 
+     * by the literal data. 
+     * 
+     * @param value  The literal data to write.
+     */
+    public void appendLiteral(byte[] value) {
+        try {
+            appendLiteralHeader(value.length);
+            out.write(value);
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Add a literal header to the buffer.  The literal 
+     * header is the literal length enclosed in a 
+     * "{n}" pair, followed by a CRLF sequence.
+     * 
+     * @param size   The size of the literal value.
+     */
+    protected void appendLiteralHeader(int size) {
+        try {
+            conditionalWhitespace();
+            out.writeByte('{');
+            out.writeBytes(Integer.toString(size));
+            out.writeBytes("}\r\n");
+            // the IMAP client is required to send literal data to the server by 
+            // writing the command up to the header, then waiting for a continuation 
+            // response to send the rest. 
+            saveCurrentSegment(); 
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append literal data to the command where the 
+     * literal sourcd is a ByteArrayOutputStream.
+     * 
+     * @param value  The source of the literal data.
+     */
+    public void appendLiteral(ByteArrayOutputStream value) {
+        try {
+            appendLiteralHeader(value.size());
+            // have this output stream write directly into our stream
+            value.writeTo(out);
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Write out a string of literal data, taking into 
+     * account the need to escape both '"' and '\' 
+     * characters.
+     * 
+     * @param value  The bytes of the string to write.
+     */
+    public void appendQuotedString(byte[] value) {
+        try {
+            conditionalWhitespace();
+            out.writeByte('"');
+
+            // look for chars requiring escaping
+            for (int i = 0; i < value.length; i++) {
+                byte ch = value[i];
+
+                if (ch == '"' || ch == '\\') {
+                    out.writeByte('\\');
+                }
+                out.writeByte(ch);
+            }
+
+            out.writeByte('"');
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Mark the start of a list value being written to 
+     * the command.  A list is a sequences of different 
+     * tokens enclosed in "(" ")" pairs.  Lists can 
+     * be nested. 
+     */
+    public void startList() {
+        try {
+            conditionalWhitespace();
+            out.writeByte('(');
+            needWhiteSpace = false;
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Write out the end of the list. 
+     */
+    public void endList() {
+        try {
+            out.writeByte(')');
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Add a whitespace character to the command if the 
+     * previous token was a type that required a 
+     * white space character to mark the boundary. 
+     */
+    protected void conditionalWhitespace() {
+        try {
+            if (needWhiteSpace) {
+                out.writeByte(' ');
+            }
+            // all callers of this are writing a token that will need white space following, so turn this on
+            // every time we're called.
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a body section specification to a command string.  Body
+     * section specifications are of the form "[section]<start.count>".
+     * 
+     * @param section  The section numeric identifier.
+     * @param partName The name of the body section we want (e.g. "TEST", "HEADERS").
+     */
+    public void appendBodySection(String section, String partName) {
+        try {
+            // we sometimes get called from the top level 
+            if (section == null) {
+                appendBodySection(partName); 
+                return;
+            }
+
+            out.writeByte('[');
+            out.writeBytes(section);
+            if (partName != null) {
+                out.writeByte('.');
+                out.writeBytes(partName);
+            }
+            out.writeByte(']');
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a body section specification to a command string.  Body
+     * section specifications are of the form "[section]".
+     * 
+     * @param partName The partname we require.
+     */
+    public void appendBodySection(String partName) {
+        try {
+            out.writeByte('[');
+            out.writeBytes(partName);
+            out.writeByte(']');
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a set of flags to a command buffer.
+     *
+     * @param flags  The flag set to append.
+     */
+    public void appendFlags(Flags flags) {
+        startList();
+
+        Flags.Flag[] systemFlags = flags.getSystemFlags();
+
+        // process each of the system flag names
+        for (int i = 0; i < systemFlags.length; i++) {
+            Flags.Flag flag = systemFlags[i];
+
+            if (flag == Flags.Flag.ANSWERED) {
+                appendAtom("\\Answered");
+            }
+            else if (flag == Flags.Flag.DELETED) {
+                appendAtom("\\Deleted");
+            }
+            else if (flag == Flags.Flag.DRAFT) {
+                appendAtom("\\Draft");
+            }
+            else if (flag == Flags.Flag.FLAGGED) {
+                appendAtom("\\Flagged");
+            }
+            else if (flag == Flags.Flag.RECENT) {
+                appendAtom("\\Recent");
+            }
+            else if (flag == Flags.Flag.SEEN) {
+                appendAtom("\\Seen");
+            }
+        }
+
+        // now process the user flags, which just get appended as is.
+        String[] userFlags = flags.getUserFlags();
+
+        for (int i = 0; i < userFlags.length; i++) {
+            appendAtom(userFlags[i]);
+        }
+
+        // close the list off
+        endList();
+    }
+
+
+    /**
+     * Format a date into the form required for IMAP commands.
+     *
+     * @param d      The source Date.
+     */
+    public void appendDate(Date d) {
+        // get a formatter to create IMAP dates.  Use the US locale, as the dates are not localized.
+        IMAPDateFormat formatter = new IMAPDateFormat();
+        // date_time strings need to be done as quoted strings because they contain blanks.
+        appendString(formatter.format(d));
+    }
+
+
+    /**
+     * Format a date into the form required for IMAP search commands.
+     *
+     * @param d      The source Date.
+     */
+    public void appendSearchDate(Date d) {
+        // get a formatter to create IMAP dates.  Use the US locale, as the dates are not localized.
+        IMAPSearchDateFormat formatter = new IMAPSearchDateFormat();
+        // date_time strings need to be done as quoted strings because they contain blanks.
+        appendString(formatter.format(d));
+    }
+    
+    
+    /**
+     * append an IMAP search sequence from a SearchTerm.  SearchTerms
+     * terms can be complex sets of terms in a tree form, so this
+     * may involve some recursion to completely translate.
+     *
+     * @param term    The search term we're processing.
+     * @param charset The charset we need to use when generating the sequence.
+     *
+     * @exception MessagingException
+     */
+    public void appendSearchTerm(SearchTerm term, String charset) throws MessagingException {
+        // we need to do this manually, by inspecting the term object against the various SearchTerm types
+        // defined by the javamail spec.
+
+        // Flag searches are used internally by other operations, so this is a good one to check first.
+        if (term instanceof FlagTerm) {
+            appendFlag((FlagTerm)term, charset);
+        }
+        // after that, I'm not sure there's any optimal order to these.  Let's start with the conditional
+        // modifiers (AND, OR, NOT), then just hit each of the header types
+        else if (term instanceof AndTerm) {
+            appendAnd((AndTerm)term, charset);
+        }
+        else if (term instanceof OrTerm) {
+            appendOr((OrTerm)term, charset);
+        }
+        else if (term instanceof NotTerm) {
+            appendNot((NotTerm)term, charset);
+        }
+        // multiple forms of From: search
+        else if (term instanceof FromTerm) {
+            appendFrom((FromTerm)term, charset);
+        }
+        else if (term instanceof FromStringTerm) {
+            appendFrom((FromStringTerm)term, charset);
+        }
+        else if (term instanceof HeaderTerm) {
+            appendHeader((HeaderTerm)term, charset);
+        }
+        else if (term instanceof RecipientTerm) {
+            appendRecipient((RecipientTerm)term, charset);
+        }
+        else if (term instanceof RecipientStringTerm) {
+            appendRecipient((RecipientStringTerm)term, charset);
+        }
+        else if (term instanceof SubjectTerm) {
+            appendSubject((SubjectTerm)term, charset);
+        }
+        else if (term instanceof BodyTerm) {
+            appendBody((BodyTerm)term, charset);
+        }
+        else if (term instanceof SizeTerm) {
+            appendSize((SizeTerm)term, charset);
+        }
+        else if (term instanceof SentDateTerm) {
+            appendSentDate((SentDateTerm)term, charset);
+        }
+        else if (term instanceof ReceivedDateTerm) {
+            appendReceivedDate((ReceivedDateTerm)term, charset);
+        }
+        else if (term instanceof MessageIDTerm) {
+            appendMessageID((MessageIDTerm)term, charset);
+        }
+        else {
+            // don't know what this is
+            throw new SearchException("Unsupported search type");
+        }
+    }
+
+    /**
+     * append IMAP search term information from a FlagTerm item.
+     *
+     * @param term    The source FlagTerm
+     * @param charset target charset for the search information (can be null).
+     * @param out     The target command buffer.
+     */
+    protected void appendFlag(FlagTerm term, String charset) {
+        // decide which one we need to test for
+        boolean set = term.getTestSet();
+
+        Flags flags = term.getFlags();
+        Flags.Flag[] systemFlags = flags.getSystemFlags();
+
+        String[] userFlags = flags.getUserFlags();
+
+        // empty search term?  not sure if this is an error.  The default search implementation would
+        // not consider this an error, so we'll just ignore this.
+        if (systemFlags.length == 0 && userFlags.length == 0) {
+            return;
+        }
+
+        if (set) {
+            for (int i = 0; i < systemFlags.length; i++) {
+                Flags.Flag flag = systemFlags[i];
+
+                if (flag == Flags.Flag.ANSWERED) {
+                    appendAtom("ANSWERED");
+                }
+                else if (flag == Flags.Flag.DELETED) {
+                    appendAtom("DELETED");
+                }
+                else if (flag == Flags.Flag.DRAFT) {
+                    appendAtom("DRAFT");
+                }
+                else if (flag == Flags.Flag.FLAGGED) {
+                    appendAtom("FLAGGED");
+                }
+                else if (flag == Flags.Flag.RECENT) {
+                    appendAtom("RECENT");
+                }
+                else if (flag == Flags.Flag.SEEN) {
+                    appendAtom("SEEN");
+                }
+            }
+        }
+        else {
+            for (int i = 0; i < systemFlags.length; i++) {
+                Flags.Flag flag = systemFlags[i];
+
+                if (flag == Flags.Flag.ANSWERED) {
+                    appendAtom("UNANSWERED");
+                }
+                else if (flag == Flags.Flag.DELETED) {
+                    appendAtom("UNDELETED");
+                }
+                else if (flag == Flags.Flag.DRAFT) {
+                    appendAtom("UNDRAFT");
+                }
+                else if (flag == Flags.Flag.FLAGGED) {
+                    appendAtom("UNFLAGGED");
+                }
+                else if (flag == Flags.Flag.RECENT) {
+                    // not UNRECENT?
+                    appendAtom("OLD");
+                }
+                else if (flag == Flags.Flag.SEEN) {
+                    appendAtom("UNSEEN");
+                }
+            }
+        }
+
+
+        // User flags are done as either "KEYWORD name" or "UNKEYWORD name"
+        for (int i = 0; i < userFlags.length; i++) {
+            appendAtom(set ? "KEYWORD" : "UNKEYWORD");
+            appendAtom(userFlags[i]);
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from an AndTerm item.
+     *
+     * @param term    The source AndTerm
+     * @param charset target charset for the search information (can be null).
+     * @param out     The target command buffer.
+     */
+    protected void appendAnd(AndTerm term, String charset) throws MessagingException {
+        // ANDs are pretty easy.  Just append all of the terms directly to the
+        // command as is.
+
+        SearchTerm[] terms = term.getTerms();
+
+        for (int i = 0; i < terms.length; i++) {
+            appendSearchTerm(terms[i], charset);
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from an OrTerm item.
+     *
+     * @param term    The source OrTerm
+     * @param charset target charset for the search information (can be null).
+     * @param out     The target command buffer.
+     */
+    protected void appendOr(OrTerm term, String charset) throws MessagingException {
+        SearchTerm[] terms = term.getTerms();
+
+        // OrTerms are a bit of a pain to translate to IMAP semantics.  The IMAP OR operation only allows 2
+        // search keys, while OrTerms can have n keys (including, it appears, just one!  If we have more than
+        // 2, it's easiest to convert this into a tree of OR keys and let things generate that way.  The
+        // resulting IMAP operation would be OR (key1) (OR (key2) (key3))
+
+        // silly rabbit...somebody doesn't know how to use OR
+        if (terms.length == 1) {
+            // just append the singleton in place without the OR operation.
+            appendSearchTerm(terms[0], charset);
+            return;
+        }
+
+        // is this a more complex operation?
+        if (terms.length > 2) {
+            // have to chain these together (shazbat).
+            SearchTerm current = terms[0];
+
+            for (int i = 1; i < terms.length; i++) {
+                current = new OrTerm(current, terms[i]);
+            }
+
+            // replace the term array with the newly generated top array
+            terms = ((OrTerm)current).getTerms();
+        }
+
+        // we're going to generate this with parenthetical search keys, even if it is just a simple term.
+        appendAtom("OR");
+        startList(); 
+        // generated OR argument 1
+        appendSearchTerm(terms[0], charset);
+        endList(); 
+        startList(); 
+        // generated OR argument 2
+        appendSearchTerm(terms[0], charset);
+        // and the closing parens
+        endList(); 
+    }
+
+
+    /**
+     * append IMAP search term information from a NotTerm item.
+     *
+     * @param term    The source NotTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendNot(NotTerm term, String charset) throws MessagingException {
+        // we're goint to generate this with parenthetical search keys, even if it is just a simple term.
+        appendAtom("NOT");
+        startList(); 
+        // generated the NOT expression
+        appendSearchTerm(term.getTerm(), charset);
+        // and the closing parens
+        endList(); 
+    }
+
+
+    /**
+     * append IMAP search term information from a FromTerm item.
+     *
+     * @param term    The source FromTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendFrom(FromTerm term, String charset) throws MessagingException {
+        appendAtom("FROM");
+        // this may require encoding
+        appendString(term.getAddress().toString(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a FromStringTerm item.
+     *
+     * @param term    The source FromStringTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendFrom(FromStringTerm term, String charset) throws MessagingException {
+        appendAtom("FROM");
+        // this may require encoding
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a RecipientTerm item.
+     *
+     * @param term    The source RecipientTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendRecipient(RecipientTerm term, String charset) throws MessagingException {
+        appendAtom(recipientType(term.getRecipientType()));
+        // this may require encoding
+        appendString(term.getAddress().toString(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a RecipientStringTerm item.
+     *
+     * @param term    The source RecipientStringTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendRecipient(RecipientStringTerm term, String charset) throws MessagingException {
+        appendAtom(recipientType(term.getRecipientType()));
+        // this may require encoding
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * Translate a recipient type into it's string name equivalent.
+     *
+     * @param type   The source recipient type
+     *
+     * @return A string name matching the recipient type.
+     */
+    protected String recipientType(Message.RecipientType type) throws MessagingException {
+        if (type == Message.RecipientType.TO) {
+            return "TO";
+        }
+        if (type == Message.RecipientType.CC) {
+            return "CC";
+        }
+        if (type == Message.RecipientType.BCC) {
+            return "BCC";
+        }
+
+        throw new SearchException("Unsupported RecipientType");
+    }
+
+
+    /**
+     * append IMAP search term information from a HeaderTerm item.
+     *
+     * @param term    The source HeaderTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendHeader(HeaderTerm term, String charset) throws MessagingException {
+        appendAtom("HEADER");
+        appendString(term.getHeaderName());
+        appendString(term.getPattern(), charset);
+    }
+
+
+
+    /**
+     * append IMAP search term information from a SubjectTerm item.
+     *
+     * @param term    The source SubjectTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendSubject(SubjectTerm term, String charset) throws MessagingException {
+        appendAtom("SUBJECT");
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a BodyTerm item.
+     *
+     * @param term    The source BodyTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendBody(BodyTerm term, String charset) throws MessagingException {
+        appendAtom("BODY");
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a SizeTerm item.
+     *
+     * @param term    The source SizeTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendSize(SizeTerm term, String charset) throws MessagingException {
+
+        // these comparisons can be a real pain.  IMAP only supports LARGER and SMALLER.  So comparisons
+        // other than GT and LT have to be composed of complex sequences of these.  For example, an EQ
+        // comparison becomes NOT LARGER size NOT SMALLER size
+
+        if (term.getComparison() == ComparisonTerm.GT) {
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.LT) {
+            appendAtom("SMALLER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.EQ) {
+            appendAtom("NOT");
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+
+            appendAtom("NOT");
+            appendAtom("SMALLER");
+            // it's just right <g>
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.NE) {
+            // this needs to be an OR comparison
+            appendAtom("OR");
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+
+            appendAtom("SMALLER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.LE) {
+            // just the inverse of LARGER
+            appendAtom("NOT");
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.GE) {
+            // and the reverse.
+            appendAtom("NOT");
+            appendAtom("SMALLER");
+            appendInteger(term.getNumber());
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from a MessageIDTerm item.
+     *
+     * @param term    The source MessageIDTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendMessageID(MessageIDTerm term, String charset) throws MessagingException {
+
+        // not directly supported by IMAP, but we can compare on the header information.
+        appendAtom("HEADER");
+        appendString("Message-ID");
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a SendDateTerm item.
+     *
+     * @param term    The source SendDateTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendSentDate(SentDateTerm term, String charset) throws MessagingException {
+        Date date = term.getDate();
+
+        switch (term.getComparison()) {
+            case ComparisonTerm.EQ:
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LT:
+                appendAtom("SENTBEFORE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GT:
+                appendAtom("SENTSINCE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GE:
+                appendAtom("OR");
+                appendAtom("SENTSINCE");
+                appendSearchDate(date);
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LE:
+                appendAtom("OR");
+                appendAtom("SENTBEFORE");
+                appendSearchDate(date);
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.NE:
+                appendAtom("NOT");
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            default:
+                throw new SearchException("Unsupported date comparison type");
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from a ReceivedDateTerm item.
+     *
+     * @param term    The source ReceivedDateTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendReceivedDate(ReceivedDateTerm term, String charset) throws MessagingException {
+        Date date = term.getDate();
+
+        switch (term.getComparison()) {
+            case ComparisonTerm.EQ:
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LT:
+                appendAtom("BEFORE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GT:
+                appendAtom("SINCE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GE:
+                appendAtom("OR");
+                appendAtom("SINCE");
+                appendSearchDate(date);
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LE:
+                appendAtom("OR");
+                appendAtom("BEFORE");
+                appendSearchDate(date);
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.NE:
+                appendAtom("NOT");
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            default:
+                throw new SearchException("Unsupported date comparison type");
+        }
+    }
+
+
+    /**
+     * Run the tree of search terms, checking for problems with
+     * the terms that may require specifying a CHARSET modifier
+     * on a SEARCH command sent to the server.
+     *
+     * @param term   The term to check.
+     *
+     * @return True if there are 7-bit problems, false if the terms contain
+     *         only 7-bit ASCII characters.
+     */
+    static public boolean checkSearchEncoding(SearchTerm term) {
+        // StringTerm is the basis of most of the string-valued terms, and are most important ones to check.
+        if (term instanceof StringTerm) {
+            return checkStringEncoding(((StringTerm)term).getPattern());
+        }
+        // Address terms are basically string terms also, but we need to check the string value of the
+        // addresses, since that's what we're sending along.  This covers a lot of the TO/FROM, etc. searches.
+        else if (term instanceof AddressTerm) {
+            return checkStringEncoding(((AddressTerm)term).getAddress().toString());
+        }
+        // the NOT contains a term itself, so recurse on that.  The NOT does not directly have string values
+        // to check.
+        else if (term instanceof NotTerm) {
+            return checkSearchEncoding(((NotTerm)term).getTerm());
+        }
+        // AND terms and OR terms have lists of subterms that must be checked.
+        else if (term instanceof AndTerm) {
+            return checkSearchEncoding(((AndTerm)term).getTerms());
+        }
+        else if (term instanceof OrTerm) {
+            return checkSearchEncoding(((OrTerm)term).getTerms());
+        }
+
+        // non of the other term types (FlagTerm, SentDateTerm, etc.) pose a problem, so we'll give them
+        // a free pass.
+        return false;
+    }
+
+
+    /**
+     * Run an array of search term items to check each one for ASCII
+     * encoding problems.
+     *
+     * @param terms  The array of terms to check.
+     *
+     * @return True if any of the search terms contains a 7-bit ASCII problem,
+     *         false otherwise.
+     */
+    static public boolean checkSearchEncoding(SearchTerm[] terms) {
+        for (int i = 0; i < terms.length; i++) {
+            if (checkSearchEncoding(terms[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Check a string to see if this can be processed using just
+     * 7-bit ASCII.
+     *
+     * @param s      The string to check
+     *
+     * @return true if the string contains characters outside the 7-bit ascii range,
+     *         false otherwise.
+     */
+    static public boolean checkStringEncoding(String s) {
+        for (int i = 0; i < s.length(); i++) {
+            // any value greater that 0x7f is a problem char.  We're not worried about
+            // lower ctl chars (chars < 32) since those are still expressible in 7-bit.
+            if (s.charAt(i) > 127) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+    
+    
+    /**
+     * Append a FetchProfile information to an IMAPCommand
+     * that's to be issued.
+     * 
+     * @param profile The fetch profile we're using.
+     * 
+     * @exception MessagingException
+     */
+    public void appendFetchProfile(FetchProfile profile) throws MessagingException {
+        // the fetch profile items are a parenthtical list passed on a 
+        // FETCH command. 
+        startList(); 
+        if (profile.contains(UIDFolder.FetchProfileItem.UID)) {
+            appendAtom("UID"); 
+        }
+        if (profile.contains(FetchProfile.Item.ENVELOPE)) {
+            // fetching the envelope involves several items 
+            appendAtom("ENVELOPE"); 
+            appendAtom("INTERNALDATE"); 
+            appendAtom("RFC822.SIZE"); 
+        }
+        if (profile.contains(FetchProfile.Item.FLAGS)) {
+            appendAtom("FLAGS"); 
+        }
+        if (profile.contains(FetchProfile.Item.CONTENT_INFO)) {
+            appendAtom("BODYSTRUCTURE"); 
+        }
+        if (profile.contains(IMAPFolder.FetchProfileItem.SIZE)) {
+            appendAtom("RFC822.SIZE"); 
+        }
+        // There are two choices here, that are sort of redundant.  
+        // if all headers have been requested, there's no point in 
+        // adding any specifically requested one. 
+        if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
+            appendAtom("BODY.PEEK[HEADER]"); 
+        }
+        else {
+            String[] headers = profile.getHeaderNames(); 
+            // have an actual list to retrieve?  need to craft this as a sublist
+            // of identified fields. 
+            if (headers.length > 0) {
+                appendAtom("BODY.PEEK[HEADER.FIELDS]"); 
+                startList(); 
+                for (int i = 0; i < headers.length; i++) {
+                    appendAtom(headers[i]); 
+                }
+                endList(); 
+            }
+        }
+        // end the list.  
+        endList(); 
+    }
+    
+    
+    /**
+     * Append an ACL value to a command.  The ACL is the writes string name, 
+     * followed by the rights value.  This version uses no +/- modifier.
+     * 
+     * @param acl    The ACL to append.
+     */
+    public void appendACL(ACL acl) {
+        appendACL(acl, null); 
+    }
+    
+    /**
+     * Append an ACL value to a command.  The ACL is the writes string name,
+     * followed by the rights value.  A +/- modifier can be added to the 
+     * // result. 
+     * 
+     * @param acl      The ACL to append.
+     * @param modifier The modifer string (can be null).
+     */
+    public void appendACL(ACL acl, String modifier) {
+        appendString(acl.getName()); 
+        String rights = acl.getRights().toString(); 
+        
+        if (modifier != null) {
+            rights = modifier + rights; 
+        }
+        appendString(rights); 
+    }
+    
+    
+    /**
+     * Append a quota specification to an IMAP command. 
+     * 
+     * @param quota  The quota value to append.
+     */
+    public void appendQuota(Quota quota) {
+        appendString(quota.quotaRoot); 
+        startList(); 
+        for (int i = 0; i < quota.resources.length; i++) {
+            appendQuotaResource(quota.resources[i]); 
+        }
+        endList(); 
+    }
+    
+    /**
+     * Append a Quota.Resource element to an IMAP command.  This converts as 
+     * the resoure name, the usage value and limit value). 
+     * 
+     * @param resource The resource element we're appending.
+     */
+    public void appendQuotaResource(Quota.Resource resource) {
+        appendAtom(resource.name); 
+        // NB:  For command purposes, only the limit is used. 
+        appendLong(resource.limit);
+    }
+}
+

Propchange: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message