geronimo-javamail-1.4-provider-1.8.3/ 0000775 0001750 0001750 00000000000 11703373731 016233 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/pom.xml 0000664 0001750 0001750 00000011013 11611717425 017544 0 ustar brian brian
4.0.0
org.apache.geronimo.javamail
geronimo-javamail_1.4
1.8.3
geronimo-javamail_1.4_provider
bundle
Geronimo JavaMail 1.4 :: Provider
org.apache.geronimo.specs
geronimo-activation_1.1_spec
provided
org.apache.geronimo.specs
geronimo-javamail_1.4_spec
provided
junit
junit
test
com.icegreen
greenmail
1.3.1b
test
javax.mail
mail
org.slf4j
slf4j-jdk14
1.3.1
test
org.apache.felix
maven-bundle-plugin
${groupId}.${artifactId};singleton=true
JSR-919 Javamail API 1.4 provider bundle
Sun Microsystems, Inc.
1.4
org.apache.geronimo.javamail.util,
org.apache.geronimo.javamail.authentication
org.apache.geronimo.javamail.store*;version=1.4,
org.apache.geronimo.javamail.transport*;version=1.4,
org.apache.geronimo.javamail.handlers*;version=1.4
javax.activation,
javax.mail*,
org.apache.geronimo.mail.util,
javax.imageio*;resolution:="optional",
javax.net.ssl*;resolution:="optional",
javax.security.sasl*;resolution:="optional",
javax.security.auth.callback*;resolution:="optional"
true
geronimo-javamail-1.4-provider-1.8.3/src/ 0000775 0001750 0001750 00000000000 11703373731 017022 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/ 0000775 0001750 0001750 00000000000 11703373731 017746 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/resources/ 0000775 0001750 0001750 00000000000 11703373731 021760 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/resources/META-INF/ 0000775 0001750 0001750 00000000000 11703373731 023120 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/resources/META-INF/mailcap 0000664 0001750 0001750 00000003345 11412700776 024456 0 ustar brian brian ##
## 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.
##
## $Rev$ $Date$
##
text/plain;; x-java-content-handler=org.apache.geronimo.javamail.handlers.TextPlainHandler
text/html;; x-java-content-handler=org.apache.geronimo.javamail.handlers.TextHtmlHandler
text/xml;; x-java-content-handler=org.apache.geronimo.javamail.handlers.TextXmlHandler
## These are not implemented in the reference implementation because the required support
## is not available on server JVMs.
## image/gif;; x-java-content-handler=org.apache.geronimo.javamail.handlers.ImageGifHandler
## image/jpeg;; x-java-content-handler=org.apache.geronimo.javamail.handlers.ImageJpegHandler
## image/jpg;; x-java-content-handler=org.apache.geronimo.javamail.handlers.ImageJpegHandler
multipart/*;; x-java-content-handler=org.apache.geronimo.javamail.handlers.MultipartHandler
message/rfc822;; x-java-content-handler=org.apache.geronimo.javamail.handlers.RFC822MessageHandler
geronimo-javamail-1.4-provider-1.8.3/src/main/resources/META-INF/javamail.default.address.map 0000664 0001750 0001750 00000002344 10474735322 030457 0 ustar brian brian ##
## 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.
##
##
## $Rev: 437934 $ $Date: 2006-08-28 20:27:42 -0700 (Mon, 28 Aug 2006) $
##
#
# This file configures the default behaviour of JavaMail. DO NOT EDIT.
# Create a new file /META-INF/javamail.address.map and put
# the same format lines in there.
#
# Note that you can't override these defaults, merely add to them.
#
# $Rev: 351866 $ $Date: 2005-12-02 20:12:14 -0500 (Fri, 02 Dec 2005) $
#
rfc822=smtp
news=nntp
geronimo-javamail-1.4-provider-1.8.3/src/main/resources/META-INF/javamail.default.providers 0000664 0001750 0001750 00000005100 11124332750 030253 0 ustar brian brian ##
## 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.
##
##
## $Rev: 437934 $ $Date: 2006-08-28 20:27:42 -0700 (Mon, 28 Aug 2006) $
##
#
# This file configures the default behaviour of JavaMail. DO NOT EDIT.
# Create a new file /META-INF/javamail.providers and put
# the same format lines in there.
#
# Note that you can't override these defaults, merely add to them.
#
# $Rev: 398634 $ $Date: 2006-05-01 12:56:06 -0400 (Mon, 01 May 2006) $
#
protocol=smtp; type=transport; class=org.apache.geronimo.javamail.transport.smtp.SMTPTransport; vendor=Apache Software Foundation; version=1.0
protocol=smtps; type=transport; class=org.apache.geronimo.javamail.transport.smtp.SMTPSTransport; vendor=Apache Software Foundation; version=1.0
protocol=nntp-post; type=transport; class=org.apache.geronimo.javamail.transport.nntp.NNTPTransport; vendor=Apache Software Foundation; version=1.0
protocol=nntp-posts; type=transport; class=org.apache.geronimo.javamail.transport.nntp.NNTPSSLTransport; vendor=Apache Software Foundation; version=1.0
protocol=nntp; type=store; class=org.apache.geronimo.javamail.store.nntp.NNTPStore; vendor=Apache Software Foundation; version=1.0
protocol=nntps; type=store; class=org.apache.geronimo.javamail.store.nntp.NNTPSSLStore; vendor=Apache Software Foundation; version=1.0
protocol=pop3; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3Store; vendor=Apache Software Foundation; version=1.0
protocol=pop3s; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3SSLStore; vendor=Apache Software Foundation; version=1.0
protocol=imap; type=store; class=org.apache.geronimo.javamail.store.imap.IMAPStore; vendor=Apache Software Foundation; version=1.0
protocol=imaps; type=store; class=org.apache.geronimo.javamail.store.imap.IMAPSSLStore; vendor=Apache Software Foundation; version=1.0
geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/ 0000775 0001750 0001750 00000000000 11703373731 023133 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/ 0000775 0001750 0001750 00000000000 11703373731 025150 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPSSLStore geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664 0001750 0001750 00000000156 11345242342 032015 0 ustar brian brian org.apache.geronimo.javamail.store.nntp.NNTPSSLStore # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3Store geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664 0001750 0001750 00000000153 11345242342 032012 0 ustar brian brian org.apache.geronimo.javamail.store.pop3.POP3Store # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000171 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.MultipartHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000160 11345242342 031742 0 ustar brian brian org.apache.geronimo.javamail.handlers.MultipartHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPStore geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664 0001750 0001750 00000000153 11345242342 032012 0 ustar brian brian org.apache.geronimo.javamail.store.nntp.NNTPStore # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPSSSLStore geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664 0001750 0001750 00000000156 11345242342 032015 0 ustar brian brian org.apache.geronimo.javamail.store.imap.IMAPSSLStore # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.RFC822MessageHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000164 11345242342 031746 0 ustar brian brian org.apache.geronimo.javamail.handlers.RFC822MessageHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextHtmlHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000157 11345242342 031750 0 ustar brian brian org.apache.geronimo.javamail.handlers.TextHtmlHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000177 00000000000 011572 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPSSLTransport geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664 0001750 0001750 00000000164 11345242342 032007 0 ustar brian brian org.apache.geronimo.javamail.transport.smtp.SMTPSTransport # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPTransport geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664 0001750 0001750 00000000163 11345242342 032006 0 ustar brian brian org.apache.geronimo.javamail.transport.smtp.SMTPTransport # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPTransport geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664 0001750 0001750 00000000163 11345242342 032006 0 ustar brian brian org.apache.geronimo.javamail.transport.nntp.NNTPTransport # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3SSLStore geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664 0001750 0001750 00000000156 11345242342 032015 0 ustar brian brian org.apache.geronimo.javamail.store.pop3.POP3SSLStore # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPStore geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664 0001750 0001750 00000000153 11345242342 032012 0 ustar brian brian org.apache.geronimo.javamail.store.imap.IMAPStore # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000171 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextPlainHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000160 11345242342 031742 0 ustar brian brian org.apache.geronimo.javamail.handlers.TextPlainHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000171 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageJpegHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000160 11345242342 031742 0 ustar brian brian org.apache.geronimo.javamail.handlers.ImageJpegHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageGifHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000157 11345242342 031750 0 ustar brian brian org.apache.geronimo.javamail.handlers.ImageGifHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextXmlHandler geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664 0001750 0001750 00000000156 11345242342 031747 0 ustar brian brian org.apache.geronimo.javamail.handlers.TextXmlHandler # This is directly mapped back to the same class name
././@LongLink 0000000 0000000 0000000 00000000177 00000000000 011572 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPSSLTransport geronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664 0001750 0001750 00000000166 11345242342 032011 0 ustar brian brian org.apache.geronimo.javamail.transport.nntp.NNTPSSLTransport # This is directly mapped back to the same class name
geronimo-javamail-1.4-provider-1.8.3/src/main/java/ 0000775 0001750 0001750 00000000000 11703373727 020674 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/ 0000775 0001750 0001750 00000000000 11703373727 021463 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/ 0000775 0001750 0001750 00000000000 11703373727 022704 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/ 0000775 0001750 0001750 00000000000 11703373727 024523 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/ 0000775 0001750 0001750 00000000000 11703373731 026302 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/ 0000775 0001750 0001750 00000000000 11703373731 030336 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/ 0000775 0001750 0001750 00000000000 11703373731 031315 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPT0000664 0001750 0001750 00000023603 11033126004 032130 0 ustar brian brian /*
* 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.transport.nntp;
import java.io.PrintStream;
import java.util.ArrayList;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.event.TransportEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.NewsAddress;
import org.apache.geronimo.javamail.util.ProtocolProperties;
/**
* Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
*
There is no way to indicate failure for a given recipient (it's possible
* to have a recipient address rejected). The sun impl throws exceptions even if
* others successful), but maybe we do a different way...
*
* @version $Rev: 673649 $ $Date: 2008-07-03 06:37:56 -0400 (Thu, 03 Jul 2008) $
*/
public class NNTPTransport extends Transport {
/**
* property keys for protocol properties.
*/
protected static final String NNTP_FROM = "from";
protected static final int DEFAULT_NNTP_PORT = 119;
protected static final int DEFAULT_NNTP_SSL_PORT = 563;
// our accessor for protocol properties and the holder of
// protocol-specific information
protected ProtocolProperties props;
// our active connection object (shared code with the NNTPStore).
protected NNTPConnection connection;
/**
* Normal constructor for an NNTPTransport() object. This constructor is
* used to build a transport instance for the "smtp" protocol.
*
* @param session
* The attached session.
* @param name
* An optional URLName object containing target information.
*/
public NNTPTransport(Session session, URLName name) {
this(session, name, "nntp-post", DEFAULT_NNTP_PORT, false);
}
/**
* Common constructor used by the POP3Store and POP3SSLStore classes
* to do common initialization of defaults.
*
* @param session
* The host session instance.
* @param name
* The URLName of the target.
* @param protocol
* The protocol type ("pop3"). This helps us in
* retrieving protocol-specific session properties.
* @param defaultPort
* The default port used by this protocol. For pop3, this will
* be 110. The default for pop3 with ssl is 995.
* @param sslConnection
* Indicates whether an SSL connection should be used to initial
* contact the server. This is different from the STARTTLS
* support, which switches the connection to SSL after the
* initial startup.
*/
protected NNTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
// create the protocol property holder. This gives an abstraction over the different
// flavors of the protocol.
props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
// the connection manages connection for the transport
connection = new NNTPConnection(props);
}
/**
* Do the protocol connection for an NNTP transport. This handles server
* authentication, if possible. Returns false if unable to connect to the
* server.
*
* @param host
* The target host name.
* @param port
* The server port number.
* @param user
* The authentication user (if any).
* @param password
* The server password. Might not be sent directly if more
* sophisticated authentication is used.
*
* @return true if we were able to connect to the server properly, false for
* any failures.
* @exception MessagingException
*/
protected boolean protocolConnect(String host, int port, String username, String password)
throws MessagingException {
// the connection pool handles all of the details here.
return connection.protocolConnect(host, port, username, password);
}
/**
* Send a message to multiple addressees.
*
* @param message
* The message we're sending.
* @param addresses
* An array of addresses to send to.
*
* @exception MessagingException
*/
public void sendMessage(Message message, Address[] addresses) throws MessagingException {
if (!isConnected()) {
throw new IllegalStateException("Not connected");
}
if (!connection.isPostingAllowed()) {
throw new MessagingException("Posting disabled for host server");
}
// don't bother me w/ null messages or no addreses
if (message == null) {
throw new MessagingException("Null message");
}
// NNTP only handles instances of MimeMessage, not the more general
// message case.
if (!(message instanceof MimeMessage)) {
throw new MessagingException("NNTP can only send MimeMessages");
}
// need to sort the from value out from a variety of sources.
InternetAddress from = null;
Address[] fromAddresses = message.getFrom();
// If the message has a From address set, we just use that. Otherwise,
// we set a From using
// the property version, if available.
if (fromAddresses == null || fromAddresses.length == 0) {
// the from value can be set explicitly as a property
String defaultFrom = props.getProperty(NNTP_FROM);
if (defaultFrom == null) {
message.setFrom(new InternetAddress(defaultFrom));
}
}
// we must have a message list.
if (addresses == null || addresses.length == 0) {
throw new MessagingException("Null or empty address array");
}
boolean haveGroup = false;
// enforce the requirement that all of the targets are NewsAddress
// instances.
for (int i = 0; i < addresses.length; i++) {
if (!(addresses[i] instanceof NewsAddress)) {
throw new MessagingException("Illegal NewsAddress " + addresses[i]);
}
}
// event notifcation requires we send lists of successes and failures
// broken down by category.
// The categories are:
//
// 1) addresses successfully processed.
// 2) addresses deemed valid, but had a processing failure that
// prevented sending.
// 3) addressed deemed invalid (basically all other processing
// failures).
ArrayList sentAddresses = new ArrayList();
ArrayList unsentAddresses = new ArrayList();
ArrayList invalidAddresses = new ArrayList();
boolean sendFailure = false;
// now try to post this message to the different news groups.
for (int i = 0; i < addresses.length; i++) {
try {
// select the target news group
NNTPReply reply = connection.selectGroup(((NewsAddress) addresses[i]).getNewsgroup());
if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
invalidAddresses.add(addresses[i]);
sendFailure = true;
} else {
// send data
connection.sendPost(message);
sentAddresses.add(addresses[i]);
}
} catch (MessagingException e) {
unsentAddresses.add(addresses[i]);
sendFailure = true;
}
}
// create our lists for notification and exception reporting from this
// point on.
Address[] sent = (Address[]) sentAddresses.toArray(new Address[0]);
Address[] unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
Address[] invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
if (sendFailure) {
// did we deliver anything at all?
if (sent.length == 0) {
// notify of the error.
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
} else {
// notify that we delivered at least part of this
notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
}
throw new MessagingException("Error posting NNTP message");
}
// notify our listeners of successful delivery.
notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
}
/**
* Close the connection. On completion, we'll be disconnected from the
* server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// This is done to ensure proper event notification.
super.close();
// NB: We reuse the connection if asked to reconnect
connection.close();
}
}
././@LongLink 0000000 0000000 0000000 00000000172 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/StringListInputStream.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/Strin0000664 0001750 0001750 00000006425 10474735322 032350 0 ustar brian brian /*
* 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.transport.nntp;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* @version $Rev: 437941 $ $Date: 2006-08-28 23:56:02 -0400 (Mon, 28 Aug 2006) $
*/
public class StringListInputStream extends InputStream {
// the list of lines we're reading from
protected List lines;
// the next line to process.
protected int nextLine = 0;
// current buffer of bytes to read from
byte[] buffer;
// current offset within the buffer;
int offset;
// indicator that we've left off at a split between the CR and LF of a line
// break.
boolean atLineBreak = false;
public StringListInputStream(List lines) throws IOException {
this.lines = lines;
nextLine = 0;
buffer = null;
offset = 0;
atLineBreak = false;
// if we have at least one line in the list, get the bytes now.
if (lines.size() > 0) {
nextBuffer();
}
}
/**
* Just override the single byte read version, which handles all of the
* lineend markers correctly.
*
* @return The next byte from the stream or -1 if we've hit the EOF.
*/
public int read() throws IOException {
// leave off at the split between a line?
if (atLineBreak) {
// flip this off and return the second line end character. Also step
// to the next line.
atLineBreak = false;
nextBuffer();
return '\n';
}
// gone past the end? Got an EOF
if (buffer == null) {
return -1;
}
// reach the end of the line?
if (offset >= buffer.length) {
// we're now working on a virtual linebreak
atLineBreak = true;
return '\r';
}
// just return the next byte
return buffer[offset++];
}
/**
* Step to the next buffer of string data.
*
* @exception IOException
*/
protected void nextBuffer() throws IOException {
// give an eof check.
if (nextLine >= lines.size()) {
buffer = null;
} else {
try {
String next = (String) lines.get(nextLine++);
buffer = next.getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new IOException("Invalid string encoding");
}
}
offset = 0;
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPC0000664 0001750 0001750 00000062032 11404405474 032123 0 ustar brian brian /*
* 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.transport.nntp;
import java.io.BufferedReader;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
import org.apache.geronimo.javamail.util.MailConnection;
import org.apache.geronimo.javamail.util.MIMEOutputStream;
import org.apache.geronimo.javamail.util.ProtocolProperties;
import org.apache.geronimo.mail.util.Base64;
import org.apache.geronimo.mail.util.SessionUtil;
/**
* Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
*
* @version $Rev: 953638 $ $Date: 2010-06-11 06:09:00 -0400 (Fri, 11 Jun 2010) $
*/
public class NNTPConnection extends MailConnection {
/**
* constants for EOL termination
*/
protected static final char CR = '\r';
protected static final char LF = '\n';
/**
* property keys for protocol properties.
*/
protected static final int DEFAULT_NNTP_PORT = 119;
// does the server support posting?
protected boolean postingAllowed = true;
// different authentication mechanisms
protected boolean authInfoUserAllowed = false;
protected boolean authInfoSaslAllowed = false;
// the last response line received from the server.
protected NNTPReply lastServerResponse = null;
// the welcome string from the server.
protected String welcomeString = null;
// input reader wrapped around the socket input stream
protected BufferedReader reader;
// output writer wrapped around the socket output stream.
protected PrintWriter writer;
/**
* Normal constructor for an NNTPConnection() object.
*
* @param props The property bundle for this protocol instance.
*/
public NNTPConnection(ProtocolProperties props) {
super(props);
}
/**
* Connect to the server and do the initial handshaking.
*
* @param host The target host name.
* @param port The target port
* @param username The connection username (can be null)
* @param password The authentication password (can be null).
*
* @return true if we were able to obtain a connection and
* authenticate.
* @exception MessagingException
*/
public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
super.protocolConnect(host, port, username, password);
// create socket and connect to server.
getConnection();
// receive welcoming message
getWelcome();
return true;
}
/**
* Create a transport connection object and connect it to the
* target server.
*
* @exception MessagingException
*/
protected void getConnection() throws MessagingException
{
try {
// do all of the non-protocol specific set up. This will get our socket established
// and ready use.
super.getConnection();
} catch (IOException e) {
throw new MessagingException("Unable to obtain a connection to the NNTP server", e);
}
// The NNTP protocol is inherently a string-based protocol, so we get
// string readers/writers for the connection streams. Note that we explicitly
// set the encoding to ensure that an inappropriate native encoding is not picked up.
try {
reader = new BufferedReader(new InputStreamReader(inputStream, "ISO8859-1"));
writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream), "ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
/**
* Close the connection. On completion, we'll be disconnected from the
* server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// if we're already closed, get outta here.
if (socket == null) {
return;
}
try {
// say goodbye
sendQuit();
} finally {
// and close up the connection. We do this in a finally block to
// make sure the connection
// is shut down even if quit gets an error.
closeServerConnection();
// get rid of our response processor too.
reader = null;
writer = null;
}
}
public String toString() {
return "NNTPConnection host: " + serverHost + " port: " + serverPort;
}
/**
* Get the servers welcome blob from the wire....
*/
public void getWelcome() throws MessagingException {
NNTPReply line = getReply();
//
if (line.isError()) {
throw new MessagingException("Error connecting to news server: " + line.getMessage());
}
// remember we can post.
if (line.getCode() == NNTPReply.POSTING_ALLOWED) {
postingAllowed = true;
} else {
postingAllowed = false;
}
// the NNTP store will want to use the welcome string, so save it.
welcomeString = line.getMessage();
// find out what extensions this server supports.
getExtensions();
}
/**
* Sends the QUIT message and receieves the response
*/
public void sendQuit() throws MessagingException {
sendLine("QUIT");
}
/**
* Tell the server to switch to a named group.
*
* @param name
* The name of the target group.
*
* @return The server response to the GROUP command.
*/
public NNTPReply selectGroup(String name) throws MessagingException {
// send the GROUP command
return sendCommand("GROUP " + name);
}
/**
* Ask the server what extensions it supports.
*
* @return True if the command was accepted ok, false for any errors.
* @exception MessagingException
*/
protected void getExtensions() throws MessagingException {
NNTPReply reply = sendCommand("LIST EXTENSIONS", NNTPReply.EXTENSIONS_SUPPORTED);
// we get a 202 code back. The first line is just a greeting, and
// extensions are delivered as data
// lines terminated with a "." line.
if (reply.getCode() != NNTPReply.EXTENSIONS_SUPPORTED) {
return;
}
// get a fresh extension mapping table.
capabilities = new HashMap();
authentications = new ArrayList();
// get the extension data lines.
List extensions = reply.getData();
// process all of the continuation lines
for (int i = 0; i < extensions.size(); i++) {
// go process the extention
processExtension((String) extensions.get(i));
}
}
/**
* Process an extension string passed back as the LIST EXTENSIONS response.
*
* @param extension
* The string value of the extension (which will be of the form
* "NAME arguments").
*/
protected void processExtension(String extension) {
String extensionName = extension.toUpperCase();
String argument = "";
int delimiter = extension.indexOf(' ');
// if we have a keyword with arguments, parse them out and add to the
// argument map.
if (delimiter != -1) {
extensionName = extension.substring(0, delimiter).toUpperCase();
argument = extension.substring(delimiter + 1);
}
// add this to the map so it can be tested later.
capabilities.put(extensionName, argument);
// we need to determine which authentication mechanisms are supported here
if (extensionName.equals("AUTHINFO")) {
StringTokenizer tokenizer = new StringTokenizer(argument);
while (tokenizer.hasMoreTokens()) {
// we only know how to do USER or SASL
String mechanism = tokenizer.nextToken().toUpperCase();
if (mechanism.equals("SASL")) {
authInfoSaslAllowed = true;
}
else if (mechanism.equals("USER")) {
authInfoUserAllowed = true;
}
}
}
// special case for some older servers.
else if (extensionName.equals("SASL")) {
// The security mechanisms are blank delimited tokens.
StringTokenizer tokenizer = new StringTokenizer(argument);
while (tokenizer.hasMoreTokens()) {
String mechanism = tokenizer.nextToken().toUpperCase();
authentications.add(mechanism);
}
}
}
/**
* Retrieve any argument information associated with a extension reported
* back by the server on the EHLO command.
*
* @param name
* The name of the target server extension.
*
* @return Any argument passed on a server extension. Returns null if the
* extension did not include an argument or the extension was not
* supported.
*/
public String extensionParameter(String name) {
if (capabilities != null) {
return (String) capabilities.get(name);
}
return null;
}
/**
* Tests whether the target server supports a named extension.
*
* @param name
* The target extension name.
*
* @return true if the target server reported on the EHLO command that is
* supports the targer server, false if the extension was not
* supported.
*/
public boolean supportsExtension(String name) {
// this only returns null if we don't have this extension
return extensionParameter(name) != null;
}
/**
* Sends the data in the message down the socket. This presumes the server
* is in the right place and ready for getting the DATA message and the data
* right place in the sequence
*/
public synchronized void sendPost(Message msg) throws MessagingException {
// send the POST command
NNTPReply line = sendCommand("POST");
if (line.getCode() != NNTPReply.SEND_ARTICLE) {
throw new MessagingException("Server rejected POST command: " + line);
}
// we've received permission to send the data, so ask the message to
// write itself out.
try {
// the data content has two requirements we need to meet by
// filtering the
// output stream. Requirement 1 is to conicalize any line breaks.
// All line
// breaks will be transformed into properly formed CRLF sequences.
//
// Requirement 2 is to perform byte-stuff for any line that begins
// with a "."
// so that data is not confused with the end-of-data marker (a
// "\r\n.\r\n" sequence.
//
// The MIME output stream performs those two functions on behalf of
// the content
// writer.
MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
msg.writeTo(mimeOut);
// now to finish, we send a CRLF sequence, followed by a ".".
mimeOut.writeSMTPTerminator();
// and flush the data to send it along
mimeOut.flush();
} catch (IOException e) {
throw new MessagingException("I/O error posting message", e);
} catch (MessagingException e) {
throw new MessagingException("Exception posting message", e);
}
// use a longer time out here to give the server time to process the
// data.
line = new NNTPReply(receiveLine());
if (line.getCode() != NNTPReply.POSTED_OK) {
throw new MessagingException("Server rejected POST command: " + line);
}
}
/**
* Issue a command and retrieve the response. If the given success indicator
* is received, the command is returning a longer response, terminated by a
* "crlf.crlf" sequence. These lines are attached to the reply.
*
* @param command
* The command to issue.
* @param success
* The command reply that indicates additional data should be
* retrieved.
*
* @return The command reply.
*/
public synchronized NNTPReply sendCommand(String command, int success) throws MessagingException {
NNTPReply reply = sendCommand(command);
if (reply.getCode() == success) {
reply.retrieveData(reader);
}
return reply;
}
/**
* Send a command to the server, returning the first response line back as a
* reply.
*
* @param data
* The data to send.
*
* @return A reply object with the reply line.
* @exception MessagingException
*/
public NNTPReply sendCommand(String data) throws MessagingException {
sendLine(data);
NNTPReply reply = getReply();
// did the server just inform us we need to authenticate? The spec
// allows this
// response to be sent at any time, so we need to try to authenticate
// and then retry the command.
if (reply.getCode() == NNTPReply.AUTHINFO_REQUIRED || reply.getCode() == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
debugOut("Authentication required received from server.");
// authenticate with the server, if necessary
processAuthentication(reply.getCode());
// if we've safely authenticated, we can reissue the command and
// process the response.
sendLine(data);
reply = getReply();
}
return reply;
}
/**
* Send a command to the server, returning the first response line back as a
* reply.
*
* @param data
* The data to send.
*
* @return A reply object with the reply line.
* @exception MessagingException
*/
public NNTPReply sendAuthCommand(String data) throws MessagingException {
sendLine(data);
return getReply();
}
/**
* Sends a message down the socket and terminates with the appropriate CRLF
*/
public void sendLine(String data) throws MessagingException {
if (socket == null || !socket.isConnected()) {
throw new MessagingException("no connection");
}
try {
outputStream.write(data.getBytes("ISO8859-1"));
outputStream.write(CR);
outputStream.write(LF);
outputStream.flush();
} catch (IOException e) {
throw new MessagingException(e.toString());
}
}
/**
* Get a reply line for an NNTP command.
*
* @return An NNTP reply object from the stream.
*/
public NNTPReply getReply() throws MessagingException {
lastServerResponse = new NNTPReply(receiveLine());
return lastServerResponse;
}
/**
* Retrieve the last response received from the NNTP server.
*
* @return The raw response string (including the error code) returned from
* the NNTP server.
*/
public String getLastServerResponse() {
if (lastServerResponse == null) {
return "";
}
return lastServerResponse.getReply();
}
/**
* Receives one line from the server. A line is a sequence of bytes
* terminated by a CRLF
*
* @return the line from the server as String
*/
public String receiveLine() throws MessagingException {
if (socket == null || !socket.isConnected()) {
throw new MessagingException("no connection");
}
try {
String line = reader.readLine();
if (line == null) {
throw new MessagingException("Unexpected end of stream");
}
return line;
} catch (IOException e) {
throw new MessagingException("Error reading from server", e);
}
}
/**
* Authenticate with the server, if necessary (or possible).
*/
protected void processAuthentication(int request) throws MessagingException {
// we need to authenticate, but we don't have userid/password
// information...fail this
// immediately.
if (username == null || password == null) {
throw new MessagingException("Server requires user authentication");
}
if (request == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
processAuthinfoSimple();
} else {
if (!processSaslAuthentication()) {
processAuthinfoUser();
}
}
}
/**
* Process an AUTHINFO SIMPLE command. Not widely used, but if the server
* asks for it, we can respond.
*
* @exception MessagingException
*/
protected void processAuthinfoSimple() throws MessagingException {
NNTPReply reply = sendAuthCommand("AUTHINFO SIMPLE");
if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
}
reply = sendAuthCommand(username + " " + password);
if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
}
}
/**
* Process SASL-type authentication.
*
* @return Returns true if the server support a SASL authentication mechanism and
* accepted reponse challenges.
* @exception MessagingException
*/
protected boolean processSaslAuthentication() throws MessagingException {
// only do this if permitted
if (!authInfoSaslAllowed) {
return false;
}
// if unable to get an appropriate authenticator, just fail it.
ClientAuthenticator authenticator = getSaslAuthenticator();
if (authenticator == null) {
throw new MessagingException("Unable to obtain SASL authenticator");
}
// go process the login.
return processLogin(authenticator);
}
/**
* Attempt to retrieve a SASL authenticator for this
* protocol.
*
* @return A SASL authenticator, or null if a suitable one
* was not located.
*/
protected ClientAuthenticator getSaslAuthenticator() {
return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
}
/**
* Process a login using the provided authenticator object.
*
* NB: This method is synchronized because we have a multi-step process going on
* here. No other commands should be sent to the server until we complete.
*
* @return Returns true if the server support a SASL authentication mechanism and
* accepted reponse challenges.
* @exception MessagingException
*/
protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
// if the authenticator has some initial data, we compose a command
// containing the initial data.
if (authenticator.hasInitialResponse()) {
StringBuffer command = new StringBuffer();
// the auth command initiates the handshaking.
command.append("AUTHINFO SASL ");
// and tell the server which mechanism we're using.
command.append(authenticator.getMechanismName());
command.append(" ");
// and append the response data
try {
command.append(new String(Base64.encode(authenticator.evaluateChallenge(null)), "US-ASCII"));
} catch (UnsupportedEncodingException e) {
}
// send the command now
sendLine(command.toString());
}
// we just send an auth command with the command type.
else {
StringBuffer command = new StringBuffer();
// the auth command initiates the handshaking.
command.append("AUTHINFO SASL");
// and tell the server which mechanism we're using.
command.append(authenticator.getMechanismName());
// send the command now
sendLine(command.toString());
}
// now process the challenge sequence. We get a 235 response back when
// the server accepts the
// authentication, and a 334 indicates we have an additional challenge.
while (true) {
// get the next line, and if it is an error response, return now.
NNTPReply line = getReply();
// if we get a completion return, we've passed muster, so give an
// authentication response.
if (line.getCode() == NNTPReply.AUTHINFO_ACCEPTED || line.getCode() == NNTPReply.AUTHINFO_ACCEPTED_FINAL) {
debugOut("Successful SMTP authentication");
return true;
}
// we have an additional challenge to process.
else if (line.getCode() == NNTPReply.AUTHINFO_CHALLENGE) {
// Does the authenticator think it is finished? We can't answer
// an additional challenge,
// so fail this.
if (authenticator.isComplete()) {
debugOut("Extra authentication challenge " + line);
return false;
}
// we're passed back a challenge value, Base64 encoded.
try {
byte[] challenge = Base64.decode(line.getMessage().getBytes("ISO8859-1"));
// have the authenticator evaluate and send back the encoded
// response.
sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII"));
} catch (UnsupportedEncodingException e) {
}
}
// completion or challenge are the only responses we know how to
// handle. Anything else must
// be a failure.
else {
debugOut("Authentication failure " + line);
return false;
}
}
}
/**
* Process an AUTHINFO USER command. Most common form of NNTP
* authentication.
*
* @exception MessagingException
*/
protected void processAuthinfoUser() throws MessagingException {
// only do this if allowed by the server
if (!authInfoUserAllowed) {
return;
}
NNTPReply reply = sendAuthCommand("AUTHINFO USER " + username);
// accepted without a password (uncommon, but allowed), we're done
if (reply.getCode() == NNTPReply.AUTHINFO_ACCEPTED) {
return;
}
// the only other non-error response is continue.
if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
throw new MessagingException("Error authenticating with server using AUTHINFO USER: " + reply);
}
// now send the password. We expect an accepted response.
reply = sendAuthCommand("AUTHINFO PASS " + password);
if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
}
}
/**
* Indicate whether posting is allowed for a given server.
*
* @return True if the server allows posting, false if the server is
* read-only.
*/
public boolean isPostingAllowed() {
return postingAllowed;
}
/**
* Retrieve the welcome string sent back from the server.
*
* @return The server provided welcome string.
*/
public String getWelcomeString() {
return welcomeString;
}
}
././@LongLink 0000000 0000000 0000000 00000000165 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPSSLTransport.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPS0000664 0001750 0001750 00000002223 11032465542 032136 0 ustar brian brian /*
* 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.transport.nntp;
import javax.mail.Session;
import javax.mail.URLName;
public class NNTPSSLTransport extends NNTPTransport {
/**
* @param session
* @param name
*/
public NNTPSSLTransport(Session session, URLName name) {
super(session, name, "nntp-posts", DEFAULT_NNTP_SSL_PORT, true);
}
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPR0000664 0001750 0001750 00000015512 11032465542 032142 0 ustar brian brian /*
* 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.transport.nntp;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent a reply from a NNTP server
*
* @version $Rev: 673152 $ $Date: 2008-07-01 13:37:38 -0400 (Tue, 01 Jul 2008) $
*/
public class NNTPReply {
// general server responses
public static final int CAPABILITY_LIST = 101;
public static final int POSTING_ALLOWED = 200;
public static final int NO_POSTING_ALLOWED = 201;
public static final int EXTENSIONS_SUPPORTED = 202;
public static final int SERVICE_DISCONTINUED = 400;
public static final int COMMAND_NOT_RECOGNIZED = 500;
public static final int COMMAND_SYNTAX_ERROR = 501;
public static final int PERMISSION_DENIED = 502;
public static final int PROGRAM_FAULT = 503;
// article responses
public static final int ARTICLE_FOLLOWS = 220;
public static final int HEAD_FOLLOWS = 221;
public static final int BODY_FOLLOWS = 222;
public static final int REQUEST_TEXT_SEPARATELY = 223;
public static final int OVERVIEW_FOLLOWS = 224;
public static final int NEW_ARTICLES_FOLLOWS = 230;
public static final int NEW_GROUPS_FOLLOWS = 231;
public static final int ARTICLE_TRANSFERRED = 235;
public static final int NO_NEWSGROUP_SELECTED = 412;
public static final int NO_ARTICLE_SELECTED = 420;
public static final int NO_ARTICLE_NUMBER = 423;
public static final int NO_ARTICLE_FOUND = 430;
// group responses
public static final int GROUP_SELECTED = 211;
public static final int NO_SUCH_NEWSGROUP = 411;
// post responses
public static final int POSTED_OK = 240;
public static final int SEND_ARTICLE = 340;
public static final int POSTING_NOT_ALLOWED = 440;
public static final int POSTING_FAILED = 441;
// quit responses
public static final int CLOSING_CONNECTION = 205;
// authentication responses
public static final int AUTHINFO_ACCEPTED = 250;
public static final int AUTHINFO_ACCEPTED_FINAL = 251;
public static final int AUTHINFO_CONTINUE = 350;
public static final int AUTHINFO_CHALLENGE = 350;
public static final int AUTHINFO_SIMPLE_REJECTED = 402;
public static final int AUTHENTICATION_ACCEPTED = 281;
public static final int MORE_AUTHENTICATION_REQUIRED = 381;
public static final int AUTHINFO_REQUIRED = 480;
public static final int AUTHINFO_SIMPLE_REQUIRED = 450;
public static final int AUTHENTICATION_REJECTED = 482;
// list active reponses
public static final int LIST_FOLLOWS = 215;
// The original reply string
private final String reply;
// returned message code
private final int code;
// the returned message text
private final String message;
// data associated with a long response command.
private ArrayList data;
NNTPReply(String s) throws MessagingException {
// save the reply
reply = s;
// In a normal response, the first 3 must be the return code. However,
// the response back from a QUIT command is frequently a null string.
// Therefore, if the result is
// too short, just default the code to -1 and use the entire text for
// the message.
if (s == null || s.length() < 3) {
code = -1;
message = s;
return;
}
try {
code = Integer.parseInt(s.substring(0, 3));
// message should be separated by a space OR a continuation
// character if this is a
// multi-line response.
if (s.length() > 4) {
message = s.substring(4);
} else {
message = "";
}
} catch (NumberFormatException e) {
throw new MessagingException("error in parsing reply code", e);
}
}
/**
* Retrieve data associated with a multi-line reponse from a server stream.
*
* @param in
* The reader that's the source of the additional lines.
*
* @exception IOException
*/
public void retrieveData(BufferedReader in) throws MessagingException {
try {
data = new ArrayList();
String line = in.readLine();
// read until the end of file or until we see the end of data
// marker.
while (line != null && !line.equals(".")) {
// this line is not the terminator, but it may have been byte
// stuffed. If it starts with
// '.', throw away the leading one.
if (line.startsWith(".")) {
line = line.substring(1);
}
// just add the line to the list
data.add(line);
line = in.readLine();
}
} catch (IOException e) {
throw new MessagingException("Error reading message reply", e);
}
}
/**
* Retrieve the long-command data from this response.
*
* @return The data list. Returns null if there is no associated data.
*/
public List getData() {
return data;
}
/**
* Return the code value associated with the reply.
*
* @return The integer code associated with the reply.
*/
public int getCode() {
return this.code;
}
/**
* Get the message text associated with the reply.
*
* @return The string value of the message from the reply.
*/
public String getMessage() {
return this.message;
}
/**
* Retrieve the raw reply string for the reponse.
*
* @return The original reply string from the server.
*/
public String getReply() {
return reply;
}
/**
* Indicates if reply is an error condition
*/
boolean isError() {
// error codes are all above 400
return code >= 400;
}
public String toString() {
return "CODE = " + getCode() + " : MSG = " + getMessage();
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/ 0000775 0001750 0001750 00000000000 11703373731 031321 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPR0000664 0001750 0001750 00000013373 11033126004 032141 0 ustar brian brian /*
* 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.transport.smtp;
import java.util.ArrayList;
import java.util.List;
/**
* Util class to represent a reply from a SMTP server
*
* @version $Rev: 673649 $ $Date: 2008-07-03 06:37:56 -0400 (Thu, 03 Jul 2008) $
*/
class SMTPReply {
// SMTP reply codes
public static final int SERVICE_READY = 220;
public static final int SERVICE_CLOSING = 221;
public static final int AUTHENTICATION_COMPLETE = 235;
public static final int COMMAND_ACCEPTED = 250;
public static final int ADDRESS_NOT_LOCAL = 251;
public static final int AUTHENTICATION_CHALLENGE = 334;
public static final int START_MAIL_INPUT = 354;
public static final int SERVICE_NOT_AVAILABLE = 421;
public static final int MAILBOX_BUSY = 450;
public static final int PROCESSING_ERROR = 451;
public static final int INSUFFICIENT_STORAGE = 452;
public static final int COMMAND_SYNTAX_ERROR = 500;
public static final int PARAMETER_SYNTAX_ERROR = 501;
public static final int COMMAND_NOT_IMPLEMENTED = 502;
public static final int INVALID_COMMAND_SEQUENCE = 503;
public static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
public static final int MAILBOX_NOT_FOUND = 550;
public static final int USER_NOT_LOCAL = 551;
public static final int MAILBOX_FULL = 552;
public static final int INVALID_MAILBOX = 553;
public static final int TRANSACTION_FAILED = 553;
// The original reply string
private final String reply;
// returned message code
private final int code;
// the returned message text
private final String message;
// additional returned lines from a continued response
private List lines;
// indicates that this is a continuation response
private boolean continued;
SMTPReply(String s) throws MalformedSMTPReplyException {
// save the reply
reply = s;
// In a normal response, the first 3 must be the return code. However,
// the response back from a QUIT command is frequently a null string.
// Therefore, if the result is
// too short, just default the code to -1 and use the entire text for
// the message.
if (s == null || s.length() < 3) {
code = -1;
message = s;
return;
}
try {
continued = false;
code = Integer.parseInt(s.substring(0, 3));
// message should be separated by a space OR a continuation
// character if this is a
// multi-line response.
if (s.length() > 4) {
//
if (s.charAt(3) == '-') {
continued = true;
}
message = s.substring(4);
} else {
message = "";
}
} catch (NumberFormatException e) {
throw new MalformedSMTPReplyException("error in parsing code", e);
}
}
/**
* Add a line to a continued response. This will
* update the continued status if the end of the
* response is reached.
*
* @param line The line to add.
*/
public void addLine(String line) {
if (lines == null) {
lines = new ArrayList();
lines.add(message);
}
// mark if we're still continued
continued = line.charAt(3) == '-';
// add the line to the list
lines.add(line.substring(4));
}
/**
* Get the list of all of the lines associated with
* this reply.
*
* @return A List containing all lines associated with this
* reply.
*/
public List getLines() {
if (lines == null) {
lines = new ArrayList();
lines.add(message);
}
return lines;
}
/**
* Return the code value associated with the reply.
*
* @return The integer code associated with the reply.
*/
public int getCode() {
return this.code;
}
/**
* Get the message text associated with the reply.
*
* @return The string value of the message from the reply.
*/
public String getMessage() {
return this.message;
}
/**
* Retrieve the raw reply string for the reponse.
*
* @return The original reply string from the server.
*/
public String getReply() {
return reply;
}
/**
* Indicates if reply is an error condition
*/
boolean isError() {
// error codes are all above 400
return code >= 400;
}
/**
* Indicates whether this response is flagged as part of a multiple line
* response.
*
* @return true if the response has multiple lines, false if this is the
* last line of the response.
*/
public boolean isContinued() {
return continued;
}
public String toString() {
return "CODE = " + getCode() + " : MSG = " + getMessage();
}
}
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPS0000664 0001750 0001750 00000004412 10474735322 032155 0 ustar brian brian /*
* 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.transport.smtp;
import javax.mail.Address;
import javax.mail.SendFailedException;
public class SMTPSendFailedException extends SendFailedException {
// the failing command
protected String cmd;
// the error code for the failure
protected int rc;
/**
* Constructor for an SMTPSendFaileException.
*
* @param cmd
* The failing command string.
* @param rc
* The error code for the failing command.
* @param err
* An error message for the exception.
* @param ex
* Any associated nested exception.
* @param vs
* An array of valid, sent addresses.
* @param vus
* An array of addresses that were valid, but were unsent.
* @param inv
* An array of addresses deemed invalid.
*/
SMTPSendFailedException(java.lang.String cmd, int rc, java.lang.String err, java.lang.Exception ex, Address[] vs,
Address[] vus, Address[] inv) {
super(err, ex, vs, vus, inv);
this.cmd = cmd;
this.rc = rc;
}
/**
* Get the failing command string for the exception.
*
* @return The string value of the failing command.
*/
public String getCommand() {
return cmd;
}
/**
* The failing command return code.
*
* @return The failure return code.
*/
public int getReturnCode() {
return rc;
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPT0000664 0001750 0001750 00000060511 11441725756 032165 0 ustar brian brian /*
* 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.transport.smtp;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.event.TransportEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimePart;
import org.apache.geronimo.javamail.util.ProtocolProperties;
import org.apache.geronimo.javamail.transport.smtp.SMTPConnection.SendStatus;
/**
* Simple implementation of SMTP transport. Just does plain RFC821-ish delivery.
* Supported properties :
*
* - mail.host : to set the server to deliver to. Default = localhost
* - mail.smtp.port : to set the port. Default = 25
* - mail.smtp.locahost : name to use for HELO/EHLO - default getHostName()
*
* There is no way to indicate failure for a given recipient (it's possible
* to have a recipient address rejected). The sun impl throws exceptions even if
* others successful), but maybe we do a different way... TODO : lots.
* ESMTP, user/pass, indicate failure, etc...
*
* @version $Rev: 995094 $ $Date: 2010-09-08 11:29:50 -0400 (Wed, 08 Sep 2010) $
*/
public class SMTPTransport extends Transport {
/**
* property keys for protocol properties. The actual property name will be
* appended with "mail." + protocol + ".", where the protocol is either
* "smtp" or "smtps".
*/
protected static final String MAIL_SMTP_DSN_NOTIFY = "dsn.notify";
protected static final String MAIL_SMTP_SENDPARTIAL = "sendpartial";
protected static final String MAIL_SMTP_EXTENSION = "mailextension";
protected static final String DEFAULT_MAIL_HOST = "localhost";
protected static final int DEFAULT_MAIL_SMTP_PORT = 25;
protected static final int DEFAULT_MAIL_SMTPS_PORT = 465;
// do we use SSL for our initial connection?
protected boolean sslConnection = false;
// our accessor for protocol properties and the holder of
// protocol-specific information
protected ProtocolProperties props;
// our active connection object
protected SMTPConnection connection;
// the last response line received from the server.
protected SMTPReply lastServerResponse = null;
/**
* Normal constructor for an SMTPTransport() object. This constructor is
* used to build a transport instance for the "smtp" protocol.
*
* @param session
* The attached session.
* @param name
* An optional URLName object containing target information.
*/
public SMTPTransport(Session session, URLName name) {
this(session, name, "smtp", DEFAULT_MAIL_SMTP_PORT, false);
}
/**
* Common constructor used by the SMTPTransport and SMTPSTransport classes
* to do common initialization of defaults.
*
* @param session
* The host session instance.
* @param name
* The URLName of the target.
* @param protocol
* The protocol type (either "smtp" or "smtps". This helps us in
* retrieving protocol-specific session properties.
* @param defaultPort
* The default port used by this protocol. For "smtp", this will
* be 25. The default for "smtps" is 465.
* @param sslConnection
* Indicates whether an SSL connection should be used to initial
* contact the server. This is different from the STARTTLS
* support, which switches the connection to SSL after the
* initial startup.
*/
protected SMTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
// create the protocol property holder. This gives an abstraction over the different
// flavors of the protocol.
props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
// the connection manages connection for the transport
connection = new SMTPConnection(props);
}
/**
* Connect to a server using an already created socket. This connection is
* just like any other connection, except we will not create a new socket.
*
* @param socket
* The socket connection to use.
*/
public void connect(Socket socket) throws MessagingException {
connection.connect(socket);
super.connect();
}
/**
* Do the protocol connection for an SMTP transport. This handles server
* authentication, if possible. Returns false if unable to connect to the
* server.
*
* @param host
* The target host name.
* @param port
* The server port number.
* @param user
* The authentication user (if any).
* @param password
* The server password. Might not be sent directly if more
* sophisticated authentication is used.
*
* @return true if we were able to connect to the server properly, false for
* any failures.
* @exception MessagingException
*/
protected boolean protocolConnect(String host, int port, String username, String password)
throws MessagingException {
// the connection pool handles all of the details here.
return connection.protocolConnect(host, port, username, password);
}
/**
* Send a message to multiple addressees.
*
* @param message
* The message we're sending.
* @param addresses
* An array of addresses to send to.
*
* @exception MessagingException
*/
public void sendMessage(Message message, Address[] addresses) throws MessagingException {
if (!isConnected()) {
throw new IllegalStateException("Not connected");
}
// don't bother me w/ null messages or no addreses
if (message == null) {
throw new MessagingException("Null message");
}
// SMTP only handles instances of MimeMessage, not the more general
// message case.
if (!(message instanceof MimeMessage)) {
throw new MessagingException("SMTP can only send MimeMessages");
}
// we must have a message list.
if (addresses == null || addresses.length == 0) {
throw new MessagingException("Null or empty address array");
}
boolean reportSuccess = getReportSuccess();
// now see how we're configured for this send operation.
boolean partialSends = false;
// this can be attached directly to the message.
if (message instanceof SMTPMessage) {
partialSends = ((SMTPMessage) message).getSendPartial();
}
// if still false on the message object, check for a property
// version also
if (!partialSends) {
partialSends = props.getBooleanProperty(MAIL_SMTP_SENDPARTIAL, false);
}
boolean haveGroup = false;
// enforce the requirement that all of the targets are InternetAddress
// instances.
for (int i = 0; i < addresses.length; i++) {
if (addresses[i] instanceof InternetAddress) {
// and while we're here, see if we have a groups in the address
// list. If we do, then
// we're going to need to expand these before sending.
if (((InternetAddress) addresses[i]).isGroup()) {
haveGroup = true;
}
} else {
throw new MessagingException("Illegal InternetAddress " + addresses[i]);
}
}
// did we find a group? Time to expand this into our full target list.
if (haveGroup) {
addresses = expandGroups(addresses);
}
SendStatus[] stats = new SendStatus[addresses.length];
// create our lists for notification and exception reporting.
Address[] sent = null;
Address[] unsent = null;
Address[] invalid = null;
try {
// send sender first. If this failed, send a failure notice of the
// event, using the full list of
// addresses as the unsent, and nothing for the rest.
if (!connection.sendMailFrom(message)) {
unsent = addresses;
sent = new Address[0];
invalid = new Address[0];
// notify of the error.
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
// include the reponse information here.
SMTPReply last = connection.getLastServerResponse();
// now send an "uber-exception" to indicate the failure.
throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
invalid);
}
// get the additional notification status, if available
String dsn = getDeliveryStatusNotification(message);
// we need to know about any failures once we've gone through the
// complete list, so keep a
// failure flag.
boolean sendFailure = false;
// event notifcation requires we send lists of successes and
// failures broken down by category.
// The categories are:
//
// 1) addresses successfully processed.
// 2) addresses deemed valid, but had a processing failure that
// prevented sending.
// 3) addressed deemed invalid (basically all other processing
// failures).
ArrayList sentAddresses = new ArrayList();
ArrayList unsentAddresses = new ArrayList();
ArrayList invalidAddresses = new ArrayList();
// Now we add a MAIL TO record for each recipient. At this point, we
// just collect
for (int i = 0; i < addresses.length; i++) {
InternetAddress target = (InternetAddress) addresses[i];
// write out the record now.
SendStatus status = connection.sendRcptTo(target, dsn);
stats[i] = status;
switch (status.getStatus()) {
// successfully sent
case SendStatus.SUCCESS:
sentAddresses.add(target);
break;
// we have an invalid address of some sort, or a general sending
// error (which we'll
// interpret as due to an invalid address.
case SendStatus.INVALID_ADDRESS:
case SendStatus.GENERAL_ERROR:
sendFailure = true;
invalidAddresses.add(target);
break;
// good address, but this was a send failure.
case SendStatus.SEND_FAILURE:
sendFailure = true;
unsentAddresses.add(target);
break;
}
}
// if we had a send failure, then we need to check if we allow
// partial sends. If not allowed,
// we abort the send operation now.
if (sendFailure) {
// if we're not allowing partial successes or we've failed on
// all of the addresses, it's
// time to abort.
if (!partialSends || sentAddresses.isEmpty()) {
// we send along the valid and invalid address lists on the
// notifications and
// exceptions.
// however, since we're aborting the entire send, the
// successes need to become
// members of the failure list.
unsentAddresses.addAll(sentAddresses);
// this one is empty.
sent = new Address[0];
unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
// go reset our connection so we can process additional
// sends.
connection.resetConnection();
// get a list of chained exceptions for all of the failures.
MessagingException failures = generateExceptionChain(stats, false);
// now send an "uber-exception" to indicate the failure.
throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
}
}
try {
// try to send the data
connection.sendData((MimeMessage)message);
} catch (MessagingException e) {
// If there's an error at this point, this is a complete
// delivery failure.
// we send along the valid and invalid address lists on the
// notifications and
// exceptions.
// however, since we're aborting the entire send, the successes
// need to become
// members of the failure list.
unsentAddresses.addAll(sentAddresses);
// this one is empty.
sent = new Address[0];
unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
// notify of the error.
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
// send a send failure exception.
throw new SMTPSendFailedException("DATA", 0, "Send failure", e, sent, unsent, invalid);
}
// create our lists for notification and exception reporting from
// this point on.
sent = (Address[]) sentAddresses.toArray(new Address[0]);
unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
// if sendFailure is true, we had an error during the address phase,
// but we had permission to
// process this as a partial send operation. Now that the data has
// been sent ok, it's time to
// report the partial failure.
if (sendFailure) {
// notify our listeners of the partial delivery.
notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
// get a list of chained exceptions for all of the failures (and
// the successes, if reportSuccess has been
// turned on).
MessagingException failures = generateExceptionChain(stats, reportSuccess);
// now send an "uber-exception" to indicate the failure.
throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
}
// notify our listeners of successful delivery.
notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
// we've not had any failures, but we've been asked to report
// success as an exception. Do
// this now.
if (reportSuccess) {
// generate the chain of success exceptions (we already know
// there are no failure ones to report).
MessagingException successes = generateExceptionChain(stats, reportSuccess);
if (successes != null) {
throw successes;
}
}
} catch (SMTPSendFailedException e) {
// if this is a send failure, we've already handled
// notifications....just rethrow it.
throw e;
} catch (MessagingException e) {
// notify of the error.
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
throw e;
}
}
/**
* Determine what delivery status notification should
* be added to the RCPT TO: command.
*
* @param message The message we're sending.
*
* @return The string NOTIFY= value to add to the command.
*/
protected String getDeliveryStatusNotification(Message message) {
String dsn = null;
// there's an optional notification argument that can be added to
// MAIL TO. See if we've been
// provided with one.
// an SMTPMessage object is the first source
if (message instanceof SMTPMessage) {
// get the notification options
int options = ((SMTPMessage) message).getNotifyOptions();
switch (options) {
// a zero value indicates nothing is set.
case 0:
break;
case SMTPMessage.NOTIFY_NEVER:
dsn = "NEVER";
break;
case SMTPMessage.NOTIFY_SUCCESS:
dsn = "SUCCESS";
break;
case SMTPMessage.NOTIFY_FAILURE:
dsn = "FAILURE";
break;
case SMTPMessage.NOTIFY_DELAY:
dsn = "DELAY";
break;
// now for combinations...there are few enough combinations here
// that we can just handle this in the switch statement rather
// than have to
// concatentate everything together.
case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
dsn = "SUCCESS,FAILURE";
break;
case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
dsn = "SUCCESS,DELAY";
break;
case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
dsn = "FAILURE,DELAY";
break;
case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
dsn = "SUCCESS,FAILURE,DELAY";
break;
}
}
// if still null, grab a property value (yada, yada, yada...)
if (dsn == null) {
dsn = props.getProperty(MAIL_SMTP_DSN_NOTIFY);
}
return dsn;
}
/**
* Close the connection. On completion, we'll be disconnected from the
* server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// This is done to ensure proper event notification.
super.close();
// NB: We reuse the connection if asked to reconnect
connection.close();
}
/**
* Turn a series of send status items into a chain of exceptions indicating
* the state of each send operation.
*
* @param stats
* The list of SendStatus items.
* @param reportSuccess
* Indicates whether we should include the report success items.
*
* @return The head of a chained list of MessagingExceptions.
*/
protected MessagingException generateExceptionChain(SendStatus[] stats, boolean reportSuccess) {
MessagingException current = null;
for (int i = 0; i < stats.length; i++) {
SendStatus status = stats[i];
if (status != null) {
MessagingException nextException = stats[i].getException(reportSuccess);
// if there's an exception associated with this status, chain it
// up with the rest.
if (nextException != null) {
if (current == null) {
current = nextException;
} else {
current.setNextException(nextException);
current = nextException;
}
}
}
}
return current;
}
/**
* Expand the address list by converting any group addresses into single
* address targets.
*
* @param addresses
* The input array of addresses.
*
* @return The expanded array of addresses.
* @exception MessagingException
*/
protected Address[] expandGroups(Address[] addresses) throws MessagingException {
ArrayList expandedAddresses = new ArrayList();
// run the list looking for group addresses, and add the full group list
// to our targets.
for (int i = 0; i < addresses.length; i++) {
InternetAddress address = (InternetAddress) addresses[i];
// not a group? Just copy over to the other list.
if (!address.isGroup()) {
expandedAddresses.add(address);
} else {
// get the group address and copy each member of the group into
// the expanded list.
InternetAddress[] groupAddresses = address.getGroup(true);
for (int j = 1; j < groupAddresses.length; j++) {
expandedAddresses.add(groupAddresses[j]);
}
}
}
// convert back into an array.
return (Address[]) expandedAddresses.toArray(new Address[0]);
}
/**
* Retrieve the local client host name.
*
* @return The string version of the local host name.
* @exception SMTPTransportException
*/
public String getLocalHost() throws MessagingException {
return connection.getLocalHost();
}
/**
* Explicitly set the local host information.
*
* @param localHost
* The new localHost name.
*/
public void setLocalHost(String localHost) {
connection.setLocalHost(localHost);
}
/**
* Return the current reportSuccess property.
*
* @return The current reportSuccess property.
*/
public boolean getReportSuccess() {
return connection.getReportSuccess();
}
/**
* Set a new value for the reportSuccess property.
*
* @param report
* The new setting.
*/
public void setReportSuccess(boolean report) {
connection.setReportSuccess(report);
}
/**
* Return the current startTLS property.
*
* @return The current startTLS property.
*/
public boolean getStartTLS() {
return connection.getStartTLS();
}
/**
* Set a new value for the startTLS property.
*
* @param start
* The new setting.
*/
public void setStartTLS(boolean start) {
connection.setStartTLS(start);
}
/**
* Retrieve the SASL realm used for DIGEST-MD5 authentication. This will
* either be explicitly set, or retrieved using the mail.smtp.sasl.realm
* session property.
*
* @return The current realm information (which can be null).
*/
public String getSASLRealm() {
return connection.getSASLRealm();
}
/**
* Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
*
* @param name
* The new realm name.
*/
public void setSASLRealm(String name) {
connection.setSASLRealm(name);
}
}
././@LongLink 0000000 0000000 0000000 00000000160 00000000000 011562 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPM0000664 0001750 0001750 00000014035 10474735322 032151 0 ustar brian brian /*
* 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.transport.smtp;
import java.io.InputStream;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
public class SMTPMessage extends MimeMessage {
// never notify
public static final int NOTIFY_NEVER = -1;
// notify of successful deliveries.
public static final int NOTIFY_SUCCESS = 1;
// notify of delivery failures.
public static final int NOTIFY_FAILURE = 2;
// notify of delivery delays
public static final int NOTIFY_DELAY = 4;
// return full message with status notifications
public static final int RETURN_FULL = 1;
// return only message headers with status notifications
public static final int RETURN_HDRS = 2;
// support 8BitMime encodings
protected boolean allow8bitMIME = false;
// a from address specified in the message envelope. Overrides other from
// sources.
protected String envelopeFrom = null;
// an option string to append to the MAIL command on sending.
protected String mailExtension = null;
// SMTP mail notification options if DSN is supported.
protected int notifyOptions = 0;
// DSN return option notification values.
protected int returnOption = 0;
// allow sending if some addresses give errors.
protected boolean sendPartial = false;
// an RFC 2554 AUTH= value.
protected String submitter = null;
/**
* Default (and normal) constructor for an SMTPMessage.
*
* @param session
* The hosting Javamail Session.
*/
public SMTPMessage(Session session) {
// this is a simple one.
super(session);
}
/**
* Construct an SMTPMessage instance by reading and parsing the data from
* the provided InputStream. The InputStream will be left positioned at the
* end of the message data on constructor completion.
*
* @param session
* The hosting Javamail Session.
*/
public SMTPMessage(Session session, InputStream source) throws MessagingException {
// this is a simple one.
super(session, source);
}
/**
* Construct an SMTPMimeMessage from another source MimeMessage object. The
* new object and the old object are independent of each other.
*
* @param source
* The source MimeMessage object.
*/
public SMTPMessage(MimeMessage source) throws MessagingException {
super(source);
}
/**
* Change the allow8BitMime attribute for the message.
*
* @param a
* The new setting.
*/
public void setAllow8bitMIME(boolean a) {
allow8bitMIME = a;
}
/**
* Retrieve the current 8bitMIME attribute.
*
* @return The current attribute value.
*/
public boolean getAllow8bitMIME() {
return allow8bitMIME;
}
/**
* Change the envelopeFrom attribute for the message.
*
* @param from
* The new setting.
*/
public void setEnvelopeFrom(String from) {
envelopeFrom = from;
}
/**
* Retrieve the current evelopeFrom attribute.
*
* @return The current attribute value.
*/
public String getEnvelopeFrom() {
return envelopeFrom;
}
/**
* Change the mailExtension attribute for the message.
*
* @param e
* The new setting.
*/
public void setMailExtension(String e) {
mailExtension = e;
}
/**
* Retrieve the current mailExtension attribute.
*
* @return The current attribute value.
*/
public String getMailExtension() {
return mailExtension;
}
/**
* Change the notifyOptions attribute for the message.
*
* @param options
* The new setting.
*/
public void setNotifyOptions(int options) {
notifyOptions = options;
}
/**
* Retrieve the current notifyOptions attribute.
*
* @return The current attribute value.
*/
public int getNotifyOptions() {
return notifyOptions;
}
/**
* Change the returnOptions attribute for the message.
*
* @param option
* The new setting.
*/
public void setReturnOption(int option) {
returnOption = option;
}
/**
* Retrieve the current returnOption attribute.
*
* @return The current attribute value.
*/
public int getReturnOption() {
return returnOption;
}
/**
* Change the sendPartial attribute for the message.
*
* @param a
* The new setting.
*/
public void setSendPartial(boolean a) {
sendPartial = a;
}
/**
* Retrieve the current sendPartial attribute.
*
* @return The current attribute value.
*/
public boolean getSendPartial() {
return sendPartial;
}
/**
* Change the submitter attribute for the message.
*
* @param s
* The new setting.
*/
public void setSubmitter(String s) {
submitter = s;
}
/**
* Retrieve the current submitter attribute.
*
* @return The current attribute value.
*/
public String getSubmitter() {
return submitter;
}
}
././@LongLink 0000000 0000000 0000000 00000000202 00000000000 011557 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPA0000664 0001750 0001750 00000004422 10474735322 032134 0 ustar brian brian /*
* 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.transport.smtp;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
public class SMTPAddressSucceededException extends MessagingException {
// the succeeding address
InternetAddress addr;
// the failing command
protected String cmd;
// the error code for the failure
protected int rc;
/**
* Constructor for an SMTPAddressSucceededException.
*
* @param addr
* The succeeding address.
* @param cmd
* The succeeding command string.
* @param rc
* The error code for the command.
* @param err
* An error message for the exception.
*/
SMTPAddressSucceededException(InternetAddress addr, java.lang.String cmd, int rc, java.lang.String err) {
super(err);
this.cmd = cmd;
this.rc = rc;
this.addr = addr;
}
/**
* Get the failing command string for the exception.
*
* @return The string value of the failing command.
*/
public String getCommand() {
return cmd;
}
/**
* The failing command return code.
*
* @return The failure return code.
*/
public int getReturnCode() {
return rc;
}
/**
* Retrieve the internet address associated with this exception.
*
* @return The provided InternetAddress object.
*/
public InternetAddress getAddress() {
return addr;
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPS0000664 0001750 0001750 00000002167 10474735322 032162 0 ustar brian brian /*
* 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.transport.smtp;
import javax.mail.Session;
import javax.mail.URLName;
public class SMTPSTransport extends SMTPTransport {
/**
* @param session
* @param name
*/
public SMTPSTransport(Session session, URLName name) {
super(session, name, "smtps", 465, true);
}
}
././@LongLink 0000000 0000000 0000000 00000000200 00000000000 011555 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/Malfo0000664 0001750 0001750 00000002410 10474735322 032301 0 ustar brian brian /*
* 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.transport.smtp;
/**
* Exception for when a SMTP reply string has a problem
*
* @version $Rev: 437941 $ $Date: 2006-08-28 23:56:02 -0400 (Mon, 28 Aug 2006) $
*/
class MalformedSMTPReplyException extends Exception {
MalformedSMTPReplyException() {
super();
}
MalformedSMTPReplyException(String msg) {
super(msg);
}
MalformedSMTPReplyException(String msg, Exception t) {
super(msg, t);
}
}
././@LongLink 0000000 0000000 0000000 00000000173 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPT0000664 0001750 0001750 00000002457 10474735322 032165 0 ustar brian brian /*
* 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.transport.smtp;
/**
* General purpose Exception
*
* @version $Id: SMTPTransportException.java 437941 2006-08-29 03:56:02Z jdillon $
*/
class SMTPTransportException extends Exception {
SMTPTransportException() {
super();
}
SMTPTransportException(String s) {
super(s);
}
SMTPTransportException(String s, Exception t) {
super(s, t);
}
SMTPTransportException(Exception t) {
super("SMTP Transport error", t);
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPC0000664 0001750 0001750 00000137732 11441725756 032156 0 ustar brian brian /*
* 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.transport.smtp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimePart;
import javax.mail.Session;
import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
import org.apache.geronimo.javamail.util.CountingOutputStream;
import org.apache.geronimo.javamail.util.MailConnection;
import org.apache.geronimo.javamail.util.MIMEOutputStream;
import org.apache.geronimo.javamail.util.ProtocolProperties;
import org.apache.geronimo.mail.util.Base64;
import org.apache.geronimo.mail.util.XText;
/**
* Simple implementation of SMTP transport. Just does plain RFC977-ish delivery.
*
* @version $Rev: 995094 $ $Date: 2010-09-08 11:29:50 -0400 (Wed, 08 Sep 2010) $
*/
public class SMTPConnection extends MailConnection {
protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
protected static final String MAIL_SMTP_EXTENSION = "mailextension";
protected static final String MAIL_SMTP_EHLO = "ehlo";
protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
protected static final String MAIL_SMTP_AUTH = "auth";
protected static final String MAIL_SMTP_FROM = "from";
protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
protected static final String MAIL_SMTP_SUBMITTER = "submitter";
/**
* property keys for protocol properties.
*/
protected static final int DEFAULT_NNTP_PORT = 119;
// the last response line received from the server.
protected SMTPReply lastServerResponse = null;
// do we report success after completion of each mail send.
protected boolean reportSuccess;
// does the server support transport level security?
protected boolean serverTLS = false;
// is TLS enabled on our part?
protected boolean useTLS = false;
// should we use 8BITMIME encoding if supported by the server?
protected boolean use8bit = false;
/**
* Normal constructor for an SMTPConnection() object.
*
* @param props The property bundle for this protocol instance.
*/
public SMTPConnection(ProtocolProperties props) {
super(props);
// check to see if we need to throw an exception after a send operation.
reportSuccess = props.getBooleanProperty(MAIL_SMTP_REPORT_SUCCESS, false);
// and also check for TLS enablement.
useTLS = props.getBooleanProperty(MAIL_SMTP_STARTTLS_ENABLE, false);
// and also check for 8bitmime support
use8bit = props.getBooleanProperty(MAIL_SMTP_ALLOW8BITMIME, false);
}
/**
* Connect to the server and do the initial handshaking.
*
* @param host The target host name.
* @param port The target port
* @param username The connection username (can be null)
* @param password The authentication password (can be null).
*
* @return true if we were able to obtain a connection and
* authenticate.
* @exception MessagingException
*/
public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
// now check to see if we need to authenticate. If we need this, then
// we must have a username and
// password specified. Failing this may result in a user prompt to
// collect the information.
boolean mustAuthenticate = props.getBooleanProperty(MAIL_SMTP_AUTH, false);
// if we need to authenticate, and we don't have both a userid and
// password, then we fail this
// immediately. The Service.connect() method will try to obtain the user
// information and retry the
// connection one time.
if (mustAuthenticate && (username == null || password == null)) {
debugOut("Failing connection for missing authentication information");
return false;
}
super.protocolConnect(host, port, username, password);
try {
// create socket and connect to server.
getConnection();
// receive welcoming message
if (!getWelcome()) {
debugOut("Error getting welcome message");
throw new MessagingException("Error in getting welcome msg");
}
// say hello
if (!sendHandshake()) {
debugOut("Error getting processing handshake message");
throw new MessagingException("Error in saying EHLO to server");
}
// authenticate with the server, if necessary
if (!processAuthentication()) {
debugOut("User authentication failure");
throw new AuthenticationFailedException("Error authenticating with server");
}
} catch (IOException e) {
debugOut("I/O exception establishing connection", e);
throw new MessagingException("Connection error", e);
}
debugOut("Successful connection");
return true;
}
/**
* Close the connection. On completion, we'll be disconnected from the
* server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// if we're already closed, get outta here.
if (socket == null) {
return;
}
try {
// say goodbye
sendQuit();
} finally {
// and close up the connection. We do this in a finally block to
// make sure the connection
// is shut down even if quit gets an error.
closeServerConnection();
}
}
public String toString() {
return "SMTPConnection host: " + serverHost + " port: " + serverPort;
}
/**
* Set the sender for this mail.
*
* @param message
* The message we're sending.
*
* @return True if the command was accepted, false otherwise.
* @exception MessagingException
*/
protected boolean sendMailFrom(Message message) throws MessagingException {
// need to sort the from value out from a variety of sources.
String from = null;
// first potential source is from the message itself, if it's an
// instance of SMTPMessage.
if (message instanceof SMTPMessage) {
from = ((SMTPMessage) message).getEnvelopeFrom();
}
// if not available from the message, check the protocol property next
if (from == null || from.length() == 0) {
// the from value can be set explicitly as a property
from = props.getProperty(MAIL_SMTP_FROM);
}
// if not there, see if we have something in the message header.
if (from == null || from.length() == 0) {
Address[] fromAddresses = message.getFrom();
// if we have some addresses in the header, then take the first one
// as our From: address
if (fromAddresses != null && fromAddresses.length > 0) {
from = ((InternetAddress) fromAddresses[0]).getAddress();
}
// get what the InternetAddress class believes to be the local
// address.
else {
InternetAddress local = InternetAddress.getLocalAddress(session);
if (local != null) {
from = local.getAddress();
}
}
}
if (from == null || from.length() == 0) {
throw new MessagingException("no FROM address");
}
StringBuffer command = new StringBuffer();
// start building up the command
command.append("MAIL FROM: ");
command.append(fixEmailAddress(from));
// If the server supports the 8BITMIME extension, we might need to change the
// transfer encoding for the content to allow for direct transmission of the
// 8-bit codes.
if (supportsExtension("8BITMIME")) {
// we only do this if the capability was enabled via a property option or
// by explicitly setting the property on the message object.
if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
// make sure we add the BODY= option to the FROM message.
command.append(" BODY=8BITMIME");
// go check the content and see if the can convert the transfer encoding to
// allow direct 8-bit transmission.
if (convertTransferEncoding((MimeMessage)message)) {
// if we changed the encoding on any of the parts, then we
// need to save the message again
message.saveChanges();
}
}
}
// some servers ask for a size estimate on the initial send
if (supportsExtension("SIZE")) {
int estimate = getSizeEstimate(message);
if (estimate > 0) {
command.append(" SIZE=" + estimate);
}
}
// does this server support Delivery Status Notification? Then we may
// need to add some extra to the command.
if (supportsExtension("DSN")) {
String returnNotification = null;
// the return notification stuff might be set as value on the
// message object itself.
if (message instanceof SMTPMessage) {
// we need to convert the option into a string value.
switch (((SMTPMessage) message).getReturnOption()) {
case SMTPMessage.RETURN_FULL:
returnNotification = "FULL";
break;
case SMTPMessage.RETURN_HDRS:
returnNotification = "HDRS";
break;
}
}
// if not obtained from the message object, it can also be set as a
// property.
if (returnNotification == null) {
// the DSN value is set by yet another property.
returnNotification = props.getProperty(MAIL_SMTP_DSN_RET);
}
// if we have a target, add the notification stuff to our FROM
// command.
if (returnNotification != null) {
command.append(" RET=");
command.append(returnNotification);
}
}
// if this server supports AUTH and we have submitter information, then
// we also add the
// "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
if (supportsExtension("AUTH")) {
String submitter = null;
// another option that can be specified on the message object.
if (message instanceof SMTPMessage) {
submitter = ((SMTPMessage) message).getSubmitter();
}
// if not part of the object, try for a propery version.
if (submitter == null) {
// we only send the extra keyword is a submitter is specified.
submitter = props.getProperty(MAIL_SMTP_SUBMITTER);
}
// we have one...add the keyword, plus the submitter info in xtext
// format (defined by RFC 1891).
if (submitter != null) {
command.append(" AUTH=");
try {
// add this encoded
command.append(new String(XText.encode(submitter.getBytes("US-ASCII")), "US-ASCII"));
} catch (UnsupportedEncodingException e) {
throw new MessagingException("Invalid submitter value " + submitter);
}
}
}
String extension = null;
// now see if we need to add any additional extension info to this
// command. The extension is not
// checked for validity. That's the reponsibility of the caller.
if (message instanceof SMTPMessage) {
extension = ((SMTPMessage) message).getMailExtension();
}
// this can come either from the object or from a set property.
if (extension == null) {
extension = props.getProperty(MAIL_SMTP_EXTENSION);
}
// have something real to add?
if (extension != null && extension.length() != 0) {
// tack this on the end with a blank delimiter.
command.append(' ');
command.append(extension);
}
// and finally send the command
SMTPReply line = sendCommand(command.toString());
// 250 response indicates success.
return line.getCode() == SMTPReply.COMMAND_ACCEPTED;
}
/**
* Check to see if a MIME body part can have its
* encoding changed from quoted-printable or base64
* encoding to 8bit encoding. In order for this
* to work, it must follow the rules laid out in
* RFC 2045. To qualify for conversion, the text
* must be:
*
* 1) No more than 998 bytes long
* 2) All lines are terminated with CRLF sequences
* 3) CR and LF characters only occur in properly
* formed line separators
* 4) No null characters are allowed.
*
* The conversion will only be applied to text
* elements, and this will recurse through the
* different elements of MultiPart content.
*
* @param bodyPart The bodyPart to convert. Initially, this will be
* the message itself.
*
* @return true if any conversion was performed, false if
* nothing was converted.
*/
protected boolean convertTransferEncoding(MimePart bodyPart)
{
boolean converted = false;
try {
// if this is a multipart element, apply the conversion rules
// to each of the parts.
if (bodyPart.isMimeType("multipart/")) {
MimeMultipart parts = (MimeMultipart)bodyPart.getContent();
for (int i = 0; i < parts.getCount(); i++) {
// convert each body part, and accumulate the conversion result
converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i));
}
}
else {
// we only do this if the encoding is quoted-printable or base64
String encoding = bodyPart.getEncoding();
if (encoding != null) {
encoding = encoding.toLowerCase();
if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
// this requires encoding. Read the actual content to see if
// it conforms to the 8bit encoding rules.
if (isValid8bit(bodyPart.getInputStream())) {
// There's a huge hidden gotcha lurking under the covers here.
// If the content just exists as an encoded byte array, then just
// switching the transfer encoding will mess things up because the
// already encoded data gets transmitted in encoded form, but with
// and 8bit encoding style. As a result, it doesn't get unencoded on
// the receiving end. This is a nasty problem to debug.
//
// The solution is to get the content as it's object type, set it back
// on the the message in raw form. Requesting the content will apply the
// current transfer encoding value to the data. Once we have set the
// content value back, we can reset the transfer encoding.
bodyPart.setContent(bodyPart.getContent(), bodyPart.getContentType());
// it's valid, so change the transfer encoding to just
// pass the data through.
bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
converted = true; // we've changed something
}
}
}
}
} catch (MessagingException e) {
} catch (IOException e) {
}
return converted;
}
/**
* Get the server's welcome blob from the wire....
*/
protected boolean getWelcome() throws MessagingException {
SMTPReply line = getReply();
// just return the error status...we don't care about any of the
// response information
return !line.isError();
}
/**
* Get an estimate of the transmission size for this
* message. This size is the complete message as it is
* encoded and transmitted on the DATA command, not counting
* the terminating ".CRLF".
*
* @param msg The message we're sending.
*
* @return The count of bytes, if it can be calculated.
*/
protected int getSizeEstimate(Message msg) {
// now the data... I could look at the type, but
try {
CountingOutputStream outputStream = new CountingOutputStream();
// the data content has two requirements we need to meet by
// filtering the
// output stream. Requirement 1 is to conicalize any line breaks.
// All line
// breaks will be transformed into properly formed CRLF sequences.
//
// Requirement 2 is to perform byte-stuff for any line that begins
// with a "."
// so that data is not confused with the end-of-data marker (a
// "\r\n.\r\n" sequence.
//
// The MIME output stream performs those two functions on behalf of
// the content
// writer.
MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
msg.writeTo(mimeOut);
// now to finish, we make sure there's a line break at the end.
mimeOut.forceTerminatingLineBreak();
// and flush the data to send it along
mimeOut.flush();
return outputStream.getCount();
} catch (IOException e) {
return 0; // can't get an estimate
} catch (MessagingException e) {
return 0; // can't get an estimate
}
}
/**
* Sends the data in the message down the socket. This presumes the server
* is in the right place and ready for getting the DATA message and the data
* right place in the sequence
*/
protected void sendData(MimeMessage msg) throws MessagingException {
// send the DATA command
SMTPReply line = sendCommand("DATA");
if (line.isError()) {
throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
}
// now the data... I could look at the type, but
try {
// the data content has two requirements we need to meet by
// filtering the
// output stream. Requirement 1 is to conicalize any line breaks.
// All line
// breaks will be transformed into properly formed CRLF sequences.
//
// Requirement 2 is to perform byte-stuff for any line that begins
// with a "."
// so that data is not confused with the end-of-data marker (a
// "\r\n.\r\n" sequence.
//
// The MIME output stream performs those two functions on behalf of
// the content
// writer.
MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
msg.writeTo(mimeOut, new String[] {"Bcc", "Content-Length"});
// now to finish, we send a CRLF sequence, followed by a ".".
mimeOut.writeSMTPTerminator();
// and flush the data to send it along
mimeOut.flush();
} catch (IOException e) {
throw new MessagingException(e.toString());
} catch (MessagingException e) {
throw new MessagingException(e.toString());
}
// use a longer time out here to give the server time to process the
// data.
try {
line = new SMTPReply(receiveLine(TIMEOUT * 2));
} catch (MalformedSMTPReplyException e) {
throw new MessagingException(e.toString());
} catch (MessagingException e) {
throw new MessagingException(e.toString());
}
if (line.isError()) {
throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
}
}
/**
* Sends the QUIT message and receieves the response
*/
protected void sendQuit() throws MessagingException {
// there's yet another property that controls whether we should wait for
// a reply for a QUIT command. If true, we're suppposed to wait for a response
// from the QUIT command. Otherwise we just send the QUIT and bail. The default
// is "false"
if (props.getBooleanProperty(MAIL_SMTP_QUITWAIT, true)) {
// handle as a real command...we're going to ignore the response.
sendCommand("QUIT");
} else {
// just send the command without waiting for a response.
sendLine("QUIT");
}
}
/**
* Sets a receiver address for the current message
*
* @param addr
* The target address.
* @param dsn
* An optional DSN option appended to the RCPT TO command.
*
* @return The status for this particular send operation.
* @exception MessagingException
*/
public SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
// compose the command using the fixed up email address. Normally, this
// involves adding
// "<" and ">" around the address.
StringBuffer command = new StringBuffer();
// compose the first part of the command
command.append("RCPT TO: ");
command.append(fixEmailAddress(addr.getAddress()));
// if we have DSN information, append it to the command.
if (dsn != null) {
command.append(" NOTIFY=");
command.append(dsn);
}
// get a string version of this command.
String commandString = command.toString();
SMTPReply line = sendCommand(commandString);
switch (line.getCode()) {
// these two are both successful transmissions
case SMTPReply.COMMAND_ACCEPTED:
case SMTPReply.ADDRESS_NOT_LOCAL:
// we get out of here with the status information.
return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
// these are considered invalid address errors
case SMTPReply.PARAMETER_SYNTAX_ERROR:
case SMTPReply.INVALID_COMMAND_SEQUENCE:
case SMTPReply.MAILBOX_NOT_FOUND:
case SMTPReply.INVALID_MAILBOX:
case SMTPReply.USER_NOT_LOCAL:
// we get out of here with the status information.
return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
// the command was valid, but something went wrong in the server.
case SMTPReply.SERVICE_NOT_AVAILABLE:
case SMTPReply.MAILBOX_BUSY:
case SMTPReply.PROCESSING_ERROR:
case SMTPReply.INSUFFICIENT_STORAGE:
case SMTPReply.MAILBOX_FULL:
// we get out of here with the status information.
return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
// everything else is considered really bad...
default:
// we get out of here with the status information.
return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
}
}
/**
* Send a command to the server, returning the first response line back as a
* reply.
*
* @param data
* The data to send.
*
* @return A reply object with the reply line.
* @exception MessagingException
*/
protected SMTPReply sendCommand(String data) throws MessagingException {
sendLine(data);
return getReply();
}
/**
* Sends a message down the socket and terminates with the appropriate CRLF
*/
protected void sendLine(String data) throws MessagingException {
if (socket == null || !socket.isConnected()) {
throw new MessagingException("no connection");
}
try {
outputStream.write(data.getBytes("ISO8859-1"));
outputStream.write(CR);
outputStream.write(LF);
outputStream.flush();
} catch (IOException e) {
throw new MessagingException(e.toString());
}
}
/**
* Receives one line from the server. A line is a sequence of bytes
* terminated by a CRLF
*
* @return the line from the server as String
*/
protected String receiveLine() throws MessagingException {
return receiveLine(TIMEOUT);
}
/**
* Get a reply line for an SMTP command.
*
* @return An SMTP reply object from the stream.
*/
protected SMTPReply getReply() throws MessagingException {
try {
lastServerResponse = new SMTPReply(receiveLine());
// if the first line we receive is a continuation, continue
// reading lines until we reach the non-continued one.
while (lastServerResponse.isContinued()) {
lastServerResponse.addLine(receiveLine());
}
} catch (MalformedSMTPReplyException e) {
throw new MessagingException(e.toString());
} catch (MessagingException e) {
throw e;
}
return lastServerResponse;
}
/**
* Retrieve the last response received from the SMTP server.
*
* @return The raw response string (including the error code) returned from
* the SMTP server.
*/
public SMTPReply getLastServerResponse() {
return lastServerResponse;
}
/**
* Receives one line from the server. A line is a sequence of bytes
* terminated by a CRLF
*
* @return the line from the server as String
*/
protected String receiveLine(int delayMillis) throws MessagingException {
if (socket == null || !socket.isConnected()) {
throw new MessagingException("no connection");
}
int timeout = 0;
try {
// for now, read byte for byte, looking for a CRLF
timeout = socket.getSoTimeout();
socket.setSoTimeout(delayMillis);
StringBuffer buff = new StringBuffer();
int c;
boolean crFound = false, lfFound = false;
while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
// we're looking for a CRLF sequence, so mark each one as seen.
// Any other
// character gets appended to the end of the buffer.
if (c == CR) {
crFound = true;
} else if (c == LF) {
lfFound = true;
} else {
buff.append((char) c);
}
}
String line = buff.toString();
return line;
} catch (SocketException e) {
throw new MessagingException(e.toString());
} catch (IOException e) {
throw new MessagingException(e.toString());
} finally {
try {
socket.setSoTimeout(timeout);
} catch (SocketException e) {
// ignore - was just trying to do the decent thing...
}
}
}
/**
* Convert an InternetAddress into a form sendable on an SMTP mail command.
* InternetAddress.getAddress() generally returns just the address portion
* of the full address, minus route address markers. We need to ensure we
* have an address with '<' and '>' delimiters.
*
* @param mail
* The mail address returned from InternetAddress.getAddress().
*
* @return A string formatted for sending.
*/
protected String fixEmailAddress(String mail) {
if (mail.charAt(0) == '<') {
return mail;
}
return "<" + mail + ">";
}
/**
* Start the handshake process with the server, including setting up and
* TLS-level work. At the completion of this task, we should be ready to
* authenticate with the server, if needed.
*/
protected boolean sendHandshake() throws MessagingException {
// check to see what sort of initial handshake we need to make.
boolean useEhlo = props.getBooleanProperty(MAIL_SMTP_EHLO, true);
// if we're to use Ehlo, send it and then fall back to just a HELO
// message if it fails.
if (useEhlo) {
if (!sendEhlo()) {
sendHelo();
}
} else {
// send the initial hello response.
sendHelo();
}
if (useTLS) {
// if we've been told to use TLS, and this server doesn't support
// it, then this is a failure
if (!serverTLS) {
throw new MessagingException("Server doesn't support required transport level security");
}
// if the server supports TLS, then use it for the connection.
// on our connection.
getConnectedTLSSocket();
// some servers (gmail is one that I know of) only send a STARTTLS
// extension message on the
// first EHLO command. Now that we have the TLS handshaking
// established, we need to send a
// second EHLO message to retrieve the AUTH records from the server.
if (!sendEhlo()) {
throw new MessagingException("Failure sending EHLO command to SMTP server");
}
}
// this worked.
return true;
}
/**
* Switch the connection to using TLS level security, switching to an SSL
* socket.
*/
protected void getConnectedTLSSocket() throws MessagingException {
debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
// tell the server of our intention to start a TLS session
SMTPReply line = sendCommand("STARTTLS");
if (line.getCode() != SMTPReply.SERVICE_READY) {
debugOut("STARTTLS command rejected by SMTP server " + serverHost);
throw new MessagingException("Unable to make TLS server connection");
}
debugOut("STARTTLS command accepted");
// the base class handles the socket switch details
super.getConnectedTLSSocket();
}
/**
* Send the EHLO command to the SMTP server.
*
* @return True if the command was accepted ok, false for any errors.
* @exception SMTPTransportException
* @exception MalformedSMTPReplyException
* @exception MessagingException
*/
protected boolean sendEhlo() throws MessagingException {
sendLine("EHLO " + getLocalHost());
SMTPReply reply = getReply();
// we get a 250 code back. The first line is just a greeting, and
// extensions are identifed on
// continuations. If this fails, then we'll try once more with HELO to
// establish bona fides.
if (reply.getCode() != SMTPReply.COMMAND_ACCEPTED) {
return false;
}
// create a fresh mapping and authentications table
capabilities = new HashMap();
authentications = new ArrayList();
List lines = reply.getLines();
// process all of the continuation lines
for (int i = 1; i < lines.size(); i++) {
// go process the extention
processExtension((String)lines.get(i));
}
return true;
}
/**
* Send the HELO command to the SMTP server.
*
* @exception MessagingException
*/
protected void sendHelo() throws MessagingException {
// create a fresh mapping and authentications table
// these will be empty, but it will prevent NPEs
capabilities = new HashMap();
authentications = new ArrayList();
sendLine("HELO " + getLocalHost());
SMTPReply line = getReply();
// we get a 250 code back. The first line is just a greeting, and
// extensions are identifed on
// continuations. If this fails, then we'll try once more with HELO to
// establish bona fides.
if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
throw new MessagingException("Failure sending HELO command to SMTP server");
}
}
/**
* Return the current startTLS property.
*
* @return The current startTLS property.
*/
public boolean getStartTLS() {
return useTLS;
}
/**
* Set a new value for the startTLS property.
*
* @param start
* The new setting.
*/
public void setStartTLS(boolean start) {
useTLS = start;
}
/**
* Process an extension string passed back as the EHLP response.
*
* @param extension
* The string value of the extension (which will be of the form
* "NAME arguments").
*/
protected void processExtension(String extension) {
debugOut("Processing extension " + extension);
String extensionName = extension.toUpperCase();
String argument = "";
int delimiter = extension.indexOf(' ');
// if we have a keyword with arguments, parse them out and add to the
// argument map.
if (delimiter != -1) {
extensionName = extension.substring(0, delimiter).toUpperCase();
argument = extension.substring(delimiter + 1);
}
// add this to the map so it can be tested later.
capabilities.put(extensionName, argument);
// process a few special ones that don't require extra parsing.
// AUTH and AUTH=LOGIN are handled the same
if (extensionName.equals("AUTH")) {
// if we don't have an argument on AUTH, this means LOGIN.
if (argument == null) {
authentications.add("LOGIN");
} else {
// The security mechanisms are blank delimited tokens.
StringTokenizer tokenizer = new StringTokenizer(argument);
while (tokenizer.hasMoreTokens()) {
String mechanism = tokenizer.nextToken().toUpperCase();
authentications.add(mechanism);
}
}
}
// special case for some older servers.
else if (extensionName.equals("AUTH=LOGIN")) {
authentications.add("LOGIN");
}
// does this support transport level security?
else if (extensionName.equals("STARTTLS")) {
// flag this for later
serverTLS = true;
}
}
/**
* Retrieve any argument information associated with a extension reported
* back by the server on the EHLO command.
*
* @param name
* The name of the target server extension.
*
* @return Any argument passed on a server extension. Returns null if the
* extension did not include an argument or the extension was not
* supported.
*/
public String extensionParameter(String name) {
if (capabilities != null) {
return (String)capabilities.get(name);
}
return null;
}
/**
* Tests whether the target server supports a named extension.
*
* @param name
* The target extension name.
*
* @return true if the target server reported on the EHLO command that is
* supports the targer server, false if the extension was not
* supported.
*/
public boolean supportsExtension(String name) {
// this only returns null if we don't have this extension
return extensionParameter(name) != null;
}
/**
* Authenticate with the server, if necessary (or possible).
*
* @return true if we are ok to proceed, false for an authentication
* failures.
*/
protected boolean processAuthentication() throws MessagingException {
// no authentication defined?
if (!props.getBooleanProperty(MAIL_SMTP_AUTH, false)) {
return true;
}
// we need to authenticate, but we don't have userid/password
// information...fail this
// immediately.
if (username == null || password == null) {
return false;
}
// if unable to get an appropriate authenticator, just fail it.
ClientAuthenticator authenticator = getSaslAuthenticator();
if (authenticator == null) {
throw new MessagingException("Unable to obtain SASL authenticator");
}
if (debug) {
debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
}
// if the authenticator has some initial data, we compose a command
// containing the initial data.
if (authenticator.hasInitialResponse()) {
StringBuffer command = new StringBuffer();
// the auth command initiates the handshaking.
command.append("AUTH ");
// and tell the server which mechanism we're using.
command.append(authenticator.getMechanismName());
command.append(" ");
// and append the response data
try {
command.append(new String(Base64.encode(authenticator.evaluateChallenge(null)), "US-ASCII"));
} catch (UnsupportedEncodingException e) {
}
// send the command now
sendLine(command.toString());
}
// we just send an auth command with the command type.
else {
StringBuffer command = new StringBuffer();
// the auth command initiates the handshaking.
command.append("AUTH ");
// and tell the server which mechanism we're using.
command.append(authenticator.getMechanismName());
// send the command now
sendLine(command.toString());
}
// now process the challenge sequence. We get a 235 response back when
// the server accepts the
// authentication, and a 334 indicates we have an additional challenge.
while (true) {
// get the next line, and if it is an error response, return now.
SMTPReply line;
try {
line = new SMTPReply(receiveLine());
} catch (MalformedSMTPReplyException e) {
throw new MessagingException(e.toString());
} catch (MessagingException e) {
throw e;
}
// if we get a completion return, we've passed muster, so give an
// authentication response.
if (line.getCode() == SMTPReply.AUTHENTICATION_COMPLETE) {
debugOut("Successful SMTP authentication");
return true;
}
// we have an additional challenge to process.
else if (line.getCode() == SMTPReply.AUTHENTICATION_CHALLENGE) {
// Does the authenticator think it is finished? We can't answer
// an additional challenge,
// so fail this.
if (authenticator.isComplete()) {
return false;
}
try {
// we're passed back a challenge value, Base64 encoded.
byte[] challenge = Base64.decode(line.getMessage().getBytes("ISO8859-1"));
// have the authenticator evaluate and send back the encoded
// response.
sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII"));
} catch (UnsupportedEncodingException e) {
}
}
// completion or challenge are the only responses we know how to
// handle. Anything else must
// be a failure.
else {
if (debug) {
debugOut("Authentication failure " + line);
}
return false;
}
}
}
/**
* Attempt to retrieve a SASL authenticator for this
* protocol.
*
* @return A SASL authenticator, or null if a suitable one
* was not located.
*/
protected ClientAuthenticator getSaslAuthenticator() {
return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
}
/**
* Read the bytes in a stream a test to see if this
* conforms to the RFC 2045 rules for 8bit encoding.
*
* 1) No more than 998 bytes long
* 2) All lines are terminated with CRLF sequences
* 3) CR and LF characters only occur in properly
* formed line separators
* 4) No null characters are allowed.
*
* @param inStream The source input stream.
*
* @return true if this can be transmitted successfully
* using 8bit encoding, false if an alternate encoding
* will be required.
*/
protected boolean isValid8bit(InputStream inStream) {
try {
int ch;
int lineLength = 0;
while ((ch = inStream.read()) >= 0) {
// nulls are decidedly not allowed
if (ch == 0) {
return false;
}
// start of a CRLF sequence (potentially)
else if (ch == '\r') {
// check the next character. There must be one,
// and it must be a LF for this to be value
ch = inStream.read();
if (ch != '\n') {
return false;
}
// reset the line length
lineLength = 0;
}
else {
// a normal character
lineLength++;
// make sure the line is not too long
if (lineLength > 998) {
return false;
}
}
}
} catch (IOException e) {
return false; // can't read this, don't try passing it
}
// this converted ok
return true;
}
/**
* Simple holder class for the address/send status duple, as we can have
* mixed success for a set of addresses and a message
*/
static public class SendStatus {
public final static int SUCCESS = 0;
public final static int INVALID_ADDRESS = 1;
public final static int SEND_FAILURE = 2;
public final static int GENERAL_ERROR = 3;
// the status type of the send operation.
int status;
// the address associated with this status
InternetAddress address;
// the command string send to the server.
String cmd;
// the reply from the server.
SMTPReply reply;
/**
* Constructor for a SendStatus item.
*
* @param s
* The status type.
* @param a
* The address this is the status for.
* @param c
* The command string associated with this status.
* @param r
* The reply information from the server.
*/
public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
this.cmd = c;
this.status = s;
this.address = a;
this.reply = r;
}
/**
* Get the status information for this item.
*
* @return The current status code.
*/
public int getStatus() {
return this.status;
}
/**
* Retrieve the InternetAddress object associated with this send
* operation.
*
* @return The associated address object.
*/
public InternetAddress getAddress() {
return this.address;
}
/**
* Retrieve the reply information associated with this send operati
*
* @return The SMTPReply object received for the operation.
*/
public SMTPReply getReply() {
return reply;
}
/**
* Get the command string sent for this send operation.
*
* @return The command string for the MAIL TO command sent to the
* server.
*/
public String getCommand() {
return cmd;
}
/**
* Get an exception object associated with this send operation. There is
* a mechanism for reporting send success via a send operation, so this
* will be either a success or failure exception.
*
* @param reportSuccess
* Indicates if we want success operations too.
*
* @return A newly constructed exception object.
*/
public MessagingException getException(boolean reportSuccess) {
if (status != SUCCESS) {
return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
} else {
if (reportSuccess) {
return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
}
}
return null;
}
}
/**
* Reset the server connection after an error.
*
* @exception MessagingException
*/
public void resetConnection() throws MessagingException {
// we want the caller to retrieve the last response responsbile for
// requiring the reset, so save and
// restore that info around the reset.
SMTPReply last = lastServerResponse;
// send a reset command.
SMTPReply line = sendCommand("RSET");
// if this did not reset ok, just close the connection
if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
close();
}
// restore this.
lastServerResponse = last;
}
/**
* Return the current reportSuccess property.
*
* @return The current reportSuccess property.
*/
public boolean getReportSuccess() {
return reportSuccess;
}
/**
* Set a new value for the reportSuccess property.
*
* @param report
* The new setting.
*/
public void setReportSuccess(boolean report) {
reportSuccess = report;
}
}
././@LongLink 0000000 0000000 0000000 00000000177 00000000000 011572 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPA0000664 0001750 0001750 00000004401 10474735322 032131 0 ustar brian brian /*
* 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.transport.smtp;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
public class SMTPAddressFailedException extends MessagingException {
// the failing address
InternetAddress addr;
// the failing command
protected String cmd;
// the error code for the failure
protected int rc;
/**
* Constructor for an SMTPAddressFailingException.
*
* @param addr
* The failing address.
* @param cmd
* The failing command string.
* @param rc
* The error code for the command.
* @param err
* An error message for the exception.
*/
SMTPAddressFailedException(InternetAddress addr, java.lang.String cmd, int rc, java.lang.String err) {
super(err);
this.cmd = cmd;
this.rc = rc;
this.addr = addr;
}
/**
* Get the failing command string for the exception.
*
* @return The string value of the failing command.
*/
public String getCommand() {
return cmd;
}
/**
* The failing command return code.
*
* @return The failure return code.
*/
public int getReturnCode() {
return rc;
}
/**
* Retrieve the internet address associated with this exception.
*
* @return The provided InternetAddress object.
*/
public InternetAddress getAddress() {
return addr;
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ 0000775 0001750 0001750 00000000000 11703373731 027257 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStrea0000664 0001750 0001750 00000010636 11027776137 032165 0 ustar brian brian /*
* 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.util;
import java.io.IOException;
import java.io.OutputStream;
/**
* An implementation of an OutputStream that performs MIME linebreak
* canonicalization and "byte-stuff" so that data content does not get mistaken
* for a message data-end marker (CRLF.CRLF)l
*
* @version $Rev: 670717 $ $Date: 2008-06-23 15:41:19 -0400 (Mon, 23 Jun 2008) $
*/
public class MIMEOutputStream extends OutputStream {
// the wrappered output stream.
protected OutputStream out;
// last character we handled...used to recongnize line breaks.
protected int lastWrite = -1;
// a flag to indicate we've just processed a line break. This is used for
// byte stuffing purposes. This
// is initially true, because if the first character of the content is a
// period, we need to byte-stuff
// immediately.
protected boolean atLineBreak = true;
/**
* Create an output stream that writes to the target output stream.
*
* @param out
* The wrapped output stream.
*/
public MIMEOutputStream(OutputStream out) {
this.out = out;
}
// in order for this to work, we only need override the single character
// form, as the others
// funnel through this one by default.
public void write(int ch) throws IOException {
// if this is a CR character, always write out a full sequence, and
// remember that we just did this.
if (ch == '\r') {
out.write((byte) '\r');
out.write((byte) '\n');
// we've just taken a break;
atLineBreak = true;
}
// if this is a new line, then we need to determine if this is a loner
// or part of a CRLF sequence.
else if (ch == '\n') {
// is this a lone ranger?
if (lastWrite != '\r') {
// write the full CRLF sequence.
out.write((byte) '\r');
out.write((byte) '\n');
}
// regardless of whether we wrote something or not, we're still at a
// line break.
atLineBreak = true;
}
// potential byte-stuffing situation?
else if (ch == '.') {
// ok, this is a potential stuff situation. Did we just have a line
// break? Double up the character.
if (atLineBreak) {
out.write('.');
}
out.write('.');
atLineBreak = false;
} else {
// just write this out and flip the linebreak flag.
out.write(ch);
atLineBreak = false;
}
// remember this last one for CRLF tracking purposes.
lastWrite = ch;
}
/**
* Force the stream to be terminated at a line break.
* This is generally in preparation for the transport to
* write out an end-of-data marker, which generally
* needs to be preceded by a CRLF sequence.
*
* @exception IOException
*/
public void forceTerminatingLineBreak() throws IOException {
if (!atLineBreak) {
out.write((byte) '\r');
out.write((byte) '\n');
// we've just taken a break;
atLineBreak = true;
}
}
/**
* Write out the SMTP terminator to the output stream.
* This ensures that we don't write out an extra
* CRLF if the data terminates with that value.
*
* @exception IOException
*/
public void writeSMTPTerminator() throws IOException {
forceTerminatingLineBreak();
out.write('.');
out.write('\r');
out.write('\n');
}
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011562 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MailConnection.0000664 0001750 0001750 00000100445 11265117504 032163 0 ustar brian brian /**
* 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.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.net.ssl.SSLSocket;
import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
import org.apache.geronimo.javamail.authentication.SASLAuthenticator;
import org.apache.geronimo.javamail.util.CommandFailedException;
import org.apache.geronimo.javamail.util.InvalidCommandException;
/**
* Base class for all mail Store/Transport connection. Centralizes management
* of a lot of common connection handling. Actual protcol-specific
* functions are handled at the subclass level.
*/
public class MailConnection {
/**
* constants for EOL termination
*/
protected static final char CR = '\r';
protected static final char LF = '\n';
/**
* property keys for protocol properties.
*/
protected static final String MAIL_AUTH = "auth";
protected static final String MAIL_PORT = "port";
protected static final String MAIL_LOCALHOST = "localhost";
protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable";
protected static final String MAIL_SSL_ENABLE = "ssl.enable";
protected static final String MAIL_TIMEOUT = "timeout";
protected static final String MAIL_SASL_ENABLE = "sasl.enable";
protected static final String MAIL_SASL_REALM = "sasl.realm";
protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid";
protected static final String MAIL_SASL_MECHANISMS = "sasl.mechanisms";
protected static final String MAIL_PLAIN_DISABLE = "auth.plain.disable";
protected static final String MAIL_LOGIN_DISABLE = "auth.login.disable";
protected static final String MAIL_FACTORY_CLASS = "socketFactory.class";
protected static final String MAIL_FACTORY_FALLBACK = "socketFactory.fallback";
protected static final String MAIL_FACTORY_PORT = "socketFactory.port";
protected static final String MAIL_SSL_PROTOCOLS = "ssl.protocols";
protected static final String MAIL_SSL_CIPHERSUITES = "ssl.ciphersuites";
protected static final String MAIL_LOCALADDRESS = "localaddress";
protected static final String MAIL_LOCALPORT = "localport";
protected static final String MAIL_ENCODE_TRACE = "encodetrace";
protected static final int MIN_MILLIS = 1000 * 60;
protected static final int TIMEOUT = MIN_MILLIS * 5;
protected static final String DEFAULT_MAIL_HOST = "localhost";
protected static final String CAPABILITY_STARTTLS = "STARTTLS";
protected static final String AUTHENTICATION_PLAIN = "PLAIN";
protected static final String AUTHENTICATION_LOGIN = "LOGIN";
protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
// The mail Session we're associated with
protected Session session;
// The protocol we're implementing
protected String protocol;
// There are usually SSL and non-SSL versions of these protocols. This
// indicates which version we're using.
protected boolean sslConnection;
// This is the default port we should be using for making a connection. Each
// protocol (and each ssl version of the protocol) normally has a different default that
// should be used.
protected int defaultPort;
// a wrapper around our session to provide easier lookup of protocol
// specific property values
protected ProtocolProperties props;
// The target server host
protected String serverHost;
// The target server port
protected int serverPort;
// the connection socket...can be a plain socket or SSLSocket, if TLS is being used.
protected Socket socket;
// our local host name
protected InetAddress localAddress;
// our local port value
protected int localPort;
// our local host name
protected String localHost;
// our timeout value
protected int timeout;
// our login username
protected String username;
// our login password
protected String password;
// our SASL security realm
protected String realm;
// our authorization id
protected String authid;
// input stream used to read data. If Sasl is in use, this might be other than the
// direct access to the socket input stream.
protected InputStream inputStream;
// the other end of the connection pipeline.
protected OutputStream outputStream;
// our session provided debug output stream.
protected PrintStream debugStream;
// our debug flag (passed from the hosting transport)
protected boolean debug;
// list of authentication mechanisms supported by the server
protected List authentications;
// map of server extension arguments
protected Map capabilities;
// property list of authentication mechanisms
protected List mechanisms;
protected MailConnection(ProtocolProperties props)
{
// this is our properties retriever utility, which will look up
// properties based on the appropriate "mail.protocol." prefix.
// this also holds other information we might need for access, such as
// the protocol name and the Session;
this.props = props;
this.protocol = props.getProtocol();
this.session = props.getSession();
this.sslConnection = props.getSSLConnection();
this.defaultPort = props.getDefaultPort();
// initialize our debug settings from the session
debug = session.getDebug();
debugStream = session.getDebugOut();
}
/**
* Connect to the server and do the initial handshaking.
*
* @param host The target host name.
* @param port The target port
* @param username The connection username (can be null)
* @param password The authentication password (can be null).
*
* @return true if we were able to obtain a connection and
* authenticate.
* @exception MessagingException
*/
public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
// NOTE: We don't check for the username/password being null at this point. It's possible that
// the server will send back a PREAUTH response, which means we don't need to go through login
// processing. We'll need to check the capabilities response after we make the connection to decide
// if logging in is necesssary.
// save this for subsequent connections. All pool connections will use this info.
// if the port is defaulted, then see if we have something configured in the session.
// if not configured, we just use the default default.
if (port == -1) {
// check for a property and fall back on the default if it's not set.
port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
// it's possible that -1 might have been explicitly set, so one last check.
if (port == -1) {
port = props.getDefaultPort();
}
}
// Before we do anything, let's make sure that we succesfully received a host
if ( host == null ) {
host = DEFAULT_MAIL_HOST;
}
this.serverHost = host;
this.serverPort = port;
this.username = username;
this.password = password;
// make sure we have the realm information
realm = props.getProperty(MAIL_SASL_REALM);
// get an authzid value, if we have one. The default is to use the username.
authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
return true;
}
/**
* Establish a connection using an existing socket.
*
* @param s The socket to use.
*/
public void connect(Socket s) {
// just save the socket connection
this.socket = s;
}
/**
* Create a transport connection object and connect it to the
* target server.
*
* @exception MessagingException
*/
protected void getConnection() throws IOException, MessagingException
{
// We might have been passed a socket to connect with...if not, we need to create one of the correct type.
if (socket == null) {
// get the connection properties that control how we set this up.
getConnectionProperties();
// if this is the SSL version of the protocol, we start with an SSLSocket
if (sslConnection) {
getConnectedSSLSocket();
}
else
{
getConnectedSocket();
}
}
// if we already have a socket, get some information from it and override what we've been passed.
else {
localPort = socket.getPort();
localAddress = socket.getInetAddress();
}
// now set up the input/output streams.
getConnectionStreams();
}
/**
* Get common connection properties before creating a connection socket.
*/
protected void getConnectionProperties() {
// there are several protocol properties that can be set to tune the created socket. We need to
// retrieve those bits before creating the socket.
timeout = props.getIntProperty(MAIL_TIMEOUT, -1);
localAddress = null;
// see if we have a local address override.
String localAddrProp = props.getProperty(MAIL_LOCALADDRESS);
if (localAddrProp != null) {
try {
localAddress = InetAddress.getByName(localAddrProp);
} catch (UnknownHostException e) {
// not much we can do if this fails.
}
}
// check for a local port...default is to allow socket to choose.
localPort = props.getIntProperty(MAIL_LOCALPORT, 0);
}
/**
* Creates a connected socket
*
* @exception MessagingException
*/
protected void getConnectedSocket() throws IOException {
debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort);
// check the properties that control how we connect.
getConnectionProperties();
// the socket factory can be specified via a session property. By default, we just directly
// instantiate a socket without using a factory.
String socketFactory = props.getProperty(MAIL_FACTORY_CLASS);
// make sure the socket is nulled out to start
socket = null;
// if there is no socket factory defined (normal), we just create a socket directly.
if (socketFactory == null) {
socket = new Socket(serverHost, serverPort, localAddress, localPort);
}
else {
try {
int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
// we choose the port used by the socket based on overrides.
Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
// use the current context loader to resolve this.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class factoryClass = loader.loadClass(socketFactory);
// done indirectly, we need to invoke the method using reflection.
// This retrieves a factory instance.
Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
Object defFactory = getDefault.invoke(new Object(), new Object[0]);
// now that we have the factory, there are two different createSocket() calls we use,
// depending on whether we have a localAddress override.
if (localAddress != null) {
// retrieve the createSocket(String, int, InetAddress, int) method.
Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
}
else {
// retrieve the createSocket(String, int) method.
Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
Object[] createSocketArgs = new Object[] { serverHost, portArg };
socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
}
} catch (Throwable e) {
// if a socket factor is specified, then we may need to fall back to a default. This behavior
// is controlled by (surprise) more session properties.
if (props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false)) {
debugOut("First plain socket attempt failed, falling back to default factory", e);
socket = new Socket(serverHost, serverPort, localAddress, localPort);
}
// we have an exception. We're going to throw an IOException, which may require unwrapping
// or rewrapping the exception.
else {
// we have an exception from the reflection, so unwrap the base exception
if (e instanceof InvocationTargetException) {
e = ((InvocationTargetException)e).getTargetException();
}
debugOut("Plain socket creation failure", e);
// throw this as an IOException, with the original exception attached.
IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
ioe.initCause(e);
throw ioe;
}
}
}
// if we have a timeout value, set that before returning
if (timeout >= 0) {
socket.setSoTimeout(timeout);
}
}
/**
* Creates a connected SSL socket for an initial SSL connection.
*
* @exception MessagingException
*/
protected void getConnectedSSLSocket() throws IOException {
debugOut("Attempting SSL socket connection to server " + serverHost + ":" + serverPort);
// the socket factory can be specified via a protocol property, a session property, and if all else
// fails (which it usually does), we fall back to the standard factory class.
String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
// make sure this is null
socket = null;
// we'll try this with potentially two different factories if we're allowed to fall back.
boolean fallback = props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false);
while (true) {
try {
debugOut("Creating SSL socket using factory " + socketFactory);
int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
// we choose the port used by the socket based on overrides.
Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
// use the current context loader to resolve this.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class factoryClass = loader.loadClass(socketFactory);
// done indirectly, we need to invoke the method using reflection.
// This retrieves a factory instance.
Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
Object defFactory = getDefault.invoke(new Object(), new Object[0]);
// now that we have the factory, there are two different createSocket() calls we use,
// depending on whether we have a localAddress override.
if (localAddress != null) {
// retrieve the createSocket(String, int, InetAddress, int) method.
Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
break;
}
else {
// retrieve the createSocket(String, int) method.
Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
Object[] createSocketArgs = new Object[] { serverHost, portArg };
socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
break;
}
} catch (Throwable e) {
// if we're allowed to fallback, then use the default factory and try this again. We only
// allow this to happen once.
if (fallback) {
debugOut("First attempt at creating SSL socket failed, falling back to default factory");
socketFactory = "javax.net.ssl.SSLSocketFactory";
fallback = false;
continue;
}
// we have an exception. We're going to throw an IOException, which may require unwrapping
// or rewrapping the exception.
else {
// we have an exception from the reflection, so unwrap the base exception
if (e instanceof InvocationTargetException) {
e = ((InvocationTargetException)e).getTargetException();
}
debugOut("Failure creating SSL socket", e);
// throw this as an IOException, with the original exception attached.
IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
ioe.initCause(e);
throw ioe;
}
}
}
// and set the timeout value
if (timeout >= 0) {
socket.setSoTimeout(timeout);
}
// if there is a list of protocols specified, we need to break this down into
// the individual names
String protocols = props.getProperty(MAIL_SSL_PROTOCOLS);
if (protocols != null) {
ArrayList list = new ArrayList();
StringTokenizer t = new StringTokenizer(protocols);
while (t.hasMoreTokens()) {
list.add(t.nextToken());
}
((SSLSocket)socket).setEnabledProtocols((String[])list.toArray(new String[list.size()]));
}
// and do the same for any cipher suites
String suites = props.getProperty(MAIL_SSL_CIPHERSUITES);
if (suites != null) {
ArrayList list = new ArrayList();
StringTokenizer t = new StringTokenizer(suites);
while (t.hasMoreTokens()) {
list.add(t.nextToken());
}
((SSLSocket)socket).setEnabledCipherSuites((String[])list.toArray(new String[list.size()]));
}
}
/**
* Switch the connection to using TLS level security,
* switching to an SSL socket.
*/
protected void getConnectedTLSSocket() throws MessagingException {
// it worked, now switch the socket into TLS mode
try {
// we use the same target and port as the current connection.
String host = socket.getInetAddress().getHostName();
int port = socket.getPort();
// the socket factory can be specified via a session property. By default, we use
// the native SSL factory.
String socketFactory = props.getProperty(MAIL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
// use the current context loader to resolve this.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class factoryClass = loader.loadClass(socketFactory);
// done indirectly, we need to invoke the method using reflection.
// This retrieves a factory instance.
Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
Object defFactory = getDefault.invoke(new Object(), new Object[0]);
// now we need to invoke createSocket()
Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
// and finally create the socket
Socket sslSocket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
// if this is an instance of SSLSocket (very common), try setting the protocol to be
// "TLSv1". If this is some other class because of a factory override, we'll just have to
// accept that things will work.
if (sslSocket instanceof SSLSocket) {
((SSLSocket)sslSocket).setEnabledProtocols(new String[] {"TLSv1"} );
((SSLSocket)sslSocket).setUseClientMode(true);
debugOut("Initiating STARTTLS handshake");
((SSLSocket)sslSocket).startHandshake();
}
// this is our active socket now
socket = sslSocket;
getConnectionStreams();
debugOut("TLS connection established");
}
catch (Exception e) {
debugOut("Failure attempting to convert connection to TLS", e);
throw new MessagingException("Unable to convert connection to SSL", e);
}
}
/**
* Set up the input and output streams for server communications once the
* socket connection has been made.
*
* @exception MessagingException
*/
protected void getConnectionStreams() throws MessagingException, IOException {
// and finally, as a last step, replace our input streams with the secure ones.
// now set up the input/output streams.
inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, props.getBooleanProperty(
MAIL_ENCODE_TRACE, false));
outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, props.getBooleanProperty(
MAIL_ENCODE_TRACE, false));
}
/**
* Close the server connection at termination.
*/
public void closeServerConnection()
{
try {
socket.close();
} catch (IOException ignored) {
}
socket = null;
inputStream = null;
outputStream = null;
}
/**
* Verify that we have a good connection before
* attempting to send a command.
*
* @exception MessagingException
*/
protected void checkConnected() throws MessagingException {
if (socket == null || !socket.isConnected()) {
throw new MessagingException("no connection");
}
}
/**
* Retrieve the SASL realm used for DIGEST-MD5 authentication.
* This will either be explicitly set, or retrieved using the
* mail.imap.sasl.realm session property.
*
* @return The current realm information (which can be null).
*/
public String getSASLRealm() {
// if the realm is null, retrieve it using the realm session property.
if (realm == null) {
realm = props.getProperty(MAIL_SASL_REALM);
}
return realm;
}
/**
* Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
*
* @param name The new realm name.
*/
public void setSASLRealm(String name) {
realm = name;
}
/**
* Get a list of the SASL mechanisms we're configured to accept.
*
* @return A list of mechanisms we're allowed to use.
*/
protected List getSaslMechanisms() {
if (mechanisms == null) {
mechanisms = new ArrayList();
String mechList = props.getProperty(MAIL_SASL_MECHANISMS);
if (mechList != null) {
// the mechanisms are a blank or comma-separated list
StringTokenizer tokenizer = new StringTokenizer(mechList, " ,");
while (tokenizer.hasMoreTokens()) {
String mech = tokenizer.nextToken().toUpperCase();
mechanisms.add(mech);
}
}
}
return mechanisms;
}
/**
* Get the list of authentication mechanisms the server
* is supposed to support.
*
* @return A list of the server supported authentication
* mechanisms.
*/
protected List getServerMechanisms() {
return authentications;
}
/**
* Merge the configured SASL mechanisms with the capabilities that the
* server has indicated it supports, returning a merged list that can
* be used for selecting a mechanism.
*
* @return A List representing the intersection of the configured list and the
* capabilities list.
*/
protected List selectSaslMechanisms() {
List configured = getSaslMechanisms();
List supported = getServerMechanisms();
// if not restricted, then we'll select from anything supported.
if (configured.isEmpty()) {
return supported;
}
List merged = new ArrayList();
// we might need a subset of the supported ones
for (int i = 0; i < configured.size(); i++) {
// if this is in both lists, add to the merged one.
String mech = (String)configured.get(i);
if (supported.contains(mech)) {
merged.add(mech);
}
}
return merged;
}
/**
* Process SASL-type authentication.
*
* @return An authenticator to process the login challenge/response handling.
* @exception MessagingException
*/
protected ClientAuthenticator getLoginAuthenticator() throws MessagingException {
// get the list of mechanisms we're allowed to use.
List mechs = selectSaslMechanisms();
try {
String[] mechArray = (String[])mechs.toArray(new String[0]);
// create a SASLAuthenticator, if we can. A failure likely indicates we're not
// running on a Java 5 VM, and the Sasl API isn't available.
return new SASLAuthenticator(mechArray, session.getProperties(), protocol, serverHost, getSASLRealm(), authid, username, password);
} catch (Throwable e) {
}
// now go through the progression of mechanisms we support, from the most secure to the
// least secure.
if (mechs.contains(AUTHENTICATION_DIGESTMD5)) {
return new DigestMD5Authenticator(serverHost, username, password, getSASLRealm());
}
else if (mechs.contains(AUTHENTICATION_CRAMMD5)) {
return new CramMD5Authenticator(username, password);
}
else if (mechs.contains(AUTHENTICATION_LOGIN)) {
return new LoginAuthenticator(username, password);
}
else if (mechs.contains(AUTHENTICATION_PLAIN)) {
return new PlainAuthenticator(username, password);
}
else {
// can't find a mechanism we support in common
return null;
}
}
/**
* Internal debug output routine.
*
* @param value The string value to output.
*/
protected void debugOut(String message) {
if (debug) {
debugStream.println(protocol + " DEBUG: " + message);
}
}
/**
* Internal debugging routine for reporting exceptions.
*
* @param message A message associated with the exception context.
* @param e The received exception.
*/
protected void debugOut(String message, Throwable e) {
if (debug) {
debugOut("Received exception -> " + message);
debugOut("Exception message -> " + e.getMessage());
e.printStackTrace(debugStream);
}
}
/**
* Test if this connection has a given capability.
*
* @param capability The capability name.
*
* @return true if this capability is in the list, false for a mismatch.
*/
public boolean hasCapability(String capability) {
return capabilities.containsKey(capability);
}
/**
* Get the capabilities map.
*
* @return The capabilities map for the connection.
*/
public Map getCapabilities() {
return capabilities;
}
/**
* Test if the server supports a given mechanism.
*
* @param mech The mechanism name.
*
* @return true if the server has asserted support for the named
* mechanism.
*/
public boolean supportsMechanism(String mech) {
return authentications.contains(mech);
}
/**
* Retrieve the connection host.
*
* @return The host name.
*/
public String getHost() {
return serverHost;
}
/**
* Retrieve the local client host name.
*
* @return The string version of the local host name.
* @exception SMTPTransportException
*/
public String getLocalHost() throws MessagingException {
if (localHost == null) {
try {
localHost = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
// fine, we're misconfigured - ignore
}
if (localHost == null) {
localHost = props.getProperty(MAIL_LOCALHOST);
}
if (localHost == null) {
localHost = props.getSessionProperty(MAIL_LOCALHOST);
}
if (localHost == null) {
throw new MessagingException("Can't get local hostname. "
+ " Please correctly configure JDK/DNS or set mail.smtp.localhost");
}
}
return localHost;
}
/**
* Explicitly set the local host information.
*
* @param localHost
* The new localHost name.
*/
public void setLocalHost(String localHost) {
this.localHost = localHost;
}
}
././@LongLink 0000000 0000000 0000000 00000000161 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CommandFailedException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CommandFailedEx0000664 0001750 0001750 00000002267 10716317503 032167 0 ustar brian brian /**
* 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.util;
import javax.mail.MessagingException;
public class CommandFailedException extends MessagingException {
public CommandFailedException() {
super();
}
public CommandFailedException(String message) {
super(message);
}
public CommandFailedException(String message, Exception cause) {
super(message, cause);
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceInputStrea0000664 0001750 0001750 00000006517 11025715072 032263 0 ustar brian brian /*
* 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.util;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.geronimo.mail.util.QuotedPrintableEncoderStream;
/**
* @version $Rev: 668614 $ $Date: 2008-06-17 07:04:26 -0400 (Tue, 17 Jun 2008) $
*/
public class TraceInputStream extends FilterInputStream {
// the current debug setting
protected boolean debug = false;
// the target trace output stream.
protected OutputStream traceStream;
/**
* Construct a debug trace stream.
*
* @param in
* The source input stream.
* @param traceStream
* The side trace stream to which trace data gets written.
* @param encode
* Indicates whether we wish the Trace data to be Q-P encoded.
*/
public TraceInputStream(InputStream in, OutputStream traceStream, boolean debug, boolean encode) {
super(in);
this.debug = debug;
if (encode) {
this.traceStream = new QuotedPrintableEncoderStream(traceStream);
} else {
this.traceStream = traceStream;
}
}
/**
* Set the current setting of the debug trace stream debug flag.
*
* @param d
* The new debug flag settings.
*/
public void setDebug(boolean d) {
debug = d;
}
/**
* Reads up to len bytes of data from this input stream, placing them directly
* into the provided byte array.
*
* @param b the provided data buffer.
* @param off the starting offset within the buffer for placing the data.
* @param len the maximum number of bytes to read.
* @return that number of bytes that have been read and copied into the
* buffer or -1 if an end of stream occurs.
* @exception IOException for any I/O errors.
*/
public int read(byte b[], int off, int len) throws IOException {
int count = in.read(b, off, len);
if (debug && count > 0) {
traceStream.write(b, off, count);
}
return count;
}
/**
* Read the next byte of data from the input stream, returning it as an
* int value. Returns -1 if the end of stream is detected.
*
* @return The next byte of data or -1 to indicate end-of-stream.
* @exception IOException for any I/O errors
*/
public int read() throws IOException {
int b = in.read();
if (debug) {
traceStream.write(b);
}
return b;
}
}
././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader0000664 0001750 0001750 00000011301 10721056121 032056 0 ustar brian brian /*
* 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.util;
import java.io.IOException;
import java.io.Reader;
/**
* An implementation of an OutputStream that performs MIME linebreak
* canonicalization and "byte-stuff" so that data content does not get mistaken
* for a message data-end marker (CRLF.CRLF)l
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class MIMEInputReader extends Reader {
// the wrappered output stream.
protected Reader source;
// a flag to indicate we've just processed a line break. This is used for
// byte stuffing purposes. This
// is initially true, because if the first character of the content is a
// period, we need to byte-stuff
// immediately.
protected boolean atLineBreak = true;
// we've hit the terminating marker on the data
protected boolean endOfData = false;
/**
* Create an input reader that reads from the source input reader
*
* @param out
* The wrapped Reader
*/
public MIMEInputReader(Reader source) {
this.source = source;
}
/**
* Concrete implementation of the Reader read()
* abstract method. This appears to be the only
* abstract method, so all of the other reads must
* funnel through this method.
*
* @param buffer The buffer to fill.
* @param off The offset to start adding characters.
* @param len The number of requested characters.
*
* @return The actual count of characters read. Returns -1
* if we hit an EOF without reading any characters.
* @exception IOException
*/
public int read(char buffer[], int off, int len) throws IOException {
// we've been asked for nothing, we'll return nothing.
if (len == 0) {
return 0;
}
// have we hit the end of data? Return a -1 indicator
if (endOfData) {
return -1;
}
// number of bytes read
int bytesRead = 0;
int lastRead;
while (bytesRead < len && (lastRead = source.read()) >= 0) {
// We are checking for the end of a multiline response
// the format is .CRLF
// we also have to check for byte-stuffing situation
// where we remove a leading period.
if (atLineBreak && lastRead == '.') {
// step to the next character
lastRead = source.read();
// we have ".CR"...this is our end of stream
// marker. Consume the LF from the reader and return
if (lastRead == '\r') {
source.read();
// no more reads from this point.
endOfData = true;
break;
}
// the next character SHOULD be a ".". We swallow the first
// dot and just write the next character to the buffer
atLineBreak = false;
}
else if (lastRead == '\n') {
// hit an end-of-line marker?
// remember we just had a line break
atLineBreak = true;
}
else
{
// something other than a line break character
atLineBreak = false;
}
// add the character to the buffer
buffer[off++] = (char)lastRead;
bytesRead++;
}
// we must have had an EOF condition of some sort
if (bytesRead == 0) {
return -1;
}
// return the actual length read in
return bytesRead;
}
/**
* Close the stream. This is a NOP for this stream.
*
* @exception IOException
*/
public void close() throws IOException {
// does nothing
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandE0000664 0001750 0001750 00000002273 10716317503 032176 0 ustar brian brian /**
* 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.util;
import javax.mail.MessagingException;
public class InvalidCommandException extends MessagingException {
public InvalidCommandException() {
super();
}
public InvalidCommandException(String message) {
super(message);
}
public InvalidCommandException(String message, Exception cause) {
super(message, cause);
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatE0000664 0001750 0001750 00000002272 10716317503 032257 0 ustar brian brian /**
* 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.util;
import javax.mail.MessagingException;
public class ResponseFormatException extends MessagingException {
public ResponseFormatException() {
super();
}
public ResponseFormatException(String message) {
super(message);
}
public ResponseFormatException(String message, Exception cause) {
super(message, cause);
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CountingOutputStream.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CountingOutputS0000664 0001750 0001750 00000003351 11030212510 032312 0 ustar brian brian /*
* 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.util;
import java.io.IOException;
import java.io.OutputStream;
/**
* An implementation of an OutputStream just counts
* the number of bytes written to the stream.
* @version $Rev: 671217 $ $Date: 2008-06-24 11:39:52 -0400 (Tue, 24 Jun 2008) $
*/
public class CountingOutputStream extends OutputStream {
// the counting accumulator
int count = 0;
// in order for this to work, we only need override the single character
// form, as the others
// funnel through this one by default.
public void write(int ch) throws IOException {
// just increment the count
count++;
}
/**
* Get the current accumulator total for this stream.
*
* @return The current count.
*/
public int getCount() {
return count;
}
/**
* Reset the counter to zero.
*/
public void reset() {
count = 0;
}
}
././@LongLink 0000000 0000000 0000000 00000000154 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStre0000664 0001750 0001750 00000004760 11025715252 032321 0 ustar brian brian /*
* 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.util;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.geronimo.mail.util.QuotedPrintableEncoderStream;
/**
* @version $Rev: 668615 $ $Date: 2008-06-17 07:06:18 -0400 (Tue, 17 Jun 2008) $
*/
public class TraceOutputStream extends FilterOutputStream {
// the current debug setting
protected boolean debug = false;
// the target trace output stream.
protected OutputStream traceStream;
/**
* Construct a debug trace stream.
*
* @param out
* The target out put stream.
* @param traceStream
* The side trace stream to which trace data gets written.
* @param encode
* Indicates whether we wish the Trace data to be Q-P encoded.
*/
public TraceOutputStream(OutputStream out, OutputStream traceStream, boolean debug, boolean encode) {
super(out);
this.debug = debug;
if (encode) {
this.traceStream = new QuotedPrintableEncoderStream(traceStream);
} else {
this.traceStream = traceStream;
}
}
/**
* Set the current setting of the debug trace stream debug flag.
*
* @param d
* The new debug flag settings.
*/
public void setDebug(boolean d) {
debug = d;
}
/**
* Write a single byte to the output stream.
*
* @param b The byte to be written.
*
* @exception IOException
* thrown for any I/O errors.
*/
public void write(int b) throws IOException {
if (debug) {
traceStream.write(b);
}
super.write(b);
}
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ConnectionException.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ConnectionExcep0000664 0001750 0001750 00000002253 10716317503 032266 0 ustar brian brian /**
* 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.util;
import javax.mail.MessagingException;
public class ConnectionException extends MessagingException {
public ConnectionException() {
super();
}
public ConnectionException(String message) {
super(message);
}
public ConnectionException(String message, Exception cause) {
super(message, cause);
}
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ProtocolPropert0000664 0001750 0001750 00000022012 10716317503 032352 0 ustar brian brian /**
* 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.util;
import java.util.Properties;
import javax.mail.Session;
/**
* Interface for providing access to protocol specific properties to
* utility classes.
*/
public class ProtocolProperties {
// the protocol we're working with.
protected String protocol;
// a preconstructed prefix string to reduce concatenation operations.
protected String protocolPrefix;
// the Session that's the source of all of the properties
protected Session session;
// the sslConnection property. This indicates this protocol is to use SSL for
// all communications with the server.
protected boolean sslConnection;
// the default port property. The default port differs with the protocol
// and the sslConnection property.
protected int defaultPort;
public ProtocolProperties(Session session, String protocol, boolean sslConnection, int defaultPort) {
this.session = session;
this.protocol = protocol;
this.sslConnection = sslConnection;
this.defaultPort = defaultPort;
// this helps avoid a lot of concatentates when retrieving properties.
protocolPrefix = "mail." + protocol + ".";
}
/**
* Retrieve the Session associated with this property bundle instance.
*
* @return A Session object that's the source of the accessed properties.
*/
public Session getSession() {
return session;
}
/**
* Retrieve the name of the protocol used to access properties.
*
* @return The protocol String name.
*/
public String getProtocol() {
return protocol;
}
/**
* Retrieve the SSL Connection flag for this protocol;
*
* @return true if an SSL connection is required, false otherwise.
*/
public boolean getSSLConnection() {
return sslConnection;
}
/**
* Return the default port to use with this connection.
*
* @return The default port value.
*/
public int getDefaultPort() {
return defaultPort;
}
/**
* Get a property associated with this mail protocol.
*
* @param name The name of the property.
*
* @return The property value (returns null if the property has not been set).
*/
public String getProperty(String name) {
// the name we're given is the least qualified part of the name.
// We construct the full property name
// using the protocol
String fullName = protocolPrefix + name;
return session.getProperty(fullName);
}
/**
* Get a property associated with this mail session. Returns
* the provided default if it doesn't exist.
*
* @param name The name of the property.
* @param defaultValue
* The default value to return if the property doesn't exist.
*
* @return The property value (returns defaultValue if the property has not been set).
*/
public String getProperty(String name, String defaultValue) {
// the name we're given is the least qualified part of the name.
// We construct the full property name
// using the protocol
String fullName = protocolPrefix + name;
String value = session.getProperty(fullName);
if (value == null) {
value = defaultValue;
}
return value;
}
/**
* Get a property associated with this mail session as an integer value. Returns
* the default value if the property doesn't exist or it doesn't have a valid int value.
*
* @param name The name of the property.
* @param defaultValue
* The default value to return if the property doesn't exist.
*
* @return The property value converted to an int.
*/
public int getIntProperty(String name, int defaultValue)
{
// retrieve the property
String value = getProperty(name);
// return the default value if not set.
if (value == null) {
return defaultValue;
}
return Integer.parseInt(value);
}
/**
* Get a property associated with this mail session as an boolean value. Returns
* the default value if the property doesn't exist or it doesn't have a valid int value.
*
* @param name The name of the property.
* @param defaultValue
* The default value to return if the property doesn't exist.
*
* @return The property value converted to a boolean
*/
public boolean getBooleanProperty(String name, boolean defaultValue)
{
// retrieve the property
String value = getProperty(name);
// return the default value if not set.
if (value == null) {
return defaultValue;
}
// just do a single test for true.
if ("true".equals(value)) {
return true;
}
// return false for anything other than true
return false;
}
/**
* Get a property associated with this mail session. Session
* properties all begin with "mail."
*
* @param name The name of the property.
*
* @return The property value (returns null if the property has not been set).
*/
public String getSessionProperty(String name) {
// the name we're given is the least qualified part of the name.
// We construct the full property name
// using the protocol
String fullName = "mail." + name;
return session.getProperty(fullName);
}
/**
* Get a property associated with this mail session. Returns
* the provided default if it doesn't exist.
*
* @param name The name of the property.
* @param defaultValue
* The default value to return if the property doesn't exist.
*
* @return The property value (returns defaultValue if the property has not been set).
*/
public String getSessionProperty(String name, String defaultValue) {
// the name we're given is the least qualified part of the name.
// We construct the full property name
// using the protocol
String fullName = "mail." + name;
String value = session.getProperty(fullName);
if (value == null) {
value = defaultValue;
}
return value;
}
/**
* Get a property associated with this mail session as an integer value. Returns
* the default value if the property doesn't exist or it doesn't have a valid int value.
*
* @param name The name of the property.
* @param defaultValue
* The default value to return if the property doesn't exist.
*
* @return The property value converted to an int.
*/
public int getIntSessionProperty(String name, int defaultValue)
{
// retrieve the property
String value = getSessionProperty(name);
// return the default value if not set.
if (value == null) {
return defaultValue;
}
return Integer.parseInt(value);
}
/**
* Get a property associated with this mail session as an boolean value. Returns
* the default value if the property doesn't exist or it doesn't have a valid int value.
*
* @param name The name of the property.
* @param defaultValue
* The default value to return if the property doesn't exist.
*
* @return The property value converted to a boolean
*/
public boolean getBooleanSessionProperty(String name, boolean defaultValue)
{
// retrieve the property
String value = getSessionProperty(name);
// return the default value if not set.
if (value == null) {
return defaultValue;
}
// just do a single test for true.
if ("true".equals(value)) {
return true;
}
// return false for anything other than true
return false;
}
/**
* Get the complete set of properties associated with this Session.
*
* @return The Session properties bundle.
*/
public Properties getProperties() {
return session.getProperties();
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/ 0000775 0001750 0001750 00000000000 11703373730 027435 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/ 0000775 0001750 0001750 00000000000 11703373730 030414 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000160 00000000000 011562 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroup0000664 0001750 0001750 00000032012 11051310624 032116 0 ustar brian brian /*
* 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.nntp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.mail.FetchProfile;
import javax.mail.FolderNotFoundException;
import javax.mail.Message;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
/**
* The NNTP implementation of the javax.mail.Folder Note that only INBOX is
* supported in NNTP
*
* http://www.faqs.org/rfcs/rfc1939.html
*
*
* @see javax.mail.Folder
*
* @version $Rev: 686231 $ $Date: 2008-08-15 10:24:20 -0400 (Fri, 15 Aug 2008) $
*/
public class NNTPGroupFolder extends NNTPFolder {
// holders for status information returned by the GROUP command.
protected int firstArticle = -1;
protected int lastArticle = -1;
// retrieved articles, mapped by article number.
Map articles;
// information stored in the newsrc group.
NNTPNewsrcGroup groupInfo;
/**
* Construct a "real" folder representing an NNTP news group.
*
* @param parent
* The parent root folder.
* @param store
* The Store this folder is attached to.
* @param name
* The folder name.
* @param groupInfo
* The newsrc group information attached to the newsrc database.
* This contains subscription and article "SEEN" information.
*/
protected NNTPGroupFolder(NNTPRootFolder parent, NNTPStore store, String name, NNTPNewsrcGroup groupInfo) {
super(store);
// the name and the full name are the same.
this.name = name;
this.fullName = name;
// set the parent appropriately.
this.parent = parent = parent;
this.groupInfo = groupInfo;
}
/**
* Ping the server and update the group count, first, and last information.
*
* @exception MessagingException
*/
private void updateGroupStats() throws MessagingException {
// ask the server for information about the group. This is a one-line
// reponse with status on
// the group, if it exists.
NNTPReply reply = connection.sendCommand("GROUP " + name);
// explicitly not there?
if (reply.getCode() == NNTPReply.NO_SUCH_NEWSGROUP) {
throw new FolderNotFoundException(this, "Folder does not exist on server: " + reply);
} else if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
throw new MessagingException("Error requesting group information: " + reply);
}
// we've gotten back a good response, now parse out the group specifics
// from the
// status response.
StringTokenizer tokenizer = new StringTokenizer(reply.getMessage());
// we should have a least 3 tokens here, in the order "count first
// last".
// article count
if (tokenizer.hasMoreTokens()) {
String count = tokenizer.nextToken();
try {
messageCount = Integer.parseInt(count);
} catch (NumberFormatException e) {
// ignore
}
}
// first article number
if (tokenizer.hasMoreTokens()) {
String first = tokenizer.nextToken();
try {
firstArticle = Integer.parseInt(first);
} catch (NumberFormatException e) {
// ignore
}
}
// last article number.
if (tokenizer.hasMoreTokens()) {
String last = tokenizer.nextToken();
try {
lastArticle = Integer.parseInt(last);
} catch (NumberFormatException e) {
// ignore
}
}
}
/**
* Test to see if this folder actually exists. This pings the server for
* information about the GROUP and updates the article count and index
* information.
*
* @return true if the newsgroup exists on the server, false otherwise.
* @exception MessagingException
*/
public boolean exists() throws MessagingException {
try {
// update the group statistics. If the folder doesn't exist, we'll
// get an exception that we
// can turn into a false reply.
updateGroupStats();
// updated ok, so it must be there.
return true;
} catch (FolderNotFoundException e) {
return false;
}
}
/**
* Ping the NNTP server to check if a newsgroup has any new messages.
*
* @return True if the server has new articles from the last time we
* checked. Also returns true if this is the first time we've
* checked.
* @exception MessagingException
*/
public boolean hasNewMessages() throws MessagingException {
int oldLast = lastArticle;
updateGroupStats();
return lastArticle > oldLast;
}
/**
* Open the folder for use. This retrieves article count information from
* the server.
*
* @exception MessagingException
*/
protected void openFolder() throws MessagingException {
// update the group specifics, especially the message count.
updateGroupStats();
// get a cache for retrieved articles
articles = new HashMap();
}
/**
* Close the folder, which also clears out the article caches.
*
* @exception MessagingException
*/
public void closeFolder() throws MessagingException {
// get ride of any retrieve articles, and flip over the open for
// business sign.
articles = null;
}
/**
* Checks wether the message is in cache, if not will create a new message
* object and return it.
*
* @see javax.mail.Folder#getMessage(int)
*/
public Message getMessage(int msgNum) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
// get an object form to look up in the retrieve messages list (oh how I
// wish there was
// something like Map that could use integer keys directly!).
Integer key = new Integer(msgNum);
NNTPMessage message = (NNTPMessage) articles.get(key);
if (message != null) {
// piece of cake!
return message;
}
// we need to suck a message down from the server.
// but first, make sure the group is still valid.
updateGroupStats();
// just send a STAT command to this message. Right now, all we want is
// existance proof. We'll
// retrieve the other bits when requested.
NNTPReply reply = connection.sendCommand("STAT " + Integer.toString(msgNum));
if (reply.getCode() != NNTPReply.REQUEST_TEXT_SEPARATELY) {
throw new MessagingException("Error retrieving article from NNTP server: " + reply);
}
// we need to parse out the message id.
String response = reply.getMessage();
int idStart = response.indexOf('<');
int idEnd = response.indexOf('>');
// NB: The "<" and ">" delimiters are required elements of the message id, not just
// delimiters for the sake of the command. We need to keep these around
message = new NNTPMessage(this, (NNTPStore) store, msgNum, response.substring(idStart, idEnd + 1));
// add this to the article cache.
articles.put(key, message);
return message;
}
/**
* Retrieve all articles in the group.
*
* @return An array of all messages in the group.
*/
public Message[] getMessages() throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
// we're going to try first with XHDR, which will allow us to retrieve
// everything in one shot. If that
// fails, we'll fall back on issing STAT commands for the entire article
// range.
NNTPReply reply = connection.sendCommand("XHDR Message-ID " + Integer.toString(firstArticle) + "-"
+ Integer.toString(lastArticle), NNTPReply.HEAD_FOLLOWS);
List messages = new ArrayList();
if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
List lines = reply.getData();
for (int i = 0; i < lines.size(); i++) {
String line = (String) lines.get(i);
try {
int pos = line.indexOf(' ');
int articleID = Integer.parseInt(line.substring(0, pos));
String messageID = line.substring(pos + 1);
Integer key = new Integer(articleID);
// see if we have this message cached, If not, create it.
Message message = (Message)articles.get(key);
if (message == null) {
message = new NNTPMessage(this, (NNTPStore) store, key.intValue(), messageID);
articles.put(key, message);
}
messages.add(message);
} catch (NumberFormatException e) {
// should never happen, but just skip this entry if it does.
}
}
} else {
// grumble, we need to stat each article id to see if it
// exists....lots of round trips.
for (int i = firstArticle; i <= lastArticle; i++) {
try {
messages.add(getMessage(i));
} catch (MessagingException e) {
// just assume if there is an error, it's because the
// message id doesn't exist.
}
}
}
return (Message[]) messages.toArray(new Message[0]);
}
/**
* @see javax.mail.Folder#fetch(javax.mail.Message[],
* javax.mail.FetchProfile)
*
* The JavaMail API recommends that this method be overrident to provide a
* meaningfull implementation.
*/
public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
for (int i = 0; i < msgs.length; i++) {
Message msg = msgs[i];
// we can only perform this operation for NNTPMessages.
if (msg == null || !(msg instanceof NNTPMessage)) {
// we can't fetch if it's the wrong message type
continue;
}
// fetching both the headers and body?
if (fp.contains(FetchProfile.Item.ENVELOPE) && fp.contains(FetchProfile.Item.CONTENT_INFO)) {
// retrive everything
((NNTPMessage) msg).loadArticle();
}
// headers only?
else if (fp.contains(FetchProfile.Item.ENVELOPE)) {
((NNTPMessage) msg).loadHeaders();
} else if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
((NNTPMessage) msg).loadContent();
}
}
}
/**
* Return the subscription status of this folder.
*
* @return true if the folder is marked as subscribed, false for
* unsubscribed.
*/
public boolean isSubscribed() {
return groupInfo.isSubscribed();
}
/**
* Set or clear the subscription status of a file.
*
* @param flag
* The new subscription state.
*/
public void setSubscribed(boolean flag) {
groupInfo.setSubscribed(flag);
}
/**
* Return the "seen" state for an article in a folder.
*
* @param article
* The article number.
*
* @return true if the article is marked as seen in the newsrc file, false
* for unseen files.
*/
public boolean isSeen(int article) {
return groupInfo.isArticleSeen(article);
}
/**
* Set the seen state for an article in a folder.
*
* @param article
* The article number.
* @param flag
* The new seen state.
*/
public void setSeen(int article, boolean flag) {
if (flag) {
groupInfo.markArticleSeen(article);
} else {
groupInfo.markArticleUnseen(article);
}
}
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLStore.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLSt0000664 0001750 0001750 00000003062 11032465542 032006 0 ustar brian brian /**
* 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.nntp;
import javax.mail.Session;
import javax.mail.URLName;
/**
* NNTP implementation of javax.mail.Store over an SSL connection.
*
* @version $Rev: 673152 $ $Date: 2008-07-01 13:37:38 -0400 (Tue, 01 Jul 2008) $
*/
public class NNTPSSLStore extends NNTPStore {
/**
* Construct an NNTPSSLStore item.
*
* @param session The owning javamail Session.
* @param urlName The Store urlName, which can contain server target information.
*/
public NNTPSSLStore(Session session, URLName urlName) {
// we're the imaps protocol, our default connection port is 563, and we must use
// an SSL connection for the initial hookup
super(session, urlName, "nntps", DEFAULT_NNTP_SSL_PORT, true);
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolde0000664 0001750 0001750 00000032157 10474735322 032103 0 ustar brian brian /*
* 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.nntp;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.IllegalWriteException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Session;
import javax.mail.event.ConnectionEvent;
import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
/**
* The base NNTP implementation of the javax.mail.Folder This is a base class
* for both the Root NNTP server and each NNTP group folder.
*
* @see javax.mail.Folder
*
* @version $Rev: 437941 $
*/
public class NNTPFolder extends Folder {
// our active connection.
protected NNTPConnection connection;
// our attached session
protected Session session;
// the name of this folder (either the name of the server for the root or
// the news group name).
protected String name;
// the "full" name of the folder. For the root folder, this is the name
// returned by the connection
// welcome string. Otherwise, this is the same as the name.
protected String fullName;
// the parent folder. For the root folder, this is null. For a group folder,
// this is the root.
protected Folder parent;
// the folder open state
protected boolean folderOpen = false;
// the folder message count. For the root folder, this is always 0.
protected int messageCount = 0;
// the persistent flags we save in the store (basically just the SEEN flag).
protected Flags permanentFlags;
/**
* Super class constructor the base NNTPFolder class.
*
* @param store
* The javamail store this folder is attached to.
*/
protected NNTPFolder(NNTPStore store) {
super(store);
// get the active connection from the store...all commands are sent
// there
this.connection = store.getConnection();
this.session = store.getSession();
// set up our permanent flags bit.
permanentFlags = new Flags();
permanentFlags.add(Flags.Flag.SEEN);
}
/**
* Retrieve the folder name.
*
* @return The folder's name.
*/
public String getName() {
return name;
}
/**
* Retrieve the folder's full name (including hierarchy information). NNTP
* folders are flat, so the full name is generally the same as the name.
*
* @return The full name value.
*/
public String getFullName() {
return fullName;
}
/**
* Returns the parent folder for this folder. Returns null if this is the
* root folder.
*/
public Folder getParent() throws MessagingException {
return parent;
}
/**
* Indicated whether the folder "exists" or not. Existance in this context
* indicates that the group still exists on the server.
*
* @return
* @exception MessagingException
*/
public boolean exists() throws MessagingException {
// by default, return true. This is really only the case for the root.
// The group folder will
// need to override this.
return true;
}
/**
* List the subfolders. For group folders, this is a meaningless so we throw
* a MethodNotSupportedException.
*
* @param pattern
* The folder pattern string.
*
* @return Never returns.
* @exception MessagingException
*/
public Folder[] list(String pattern) throws MessagingException {
throw new MethodNotSupportedException("NNTP group folders cannot contain sub folders");
}
/**
* Retrieve the list of subscribed folders that match the given pattern
* string.
*
* @param pattern
* The pattern string used for the matching
*
* @return An array of matching folders from the subscribed list.
*/
public Folder[] listSubscribed(String pattern) throws MessagingException {
throw new MethodNotSupportedException("NNTP group folders cannot contain sub folders");
}
/**
* No sub folders, hence there is no notion of a seperator. We return a null
* character (consistent with what Sun returns for POP3 folders).
*/
public char getSeparator() throws MessagingException {
return '\0';
}
/**
* Return whether this folder can hold just messages or also subfolders.
* Only the root folder can hold other folders, so it will need to override.
*
* @return Either Folder.HOLDS_MESSAGES or Folder.HOLDS_FOLDERS.
* @exception MessagingException
*/
public int getType() throws MessagingException {
return HOLDS_MESSAGES;
}
/**
* Create a new folder. NNTP folders are read only, so this is a nop.
*
* @param type
* The type of folder.
*
* @return Not support, throws an exception.
* @exception MessagingException
*/
public boolean create(int type) throws MessagingException {
throw new MethodNotSupportedException("Sub folders cannot be created in NNTP");
}
/**
* Check for new messages. We always return false for the root folder. The
* group folders will need to override.
*
* @return Always returns false.
* @exception MessagingException
*/
public boolean hasNewMessages() throws MessagingException {
return false;
}
/**
* Get a named subfolder from this folder. This only has meaning from the
* root NNTP folder.
*
* @param name
* The requested name.
*
* @return If the folder exists, returns a Folder object representing the
* named folder.
* @exception MessagingException
*/
public Folder getFolder(String name) throws MessagingException {
throw new MethodNotSupportedException("NNTP Group folders do not support sub folders");
}
/**
* Delete a folder. This is not supported for NNTP.
*
* @param recurse
* The recusion flag.
*
* @return Never returns.
* @exception MessagingException
*/
public boolean delete(boolean recurse) throws MessagingException {
throw new MethodNotSupportedException("Deleting of NNTP folders is not supported");
}
/**
* Rename a folder. Not supported for NNTP folders.
*
* @param f
* The new folder specifying the rename location.
*
* @return
* @exception MessagingException
*/
public boolean renameTo(Folder f) throws MessagingException {
throw new MethodNotSupportedException("Renaming of NNTP folders is not supported.");
}
/**
* @see javax.mail.Folder#open(int)
*/
public void open(int mode) throws MessagingException {
// we don't support READ_WRITE mode, so don't allow opening in that
// mode.
if (mode == READ_WRITE) {
throw new IllegalWriteException("Newsgroup folders cannot be opened read/write");
}
// an only be performed on a closed folder
checkClosed();
this.mode = mode;
// perform folder type-specific open actions.
openFolder();
folderOpen = true;
notifyConnectionListeners(ConnectionEvent.OPENED);
}
/**
* Perform folder type-specific open actions. The default action is to do
* nothing.
*
* @exception MessagingException
*/
protected void openFolder() throws MessagingException {
}
/**
* Peform folder type-specific close actions. The default action is to do
* nothing.
*
* @exception MessagingException
*/
protected void closeFolder() throws MessagingException {
}
/**
* Close the folder. Cleans up resources, potentially expunges messages
* marked for deletion, and sends an event notification.
*
* @param expunge
* The expunge flag, which is ignored for NNTP folders.
*
* @exception MessagingException
*/
public void close(boolean expunge) throws MessagingException {
// Can only be performed on an open folder
checkOpen();
// give the subclasses an opportunity to do some cleanup
closeFolder();
folderOpen = false;
notifyConnectionListeners(ConnectionEvent.CLOSED);
}
/**
* Tests the open status of the folder.
*
* @return true if the folder is open, false otherwise.
*/
public boolean isOpen() {
return folderOpen;
}
/**
* Get the permanentFlags
*
* @return The set of permanent flags we support (only SEEN).
*/
public Flags getPermanentFlags() {
// we need a copy of our master set.
return new Flags(permanentFlags);
}
/**
* Get the count of messages in this folder.
*
* @return The message count.
* @exception MessagingException
*/
public int getMessageCount() throws MessagingException {
return messageCount;
}
/**
* Checks wether the message is in cache, if not will create a new message
* object and return it.
*
* @see javax.mail.Folder#getMessage(int)
*/
public Message getMessage(int msgNum) throws MessagingException {
// for the base, we just throw an exception.
throw new MethodNotSupportedException("Root NNTP folder does not contain messages");
}
/**
* Append messages to a folder. NNTP folders are read only, so this is not
* supported.
*
* @param msgs
* The list of messages to append.
*
* @exception MessagingException
*/
public void appendMessages(Message[] msgs) throws MessagingException {
throw new MethodNotSupportedException("Root NNTP folder does not contain messages");
}
/**
* Expunge messages marked for deletion and return a list of the Messages.
* Not supported for NNTP.
*
* @return Never returns.
* @exception MessagingException
*/
public Message[] expunge() throws MessagingException {
throw new MethodNotSupportedException("Root NNTP folder does not contain messages");
}
/**
* Below is a list of convenience methods that avoid repeated checking for a
* value and throwing an exception
*/
/** Ensure the folder is open */
protected void checkOpen() throws IllegalStateException {
if (!folderOpen) {
throw new IllegalStateException("Folder is not Open");
}
}
/** Ensure the folder is not open */
protected void checkClosed() throws IllegalStateException {
if (folderOpen) {
throw new IllegalStateException("Folder is Open");
}
}
/**
* @see javax.mail.Folder#notifyMessageChangedListeners(int,
* javax.mail.Message)
*
* this method is protected and cannot be used outside of Folder, therefore
* had to explicitly expose it via a method in NNTPFolder, so that
* NNTPMessage has access to it
*
* Bad design on the part of the Java Mail API.
*/
public void notifyMessageChangedListeners(int type, Message m) {
super.notifyMessageChangedListeners(type, m);
}
/**
* Retrieve the subscribed status for a folder. This default implementation
* just returns false (which is true for the root folder).
*
* @return Always returns true.
*/
public boolean isSubscribed() {
return false;
}
/**
* Set the subscribed status for a folder.
*
* @param flag
* The new subscribed status.
*
* @exception MessagingException
*/
public void setSubscribed(boolean flag) throws MessagingException {
throw new MessagingException("Root NNTP folder cannot be subscribed to");
}
/**
* Test if a given article number is marked as SEEN.
*
* @param article
* The target article number.
*
* @return The articles current seen status.
*/
public boolean isSeen(int article) {
return false;
}
/**
* Set the SEEN status for an article.
*
* @param article
* The target article.
* @param flag
* The new seen setting.
*
* @exception MessagingException
*/
public void setSeen(int article, boolean flag) throws MessagingException {
throw new MessagingException("Root NNTP folder does not contain articles");
}
}
././@LongLink 0000000 0000000 0000000 00000000154 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessage.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessa0000664 0001750 0001750 00000031101 10474735322 032106 0 ustar brian brian /*
* 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.nntp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import javax.mail.Flags;
import javax.mail.IllegalWriteException;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
import org.apache.geronimo.javamail.transport.nntp.StringListInputStream;
/**
* NNTP implementation of javax.mail.internet.MimeMessage
*
* Only the most basic information is given and Message objects created here is
* a light-weight reference to the actual Message As per the JavaMail spec items
* from the actual message will get filled up on demand
*
* If some other items are obtained from the server as a result of one call,
* then the other details are also processed and filled in. For ex if RETR is
* called then header information will also be processed in addition to the
* content
*
* @version $Rev: 437941 $ $Date: 2006-08-28 23:56:02 -0400 (Mon, 28 Aug 2006) $
*/
public class NNTPMessage extends MimeMessage {
// the server message identifer
String messageID = null;
// our attached session
protected Session session;
// the Store we're stored in (which manages the connection and other stuff).
protected NNTPStore store;
// our active connection.
protected NNTPConnection connection;
// used to force loading of headers
protected boolean headersLoaded = false;
// use to force content loading
protected boolean contentLoaded = false;
/**
* Contruct an NNTPMessage instance.
*
* @param folder
* The hosting folder for the message.
* @param store
* The Store owning the article (and folder).
* @param msgnum
* The article message number.
* @param messageID
* The article messageID (as assigned by the server).
*
* @exception MessagingException
*/
NNTPMessage(NNTPFolder folder, NNTPStore store, int msgnum, String messageID) throws MessagingException {
super(folder, msgnum);
this.messageID = messageID;
this.store = store;
this.session = ((NNTPStore) store).getSession();
// get the active connection from the store...all commands are sent
// there
this.connection = ((NNTPStore) store).getConnection();
// get our flag set from the folder.
flags = folder.getPermanentFlags();
// now check our initial SEEN state and set the flags appropriately
if (folder.isSeen(msgnum)) {
flags.add(Flags.Flag.SEEN);
} else {
flags.remove(Flags.Flag.SEEN);
}
}
/**
* Retrieve the size of the message content. The content will be retrieved
* from the server, if necessary.
*
* @return The size of the content.
* @exception MessagingException
*/
public int getSize() throws MessagingException {
// make sure we've retrieved the message content and continue with the
// superclass version.
loadContent();
return super.getSize();
}
/**
* Get a line count for the NNTP message. This is potentially stored in the
* Lines article header. If not there, we return a default of -1.
*
* @return The header line count estimate, or -1 if not retrieveable.
* @exception MessagingException
*/
public int getLineCount() throws MessagingException {
String[] headers = getHeader("Lines");
// hopefully, there's only a single one of these. No sensible way of
// interpreting
// multiples.
if (headers.length == 1) {
try {
return Integer.parseInt(headers[0].trim());
} catch (NumberFormatException e) {
// ignore
}
}
// dunno...and let them know I don't know.
return -1;
}
/**
* @see javax.mail.internet.MimeMessage#getContentStream()
*/
protected InputStream getContentStream() throws MessagingException {
// get the article information.
loadArticle();
return super.getContentStream();
}
/***************************************************************************
* Following is a set of methods that deal with headers These methods are
* just overrides on the superclass methods to allow lazy loading of the
* header information.
**************************************************************************/
public String[] getHeader(String name) throws MessagingException {
loadHeaders();
return headers.getHeader(name);
}
public String getHeader(String name, String delimiter) throws MessagingException {
loadHeaders();
return headers.getHeader(name, delimiter);
}
public Enumeration getAllHeaders() throws MessagingException {
loadHeaders();
return headers.getAllHeaders();
}
public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaders(names);
}
public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaders(names);
}
public Enumeration getAllHeaderLines() throws MessagingException {
loadHeaders();
return headers.getAllHeaderLines();
}
public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaderLines(names);
}
public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaderLines(names);
}
// the following are overrides for header modification methods. These
// messages are read only,
// so the headers cannot be modified.
public void addHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
public void setHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
public void removeHeader(String name) throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
public void addHeaderLine(String line) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
/**
* We cannot modify these messages
*/
public void saveChanges() throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
/**
* Retrieve the message headers from the NNTP server.
*
* @exception MessagingException
*/
public void loadHeaders() throws MessagingException {
// don't retrieve if already loaded.
if (headersLoaded) {
return;
}
NNTPReply reply = connection.sendCommand("HEAD " + messageID, NNTPReply.HEAD_FOLLOWS);
if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
try {
// wrap a stream around the reply data and read as headers.
updateHeaders(new StringListInputStream(reply.getData()));
} catch (IOException e) {
throw new MessagingException("Error retrieving article headers from server", e);
}
} else {
throw new MessagingException("Error retrieving article headers from server: " + reply);
}
}
/**
* Update the message headers from an input stream.
*
* @param in
* The InputStream source for the header information.
*
* @exception MessagingException
*/
public void updateHeaders(InputStream in) throws MessagingException {
// wrap a stream around the reply data and read as headers.
headers = new InternetHeaders(in);
headersLoaded = true;
}
/**
* Load just the message content from the NNTP server.
*
* @exception MessagingException
*/
public void loadContent() throws MessagingException {
if (contentLoaded) {
return;
}
NNTPReply reply = connection.sendCommand("BODY " + messageID, NNTPReply.BODY_FOLLOWS);
if (reply.getCode() == NNTPReply.BODY_FOLLOWS) {
try {
InputStream in = new StringListInputStream(reply.getData());
updateContent(in);
} catch (IOException e) {
throw new MessagingException("Error retrieving article body from server", e);
}
} else {
throw new MessagingException("Error retrieving article body from server: " + reply);
}
}
/**
* Load the entire article from the NNTP server. This updates both the
* headers and the content.
*
* @exception MessagingException
*/
public void loadArticle() throws MessagingException {
// if the headers are already loaded, retrieve the content portion.
if (headersLoaded) {
loadContent();
return;
}
// we need to retrieve everything.
NNTPReply reply = connection.sendCommand("ARTICLE " + messageID, NNTPReply.ARTICLE_FOLLOWS);
if (reply.getCode() == NNTPReply.ARTICLE_FOLLOWS) {
try {
InputStream in = new StringListInputStream(reply.getData());
// update both the headers and the content.
updateHeaders(in);
updateContent(in);
} catch (IOException e) {
throw new MessagingException("Error retrieving article from server", e);
}
} else {
throw new MessagingException("Error retrieving article from server: " + reply);
}
}
/**
* Update the article content from an input stream.
*
* @param in
* The content data source.
*
* @exception MessagingException
*/
public void updateContent(InputStream in) throws MessagingException {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
// copy the content data from the stream into a byte buffer for the
// content.
while (true) {
int read = in.read(buffer);
if (read == -1) {
break;
}
out.write(buffer, 0, read);
}
content = out.toByteArray();
contentLoaded = true;
} catch (IOException e) {
throw new MessagingException("Error retrieving message body from server", e);
}
}
/**
* Get the server assigned messageid for the article.
*
* @return The server assigned message id.
*/
public String getMessageId() {
return messageID;
}
/**
* Override of setFlags(). We need to ensure that if the SEEN flag is set or
* cleared, that the newsrc file correctly reflects the current state.
*
* @param flag
* The flag being set.
* @param newvalue
* The new flag value.
*
* @exception MessagingException
*/
public void setFlags(Flags flag, boolean newvalue) throws MessagingException {
// if this is the SEEN flag, make sure we shadow this in the newsrc
// file.
if (flag.contains(Flags.Flag.SEEN)) {
((NNTPFolder) folder).setSeen(msgnum, newvalue);
}
// have the superclass do the real flag setting.
super.setFlags(flag, newvalue);
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/ 0000775 0001750 0001750 00000000000 11703373730 031715 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Range.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Ra0000664 0001750 0001750 00000021416 10474735322 032211 0 ustar brian brian /*
* 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.nntp.newsrc;
import java.io.IOException;
import java.io.Writer;
/**
* Represent a single Range in a newsrc file. A Range can be either a single
* number (start == end) or a span of article numbers.
*/
public class Range {
// the low end of the range
int start;
// the high end of the range (start and end are inclusive);
int end;
/**
* Construct a Range item for a single digit range.
*
* @param spot
* The location of the singleton.
*/
public Range(int spot) {
this(spot, spot);
}
/**
* Construct a Range item.
*
* @param start
* The starting point of the Range.
* @param end
* The Range end point (which may be equal to the starting
* point).
*/
public Range(int start, int end) {
this.start = start;
this.end = end;
}
/**
* Parse a section of a .newsrc range string into a single Range item. The
* range is either a single number, or a pair of numbers separated by a
* hyphen.
*
* @param range
* The range string.
*
* @return A constructed Range item, or null if there is a parsing error.
*/
static public Range parse(String range) {
// a range from a newsrc file is either a single number or in the format
// 'nnnn-mmmm'. We need
// to figure out which type this is.
int marker = range.indexOf('-');
try {
if (marker != -1) {
String rangeStart = range.substring(0, marker).trim();
String rangeEnd = range.substring(marker + 1).trim();
int start = Integer.parseInt(rangeStart);
int end = Integer.parseInt(rangeEnd);
if (start >= 0 && end >= 0) {
return new Range(start, end);
}
} else {
// use the entire token
int start = Integer.parseInt(range);
// and start and the end are the same
return new Range(start, start);
}
} catch (NumberFormatException e) {
}
// return null for any bad values
return null;
}
/**
* Get the starting point for the Range.
*
* @return The beginning of the mark range.
*/
public int getStart() {
return start;
}
/**
* Set the starting point for a Range.
*
* @param start
* The new start value.
*/
public void setStart(int start) {
this.start = start;
}
/**
* Get the ending point for the Range.
*
* @return The end of the mark range.
*/
public int getEnd() {
return end;
}
/**
* Set the ending point for a Range.
*
* @param end
* The new end value.
*/
public void setEnd(int end) {
this.end = end;
}
/**
* Test if a range contains a point value.
*
* @param target
* The article location to test.
*
* @return True if the target is between the start and end values,
* inclusive.
*/
public boolean contains(int target) {
return target >= start && target <= end;
}
/**
* Test if one range is completely contained within another Range.
*
* @param other
* The other test range.
*
* @return true if the other start and end points are contained within this
* range.
*/
public boolean contains(Range other) {
return contains(other.getStart()) && contains(other.getEnd());
}
/**
* Tests if two ranges overlap
*
* @param other
* The other test range.
*
* @return true if the start or end points of either range are contained
* within the range of the other.
*/
public boolean overlaps(Range other) {
return other.contains(start) || other.contains(end) || contains(other.getStart()) || contains(other.getEnd());
}
/**
* Test if two ranges exactly abutt each other.
*
* @param other
* The other Range to test.
*
* @return true if the end of one range abutts the start of the other range.
*/
public boolean abutts(Range other) {
return other.getStart() == end + 1 || other.getEnd() == start - 1;
}
/**
* Tests if a single point abutts either the start or end of this Range.
*
* @param article
* The point to test.
*
* @return true if test point is equal to start - 1 or end + 1.
*/
public boolean abutts(int article) {
return article == start - 1 || article == end + 1;
}
/**
* Test if a point is below the test Range.
*
* @param article
* The point to test.
*
* @return true if the entire range is less than the test point.
*/
public boolean lessThan(int article) {
return end < article;
}
/**
* Test if another Range is less than this Range.
*
* @param other
* The other Range to test.
*
* @return true if the other Range lies completely below this Range.
*/
public boolean lessThan(Range other) {
return end < other.start;
}
/**
* Test if a point is above the test Range.
*
* @param article
* The point to test.
*
* @return true if the entire range is greater than the test point.
*/
public boolean greaterThan(int article) {
return start > article;
}
/**
* Test if another Range is greater than this Range.
*
* @param other
* The other Range to test.
*
* @return true if the other Range lies completely below this Range.
*/
public boolean greaterThan(Range other) {
return start > other.end;
}
/**
* Merge another Range into this one. Merging will increase the bounds of
* this Range to encompass the entire span of the two. If the Ranges do not
* overlap, the newly created range will include the gap between the two.
*
* @param other
* The Range to merge.
*/
public void merge(Range other) {
if (other.start < start) {
start = other.start;
}
if (other.end > end) {
end = other.end;
}
}
/**
* Split a range at a given split point. Splitting will truncate at the
* split location - 1 and return a new range beginning at location + 1; This
* code assumes that the split location is at neither end poing.
*
* @param location
* The split location. Location must be in the range start <
* location < end.
*
* @return A new Range object for the split portion of the range.
*/
public Range split(int location) {
int newEnd = end;
end = location - 1;
return new Range(location + 1, newEnd);
}
/**
* Save an individual range element to a newsrc file. The range is expressed
* either as a single number, or a hypenated pair of numbers.
*
* @param out
* The output writer used to save the data.
*
* @exception IOException
*/
public void save(Writer out) throws IOException {
// do we have a single data point range?
if (start == end) {
out.write(Integer.toString(start));
} else {
out.write(Integer.toString(start));
out.write("-");
out.write(Integer.toString(end));
}
}
/**
* Convert a Range into String form. Used mostly for debugging.
*
* @return The String representation of the Range.
*/
public String toString() {
if (start == end) {
return Integer.toString(start);
} else {
return Integer.toString(start) + "-" + Integer.toString(end);
}
}
}
././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcGroup.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NN0000664 0001750 0001750 00000012140 10474735322 032154 0 ustar brian brian /*
* 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.nntp.newsrc;
import java.io.IOException;
import java.io.Writer;
public class NNTPNewsrcGroup {
// the newsrc database we're part of
NNTPNewsrc newsrc;
// the name of the group
protected String name;
// the subscription flage
protected boolean subscribed;
// the range of already seen articles.
protected RangeList ranges;
/**
* Construct a NNTPNewsrcGroup item associated with a given .newsrc
* database.
*
* @param newsrc
* The owning .newsrc database.
* @param line
* The .newsrc range entries in .newsrc format. These ranges are
* parsed to create a set of seen flags.
*
* @return A created NNTPNewsrcGroup item.
*/
public static NNTPNewsrcGroup parse(NNTPNewsrc newsrc, String line) {
String groupName = null;
String ranges = null;
// subscribed lines have a ':' marker acting as a delimiter
int marker = line.indexOf(':');
if (marker != -1) {
groupName = line.substring(0, marker);
ranges = line.substring(marker + 1);
return new NNTPNewsrcGroup(newsrc, groupName, ranges, true);
}
// now check for an unsubscribed group
marker = line.indexOf('!');
if (marker != -1) {
groupName = line.substring(0, marker);
ranges = line.substring(marker + 1);
return new NNTPNewsrcGroup(newsrc, groupName, ranges, false);
}
// must be a comment line
return null;
}
/**
* Construct a .newsrc group item.
*
* @param newsrc
* The owning newsrc database.
* @param name
* The group name.
* @param newsrcRanges
* The initial set of seen ranges for the group (may be null).
* @param subscribed
* The initial group subscription state.
*/
public NNTPNewsrcGroup(NNTPNewsrc newsrc, String name, String newsrcRanges, boolean subscribed) {
this.newsrc = newsrc;
this.name = name;
this.subscribed = subscribed;
this.ranges = new RangeList(newsrcRanges);
}
/**
* Get the group name.
*
* @return The String name of the group.
*/
public String getName() {
return name;
}
/**
* Get the newsrc subscribed status for an article.
*
* @return The current subscription flag.
*/
public boolean isSubscribed() {
return subscribed;
}
/**
* Set the subscription status for an article.
*
* @param flag
* The new subscription value.
*/
public void setSubscribed(boolean flag) {
// we don't blindly set this to the new value since we only want to
// resave the newsrc file if
// something changes.
if (flag && !subscribed) {
subscribed = true;
newsrc.setDirty();
} else if (!flag && subscribed) {
subscribed = false;
newsrc.setDirty();
}
}
/**
* Test if an article has been seen yet.
*
* @param article
* The target article.
*
* @return The seen mark for the article.
*/
public boolean isArticleSeen(int article) {
return ranges.isMarked(article);
}
/**
* Mark an article as seen.
*
* @param article
* The target article number.
*/
public void markArticleSeen(int article) {
ranges.setMarked(article);
if (ranges.isDirty()) {
newsrc.setDirty();
}
}
/**
* Mark an article as unseen.
*
* @param article
* The target article number.
*/
public void markArticleUnseen(int article) {
ranges.setUnmarked(article);
if (ranges.isDirty()) {
newsrc.setDirty();
}
}
/**
* Save this group definition to a .newsrc file.
*
* @param out
* The output writer to send the information to.
*
* @exception IOException
*/
public void save(Writer out) throws IOException {
out.write(name);
out.write(subscribed ? ": " : "! ");
ranges.save(out);
// put a terminating line end
out.write("\r\n");
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrc.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NN0000664 0001750 0001750 00000012717 10474735322 032166 0 ustar brian brian /*
* 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.nntp.newsrc;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Base class implementation of a standard news reader news rc file. This is
* used to track newsgroup subscriptions and SEEN flags for articles. This is an
* abstract class designed for subclasses to bridge to the physical store type
* used for the newsgroup information.
*/
public abstract class NNTPNewsrc {
// the group information we've read from the news rc file.
Map groups = new HashMap();
// flag to let us know of we need to persist the newsrc file on close.
boolean dirty = false;
/**
* Base class constructor for NNTPNewsrc items. Subclasses provide their own
* domain-specific intialization.
*/
protected NNTPNewsrc() {
}
/**
* Load the data from the newsrc file and parse into an instore group
* database.
*/
public void load() {
BufferedReader in = null;
try {
in = getInputReader();
String line = in.readLine();
while (line != null) {
// parse the line...this returns null if it's something
// unrecognized.
NNTPNewsrcGroup group = NNTPNewsrcGroup.parse(this, line);
// if it parsed ok, add it to the group list, and potentially to
// the subscribed list.
if (group != null) {
groups.put(group.getName(), group);
}
line = in.readLine();
}
in.close();
} catch (IOException e) {
// an IOException may mean that the file just doesn't exist, which
// is fine. We'll ignore and
// proceed with the information we have.
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
}
/**
* Save the newsrc file data back to the original source file.
*
* @exception IOException
*/
public void save() throws IOException {
Writer out = getOutputWriter();
Iterator i = groups.values().iterator();
while (i.hasNext()) {
NNTPNewsrcGroup group = (NNTPNewsrcGroup) i.next();
group.save(out);
}
out.close();
}
/**
* Abstract open method intended for sub class initialization. The subclass
* is responsible for creating the BufferedReaded used to read the .newsrc
* file.
*
* @return A BufferedReader for reading the .newsrc file.
* @exception IOException
*/
abstract public BufferedReader getInputReader() throws IOException;
/**
* Abstract open for output method intended for subclass implementation. The
* subclasses are reponsible for opening the output stream and creating an
* appropriate Writer for saving the .newsrc file.
*
* @return A Writer target at the .newsrc file save location.
* @exception IOException
*/
abstract public Writer getOutputWriter() throws IOException;
/**
* Retrieve the newsrc group information for a named group. If the file does
* not currently include this group, an unsubscribed group will be added to
* the file.
*
* @param name
* The name of the target group.
*
* @return The NNTPNewsrcGroup item corresponding to this name.
*/
public NNTPNewsrcGroup getGroup(String name) {
NNTPNewsrcGroup group = (NNTPNewsrcGroup) groups.get(name);
// if we don't know about this, create a new one and add to the list.
// This
// will be an unsubscribed one.
if (group == null) {
group = new NNTPNewsrcGroup(this, name, null, false);
groups.put(name, group);
// we've added a group, so we need to resave
dirty = true;
}
return group;
}
/**
* Mark this newsrc database as dirty.
*/
public void setDirty() {
dirty = true;
}
/**
* Close the newsrc file, persisting it back to disk if the file has
* changed.
*/
public void close() {
if (dirty) {
try {
save();
} catch (IOException e) {
// ignore
}
}
}
/**
* Retrieve the current set of loaded groups.
*
* @return An iterator for traversing the group set.
*/
public Iterator getGroups() {
return groups.values().iterator();
}
}
././@LongLink 0000000 0000000 0000000 00000000166 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NN0000664 0001750 0001750 00000004216 11404405474 032155 0 ustar brian brian /*
* 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.nntp.newsrc;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class NNTPNewsrcFile extends NNTPNewsrc {
// source for the file data
File source;
/**
* Construct a NNTPNewsrc object that is targetted at a file-based backing
* store.
*
* @param source
* The source File for the .newsrc data.
*/
public NNTPNewsrcFile(File source) {
this.source = source;
}
/**
* Retrieve an input reader for loading the newsrc file.
*
* @return A BufferedReader object for reading from the newsrc file.
* @exception IOException
*/
public BufferedReader getInputReader() throws IOException {
return new BufferedReader(new InputStreamReader(new FileInputStream(source), "ISO8859-1"));
}
/**
* Obtain a writer for saving a newsrc file.
*
* @return The output writer targetted to the newsrc file.
* @exception IOException
*/
public Writer getOutputWriter() throws IOException {
// open this for overwriting
return new OutputStreamWriter(new FileOutputStream(source, false), "ISO8859-1");
}
}
././@LongLink 0000000 0000000 0000000 00000000161 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/RangeList.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Ra0000664 0001750 0001750 00000016770 10474735322 032220 0 ustar brian brian /*
* 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.nntp.newsrc;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.StringTokenizer;
/**
* Manage a list of ranges values from a newsrc file.
*/
public class RangeList {
boolean dirty = false;
ArrayList ranges = new ArrayList();
/**
* Create a RangeList instance from a newsrc range line. Values are saved as
* a comma separated set of range values. A range value is either a single
* number or a hypenated pair of numbers.
*
* @param line
* The newsrc range line.
*/
public RangeList(String line) {
// we might be creating an first time list, so nothing to parse.
if (line != null) {
// ranges are comma delimited tokens
StringTokenizer tokenizer = new StringTokenizer(line, ",");
while (tokenizer.hasMoreTokens()) {
String rangeString = (String) tokenizer.nextToken();
rangeString = rangeString.trim();
if (rangeString.length() != 0) {
Range range = Range.parse(rangeString);
if (range != null) {
insert(range);
}
}
}
}
// make sure we start out in a clean state. Any changes from this point
// will flip on the dirty flat.
dirty = false;
}
/**
* Insert a range item into our list. If possible, the inserted range will
* be merged with existing ranges.
*
* @param newRange
* The new range item.
*/
public void insert(Range newRange) {
// first find the insertion point
for (int i = 0; i < ranges.size(); i++) {
Range range = (Range) ranges.get(i);
// does an existing range fully contain the new range, we don't need
// to insert anything.
if (range.contains(newRange)) {
return;
}
// does the current one abutt or overlap with the inserted range?
if (range.abutts(newRange) || range.overlaps(newRange)) {
// rats, we have an overlap...and it is possible that we could
// overlap with
// the next range after this one too. Therefore, we merge these
// two ranges together,
// remove the place where we're at, and then recursively insert
// the larger range into
// the list.
dirty = true;
newRange.merge(range);
ranges.remove(i);
insert(newRange);
return;
}
// ok, we don't touch the current one at all. If it is completely
// above
// range we're adding, we can just poke this into the list here.
if (newRange.lessThan(range)) {
dirty = true;
ranges.add(i, newRange);
return;
}
}
dirty = true;
// this is easy (and fairly common)...we just tack this on to the end.
ranges.add(newRange);
}
/**
* Test if a given article point falls within one of the contained Ranges.
*
* @param article
* The test point.
*
* @return true if this falls within one of our current mark Ranges, false
* otherwise.
*/
public boolean isMarked(int article) {
for (int i = 0; i < ranges.size(); i++) {
Range range = (Range) ranges.get(i);
if (range.contains(article)) {
return true;
}
// we've passed the point where a match is possible.
if (range.greaterThan(article)) {
return false;
}
}
return false;
}
/**
* Mark a target article as having been seen.
*
* @param article
* The target article number.
*/
public void setMarked(int article) {
// go through the insertion logic.
insert(new Range(article, article));
}
/**
* Clear the seen mark for a target article.
*
* @param article
* The target article number.
*/
public void setUnmarked(int article) {
for (int i = 0; i < ranges.size(); i++) {
Range range = (Range) ranges.get(i);
// does this fall within an existing range? We don't need to do
// anything here.
if (range.contains(article)) {
// ok, we've found where to insert, now to figure out how to
// insert
// article is at the beginning of the range. We can just
// increment the lower
// bound, or if this is a single element range, we can remove it
// entirely.
if (range.getStart() == article) {
if (range.getEnd() == article) {
// piece of cake!
ranges.remove(i);
} else {
// still pretty easy.
range.setStart(article + 1);
}
} else if (range.getEnd() == article) {
// pretty easy also
range.setEnd(article - 1);
} else {
// split this into two ranges and insert the trailing piece
// after this.
Range section = range.split(article);
ranges.add(i + 1, section);
}
dirty = true;
return;
}
// did we find a point where any articles are greater?
if (range.greaterThan(article)) {
// nothing to do
return;
}
}
// didn't find it at all. That was easy!
}
/**
* Save this List of Ranges out to a .newsrc file. This creates a
* comma-separated list of range values from each of the Ranges.
*
* @param out
* The target output stream.
*
* @exception IOException
*/
public void save(Writer out) throws IOException {
// we have an empty list
if (ranges.size() == 0) {
return;
}
Range range = (Range) ranges.get(0);
range.save(out);
for (int i = 1; i < ranges.size(); i++) {
out.write(",");
range = (Range) ranges.get(i);
range.save(out);
}
}
/**
* Return the state of the dirty flag.
*
* @return True if the range list information has changed, false otherwise.
*/
public boolean isDirty() {
return dirty;
}
}
././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore0000664 0001750 0001750 00000021426 11032465542 032136 0 ustar brian brian /*
* 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.nntp;
import java.io.File;
import java.io.PrintStream;
import java.util.Iterator;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrc;
import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcFile;
import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
import org.apache.geronimo.javamail.util.ProtocolProperties;
import org.apache.geronimo.mail.util.SessionUtil;
/**
* NNTP implementation of javax.mail.Store POP protocol spec is implemented in
* org.apache.geronimo.javamail.store.pop3.NNTPConnection
*
* @version $Rev: 673152 $ $Date: 2008-07-01 13:37:38 -0400 (Tue, 01 Jul 2008) $
*/
public class NNTPStore extends Store {
protected static final String NNTP_NEWSRC = "newsrc";
protected static final String protocol = "nntp";
protected static final int DEFAULT_NNTP_PORT = 119;
protected static final int DEFAULT_NNTP_SSL_PORT = 563;
// our accessor for protocol properties and the holder of
// protocol-specific information
protected ProtocolProperties props;
// our active connection object (shared code with the NNTPStore).
protected NNTPConnection connection;
// the root folder
protected NNTPRootFolder root;
// the newsrc file where we store subscriptions and seen message markers.
protected NNTPNewsrc newsrc;
/**
* Construct an NNTPStore item. This will load the .newsrc file associated
* with the server.
*
* @param session
* The owning javamail Session.
* @param name
* The Store urlName, which can contain server target
* information.
*/
public NNTPStore(Session session, URLName name) {
this(session, name, "nntp", DEFAULT_NNTP_PORT, false);
}
/**
* Common constructor used by the POP3Store and POP3SSLStore classes
* to do common initialization of defaults.
*
* @param session
* The host session instance.
* @param name
* The URLName of the target.
* @param protocol
* The protocol type ("nntp" or "nntps"). This helps us in
* retrieving protocol-specific session properties.
* @param defaultPort
* The default port used by this protocol. For pop3, this will
* be 110. The default for pop3 with ssl is 995.
* @param sslConnection
* Indicates whether an SSL connection should be used to initial
* contact the server. This is different from the STARTTLS
* support, which switches the connection to SSL after the
* initial startup.
*/
protected NNTPStore(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
// create the protocol property holder. This gives an abstraction over the different
// flavors of the protocol.
props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
// the connection manages connection for the transport
connection = new NNTPConnection(props);
}
/**
* @see javax.mail.Store#getDefaultFolder()
*
* This returns a root folder object for all of the news groups.
*/
public Folder getDefaultFolder() throws MessagingException {
checkConnectionStatus();
if (root == null) {
return new NNTPRootFolder(this, connection.getHost(), connection.getWelcomeString());
}
return root;
}
/**
* @see javax.mail.Store#getFolder(java.lang.String)
*/
public Folder getFolder(String name) throws MessagingException {
return getDefaultFolder().getFolder(name);
}
/**
*
* @see javax.mail.Store#getFolder(javax.mail.URLName)
*/
public Folder getFolder(URLName url) throws MessagingException {
return getDefaultFolder().getFolder(url.getFile());
}
/**
* Do the protocol connection for an NNTP transport. This handles server
* authentication, if possible. Returns false if unable to connect to the
* server.
*
* @param host
* The target host name.
* @param port
* The server port number.
* @param user
* The authentication user (if any).
* @param password
* The server password. Might not be sent directly if more
* sophisticated authentication is used.
*
* @return true if we were able to connect to the server properly, false for
* any failures.
* @exception MessagingException
*/
protected boolean protocolConnect(String host, int port, String username, String password)
throws MessagingException {
// the connection pool handles all of the details here. But don't proceed
// without a connection
if (!connection.protocolConnect(host, port, username, password)) {
return false;
}
// see if we have a newsrc file location specified
String newsrcFile = props.getProperty(NNTP_NEWSRC);
File source = null;
// not given as a property? Then look for a file in user.home
if (newsrcFile != null) {
source = new File(newsrcFile);
} else {
// ok, look for a file in the user.home directory. If possible,
// we'll try for a file
// with the hostname appended.
String home = SessionUtil.getProperty("user.home");
// try for a host-specific file first. If not found, use (and
// potentially create) a generic
// .newsrc file.
newsrcFile = ".newsrc-" + host;
source = new File(home, newsrcFile);
if (!source.exists()) {
source = new File(home, ".newsrc");
}
}
// now create a newsrc read and load the file.
newsrc = new NNTPNewsrcFile(source);
newsrc.load();
// we're going to return success here, but in truth, the server may end
// up asking for our bonafides at any time, and we'll be expected to authenticate then.
return true;
}
/**
* @see javax.mail.Service#close()
*/
public void close() throws MessagingException {
// This is done to ensure proper event notification.
super.close();
// persist the newsrc file, if possible
if (newsrc != null) {
newsrc.close();
newsrc = null;
}
connection.close();
connection = null;
}
private void checkConnectionStatus() throws MessagingException {
if (!this.isConnected()) {
throw new MessagingException("Not connected ");
}
}
/**
* Retrieve the server connection created by this store.
*
* @return The active connection object.
*/
NNTPConnection getConnection() {
return connection;
}
/**
* Retrieve the Session object this Store is operating under.
*
* @return The attached Session instance.
*/
Session getSession() {
return session;
}
/**
* Retrieve all of the groups we nave persistent store information about.
*
* @return The set of groups contained in the newsrc file.
*/
Iterator getNewsrcGroups() {
return newsrc.getGroups();
}
/**
* Retrieve the newsrc group information for a named group. If the file does
* not currently include this group, an unsubscribed group will be added to
* the file.
*
* @param name
* The name of the target group.
*
* @return The NNTPNewsrcGroup item corresponding to this name.
*/
NNTPNewsrcGroup getNewsrcGroup(String name) {
return newsrc.getGroup(name);
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootF0000664 0001750 0001750 00000033433 10474735322 032101 0 ustar brian brian /*
* 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.nntp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.mail.Folder;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
import org.apache.geronimo.mail.util.SessionUtil;
/**
* The base NNTP implementation of the javax.mail.Folder This is a base class
* for both the Root NNTP server and each NNTP group folder.
*
* @see javax.mail.Folder
*
* @version $Rev: 437941 $
*/
public class NNTPRootFolder extends NNTPFolder {
protected static final String NNTP_LISTALL = "mail.nntp.listall";
/**
* Construct the NNTPRootFolder.
*
* @param store
* The owning Store.
* @param name
* The folder name (by default, this is the server host name).
* @param fullName
* The fullName to use for this server (derived from welcome
* string).
*/
protected NNTPRootFolder(NNTPStore store, String name, String fullName) {
super(store);
this.name = name;
this.fullName = fullName;
}
/**
* List the subfolders. For group folders, this is a meaningless so we throw
* a MethodNotSupportedException.
*
* @param pattern
* The folder pattern string.
*
* @return Never returns.
* @exception MessagingException
*/
public synchronized Folder[] list(String pattern) throws MessagingException {
// the pattern specfied for javamail uses two wild card characters, "%"
// and "*". The "%" matches
// and character except hierarchy separators. Since we have a flag
// hierarchy, "%" and "*" are
// essentially the same. If we convert the "%" into "*", we can just
// treat this as a wildmat
// formatted pattern and pass this on to the server rather than having
// to read everything and
// process the strings on the client side.
pattern = pattern.replace('%', '*');
// if we're not supposed to list everything, then just filter the list
// of subscribed groups.
if (SessionUtil.getBooleanProperty(NNTP_LISTALL, false)) {
return filterActiveGroups(pattern);
} else {
return filterSubscribedGroups(pattern);
}
}
/**
* Retrieve the list of subscribed folders that match the given pattern
* string.
*
* @param pattern
* The pattern string used for the matching
*
* @return An array of matching folders from the subscribed list.
*/
public Folder[] listSubscribed(String pattern) throws MessagingException {
// the pattern specfied for javamail uses two wild card characters, "%"
// and "*". The "%" matches
// and character except hierarchy separators. Since we have a flag
// hierarchy, "%" and "*" are
// essentially the same. If we convert the "%" into "*", we can just
// treat this as a wildmat
// formatted pattern and pass this on to the server rather than having
// to read everything and
// process the strings on the client side.
pattern = pattern.replace('%', '*');
return filterSubscribedGroups(pattern);
}
/**
* Retrieve the list of matching groups from the NNTP server using the LIST
* ACTIVE command. The server does the wildcard matching for us.
*
* @param pattern
* The pattern string (in wildmat format) used to match.
*
* @return An array of folders for the matching groups.
*/
protected Folder[] filterActiveGroups(String pattern) throws MessagingException {
NNTPReply reply = connection.sendCommand("LIST ACTIVE " + pattern, NNTPReply.LIST_FOLLOWS);
// if the LIST ACTIVE command isn't supported,
if (reply.getCode() == NNTPReply.COMMAND_NOT_RECOGNIZED) {
// only way to list all is to retrieve all and filter.
return filterAllGroups(pattern);
} else if (reply.getCode() != NNTPReply.LIST_FOLLOWS) {
throw new MessagingException("Error retrieving group list from NNTP server: " + reply);
}
// get the response back from the server and process each returned group
// name.
List groups = reply.getData();
Folder[] folders = new Folder[groups.size()];
for (int i = 0; i < groups.size(); i++) {
folders[i] = getFolder(getGroupName((String) groups.get(i)));
}
return folders;
}
/**
* Retrieve a list of all groups from the server and filter on the names.
* Not recommended for the usenet servers, as there are over 30000 groups to
* process.
*
* @param pattern
* The pattern string used for the selection.
*
* @return The Folders for the matching groups.
*/
protected Folder[] filterAllGroups(String pattern) throws MessagingException {
NNTPReply reply = connection.sendCommand("LIST", NNTPReply.LIST_FOLLOWS);
if (reply.getCode() != NNTPReply.LIST_FOLLOWS) {
throw new MessagingException("Error retrieving group list from NNTP server: " + reply);
}
// get the response back from the server and process each returned group
// name.
List groups = reply.getData();
WildmatMatcher matcher = new WildmatMatcher(pattern);
List folders = new ArrayList();
for (int i = 0; i < groups.size(); i++) {
String name = getGroupName((String) groups.get(i));
// does this match our pattern? Add to the list
if (matcher.matches(name)) {
folders.add(getFolder(name));
}
}
return (Folder[]) folders.toArray(new Folder[0]);
}
/**
* Return the set of groups from the newsrc subscribed groups list that
* match a given filter.
*
* @param pattern
* The selection pattern.
*
* @return The Folders for the matching groups.
*/
protected Folder[] filterSubscribedGroups(String pattern) throws MessagingException {
Iterator groups = ((NNTPStore) store).getNewsrcGroups();
WildmatMatcher matcher = new WildmatMatcher(pattern);
List folders = new ArrayList();
while (groups.hasNext()) {
NNTPNewsrcGroup group = (NNTPNewsrcGroup) groups.next();
if (group.isSubscribed()) {
// does this match our pattern? Add to the list
if (matcher.matches(group.getName())) {
folders.add(getFolder(group.getName()));
}
}
}
return (Folder[]) folders.toArray(new Folder[0]);
}
/**
* Utility method for extracting a name from a group list response.
*
* @param response
* The response string.
*
* @return The group name.
*/
protected String getGroupName(String response) {
int blank = response.indexOf(' ');
return response.substring(0, blank).trim();
}
/**
* Return whether this folder can hold just messages or also subfolders.
* Only the root folder can hold other folders, so it will need to override.
*
* @return Always returns Folder.HOLDS_FOLDERS.
* @exception MessagingException
*/
public int getType() throws MessagingException {
return HOLDS_FOLDERS;
}
/**
* Get a new folder from the root folder. This creates a new folder, which
* might not actually exist on the server. If the folder doesn't exist, an
* error will occur on folder open.
*
* @param name
* The name of the requested folder.
*
* @return A new folder object for this folder.
* @exception MessagingException
*/
public Folder getFolder(String name) throws MessagingException {
// create a new group folder and return
return new NNTPGroupFolder(this, (NNTPStore) store, name, ((NNTPStore) store).getNewsrcGroup(name));
}
/**
* Utility class to do Wildmat pattern matching on folder names.
*/
class WildmatMatcher {
// middle match sections...because these are separated by wildcards, if
// they appear in
// sequence in the string, it is a match.
List matchSections = new ArrayList();
// just a "*" match, so everything is true
boolean matchAny = false;
// no wildcards, so this must be an exact match.
String exactMatch = null;
// a leading section which must be at the beginning
String firstSection = null;
// a trailing section which must be at the end of the string.
String lastSection = null;
/**
* Create a wildmat pattern matcher.
*
* @param pattern
* The wildmat pattern to apply to string matches.
*/
public WildmatMatcher(String pattern) {
int section = 0;
// handle the easy cases first
// single wild card?
if (pattern.equals("*")) {
matchAny = true;
return;
}
// find the first wild card
int wildcard = pattern.indexOf('*');
// no wild card at all?
if (wildcard == -1) {
exactMatch = pattern;
return;
}
// pattern not begin with a wildcard? We need to pull off the
// leading section
if (!pattern.startsWith("*")) {
firstSection = pattern.substring(0, wildcard);
section = wildcard + 1;
// this could be "yada*", so we could be done.
if (section >= pattern.length()) {
return;
}
}
// now parse off the middle sections, making sure to handle the end
// condition correctly.
while (section < pattern.length()) {
// find the next wildcard position
wildcard = pattern.indexOf('*', section);
if (wildcard == -1) {
// not found, we're at the end of the pattern. We need to
// match on the end.
lastSection = pattern.substring(section);
return;
}
// we could have a null section, which we'll just ignore.
else if (wildcard == section) {
// step over the wild card
section++;
} else {
// pluck off the next section
matchSections.add(pattern.substring(section, wildcard));
// step over the wild card character and check if we've
// reached the end.
section = wildcard + 1;
}
}
}
/**
* Test if a name string matches to parsed wildmat pattern.
*
* @param name
* The name to test.
*
* @return true if the string matches the pattern, false otherwise.
*/
public boolean matches(String name) {
// handle the easy cases first
// full wildcard? Always matches
if (matchAny) {
return true;
}
// required exact matches are easy.
if (exactMatch != null) {
return exactMatch.equals(name);
}
int span = 0;
// must match the beginning?
if (firstSection != null) {
// if it doesn't start with that, it can't be true.
if (!name.startsWith(firstSection)) {
return false;
}
// we do all additional matching activity from here.
span = firstSection.length();
}
// scan for each of the sections along the string
for (int i = 1; i < matchSections.size(); i++) {
// if a section is not found, this is false
String nextMatch = (String) matchSections.get(i);
int nextLocation = name.indexOf(nextMatch, span);
if (nextLocation == -1) {
return false;
}
// step over that one
span = nextMatch.length() + nextLocation;
}
// we've matched everything up to this point, now check to see if
// need an end match
if (lastSection != null) {
// we need to have at least the number of characters of the end
// string left, else this fails.
if (name.length() - span < lastSection.length()) {
return false;
}
// ok, make sure we end with this string
return name.endsWith(lastSection);
}
// no falsies, this must be the truth.
return true;
}
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/ 0000775 0001750 0001750 00000000000 11703373730 030363 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/ geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000775 0001750 0001750 00000000000 11703373730 032265 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000202 00000000000 011557 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000006416 10716317503 032275 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Util class to represent a CAPABILITY response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPCapabilityResponse extends IMAPUntaggedResponse {
// the advertised capabilities
protected Map capabilities = new HashMap();
// the authentication mechanisms. The order is important with
// the authentications, as we a) want to process these in the
// order presented, and b) need to convert them into String arrays
// for Sasl API calls.
protected List authentications = new ArrayList();
/**
* Create a reply object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
public IMAPCapabilityResponse(IMAPResponseTokenizer source, byte [] response) throws MessagingException {
super("CAPABILITY", response);
// parse each of the capability tokens. We're using the default RFC822 parsing rules,
// which does not consider "=" to be a delimiter token, so all "AUTH=" capabilities will
// come through as a single token.
while (source.hasMore()) {
// the capabilities are always ATOMs.
String value = source.readAtom().toUpperCase();
// is this an authentication option?
if (value.startsWith("AUTH=")) {
// parse off the mechanism that fillows the "=", and add this to the supported list.
String mechanism = value.substring(5);
authentications.add(mechanism);
}
else {
// just add this to the capabilities map.
capabilities.put(value, value);
}
}
}
/**
* Return the capability map for the server.
*
* @return A map of the capability items.
*/
public Map getCapabilities() {
return capabilities;
}
/**
* Retrieve the map of the server-supported authentication
* mechanisms.
*
* @return
*/
public List getAuthentications() {
return authentications;
}
}
././@LongLink 0000000 0000000 0000000 00000000202 00000000000 011557 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListRightsResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003410 10716317503 032264 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.Rights;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Utility class to aggregate status responses for a mailbox.
*/
public class IMAPListRightsResponse extends IMAPUntaggedResponse {
public String mailbox;
public String name;
public Rights[] rights;
public IMAPListRightsResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("LISTRIGHTS", data);
mailbox = source.readEncodedString();
name = source.readString();
List acls = new ArrayList();
while (source.hasMore()) {
acls.add(new Rights(source.readString()));
}
rights = new Rights[acls.size()];
acls.toArray(rights);
}
}
././@LongLink 0000000 0000000 0000000 00000000165 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlags.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002453 10716317503 032272 0 ustar brian brian /**
* 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.util.List;
import javax.mail.MessagingException;
import javax.mail.Flags;
/**
* A fetched FLAGS value returned on a FETCH response.
*/
public class IMAPFlags extends IMAPFetchDataItem {
public Flags flags;
public IMAPFlags(IMAPResponseTokenizer source) throws MessagingException {
super(FLAGS);
// parse the list of flag values and merge each one into the flag set
flags = source.readFlagList();
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000014263 10716317503 032274 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent a composite FETCH response from an IMAP server. The
* response may have information about multiple message dataItems.
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPFetchResponse extends IMAPUntaggedResponse {
// parsed sections within the FETCH response structure
protected List dataItems = new ArrayList();
// the message number to which this applies
public int sequenceNumber;
public IMAPFetchResponse(int sequenceNumber, byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("FETCH", data);
this.sequenceNumber = sequenceNumber;
// fetch responses are a list, even if there is just a single member.
source.checkLeftParen();
// loop until we find the list end.
while (source.notListEnd()) {
// the response names are coded as ATOMS. The BODY one's use a special
// syntax, so we need to use the expanded delimiter set to pull this out.
String itemName = source.readAtom(true).toUpperCase();
if (itemName.equals("ENVELOPE")) {
dataItems.add(new IMAPEnvelope(source));
}
else if (itemName.equals("BODYSTRUCTURE")) {
dataItems.add(new IMAPBodyStructure(source));
}
else if (itemName.equals("FLAGS")) {
dataItems.add(new IMAPFlags(source));
}
else if (itemName.equals("INTERNALDATE")) {
dataItems.add(new IMAPInternalDate(source));
}
else if (itemName.equals("UID")) {
dataItems.add(new IMAPUid(sequenceNumber, source));
}
else if (itemName.equals("RFC822")) {
// all of the RFC822 items are of form
// "RFC822.name". We used the expanded parse above because
// the BODY names include some complicated bits. If we got one
// of the RFC822 sections, then parse the rest of the name using
// the old rules, which will pull in the rest of the name from the period.
itemName = source.readAtom(false).toUpperCase();
if (itemName.equals(".SIZE")) {
dataItems.add(new IMAPMessageSize(source));
}
else if (itemName.equals(".HEADER")) {
dataItems.add(new IMAPInternetHeader(source.readByteArray()));
}
else if (itemName.equals(".TEXT")) {
dataItems.add(new IMAPMessageText(source.readByteArray()));
}
}
// this is just the body alone. Specific body segments
// have a more complex naming structure. Believe it or
// not,
else if (itemName.equals("BODY")) {
// time to go parse out the section information from the
// name.
IMAPBodySection section = new IMAPBodySection(source);
switch (section.section) {
case IMAPBodySection.BODY:
// a "full body cast". Just grab the binary data
dataItems.add(new IMAPBody(section, source.readByteArray()));
break;
case IMAPBodySection.HEADERS:
case IMAPBodySection.HEADERSUBSET:
case IMAPBodySection.MIME:
// these 3 are all variations of a header request
dataItems.add(new IMAPInternetHeader(section, source.readByteArray()));
break;
case IMAPBodySection.TEXT:
// just the text portion of the body
// a "full body cast". Just grab the binary data
dataItems.add(new IMAPMessageText(section, source.readByteArray()));
break;
}
}
}
// swallow the terminating right paren
source.checkRightParen();
}
/**
* Retrieve the sequence number for the FETCH item.
*
* @return The message sequence number this FETCH applies to.
*/
public int getSequenceNumber() {
return sequenceNumber;
}
/**
* Get the section count.
*
* @return The number of sections in the response.
*/
public int getCount() {
return dataItems.size();
}
/**
* Get the complete set of response dataItems.
*
* @return The List of IMAPFetchResponse values.
*/
public List getDataItems() {
return dataItems;
}
/**
* Fetch a particular response type from the response dataItems.
*
* @param type The target FETCH type.
*
* @return The first IMAPFetchDataItem item that matches the response type.
*/
public IMAPFetchDataItem getDataItem(int type) {
for (int i = 0; i < dataItems.size(); i ++) {
IMAPFetchDataItem item = (IMAPFetchDataItem)dataItems.get(i);
if (item.isType(type)) {
return item;
}
}
return null;
}
}
././@LongLink 0000000 0000000 0000000 00000000200 00000000000 011555 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004136 10716317503 032272 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent an untagged response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPUntaggedResponse extends IMAPResponse {
// the response key word
protected String keyword;
/**
* Create a reply object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
public IMAPUntaggedResponse(String keyword, byte [] response) {
super(response);
this.keyword = keyword;
}
/**
* Return the KEYWORD that identifies the type
* of this untagged response.
*
* @return The identifying keyword.
*/
public String getKeyword() {
return keyword;
}
/**
* Test if an untagged response is of a given
* keyword type.
*
* @param keyword The test keyword.
*
* @return True if this is a type match, false for mismatches.
*/
public boolean isKeyword(String keyword) {
return this.keyword.equals(keyword);
}
}
././@LongLink 0000000 0000000 0000000 00000000171 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespace.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003325 10716317503 032271 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent a NAMESPACE response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPNamespace {
// the namespace prefix
public String prefix;
// the namespace hierarchy delimiter
public char separator = '\0';
public IMAPNamespace(IMAPResponseTokenizer source) throws MessagingException {
source.checkLeftParen();
// read the two that make up the response and ...
prefix = source.readString();
String delim = source.readString();
// if the delimiter is not a null string, grab the first character.
if (delim.length() != 0) {
separator = delim.charAt(0);
}
source.checkRightParen();
}
}
././@LongLink 0000000 0000000 0000000 00000000204 00000000000 011561 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPServerStatusResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003337 10716317503 032274 0 ustar brian brian /**
* 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;
/**
* Util class to represent an untagged response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPServerStatusResponse extends IMAPUntaggedResponse {
// any message following the response
protected String message;
/**
* Create a reply object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
public IMAPServerStatusResponse(String keyword, String message, byte [] response) {
super(keyword, response);
this.message = message;
}
/**
* Get any trailing message associated with this
* status response.
*
* @return
*/
public String getMessage() {
return message;
}
}
././@LongLink 0000000 0000000 0000000 00000000173 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageText.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003147 10716317503 032273 0 ustar brian brian /**
* 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 javax.mail.MessagingException;
public class IMAPMessageText extends IMAPFetchBodyPart {
// the header data
protected byte[] data;
/**
* Construct a top-level TEXT data item.
*
* @param data The data for the message text.
*
* @exception MessagingException
*/
public IMAPMessageText(byte[] data) throws MessagingException {
this(new IMAPBodySection(IMAPBodySection.TEXT), data);
}
public IMAPMessageText(IMAPBodySection section, byte[] data) throws MessagingException {
super(TEXT, section);
this.data = data;
}
/**
* Retrieved the header data.
*
* @return The header data.
*/
public byte[] getContent() {
return data;
}
}
././@LongLink 0000000 0000000 0000000 00000000172 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPOkResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004536 10716317503 032276 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent an untagged response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPOkResponse extends IMAPUntaggedResponse {
// the response status value
protected List status;
// any message following the response
protected String message;
/**
* Create a reply object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
public IMAPOkResponse(String keyword, List status, String message, byte [] response) {
super(keyword, response);
this.status = status;
this.message = message;
}
/**
* Get the response code included with the OK
* response.
*
* @return The string name of the response code.
*/
public String getResponseCode() {
return getKeyword();
}
/**
* Return the status argument values associated with
* this status response.
*
* @return The status value information, as a list of tokens.
*/
public List getStatus() {
return status;
}
/**
* Get any trailing message associated with this
* status response.
*
* @return
*/
public String getMessage() {
return message;
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUid.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002455 10716317503 032274 0 ustar brian brian /**
* 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 javax.mail.MessagingException;
public class IMAPUid extends IMAPFetchDataItem {
// the returned uid
public long uid;
// the returned sequence number for the message
public int messageNumber;
public IMAPUid(int messageNumber, IMAPResponseTokenizer source) throws MessagingException {
super(UID);
// just read the number pairs
this.messageNumber = messageNumber;
uid = source.readLong();
}
}
././@LongLink 0000000 0000000 0000000 00000000172 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPDateFormat.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004714 10716317503 032274 0 ustar brian brian /**
* 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.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* Formats ths date as specified by
* draft-ietf-drums-msg-fmt-08 dated January 26, 2000
* which supercedes RFC822.
*
*
* The format used is EEE, d MMM yyyy HH:mm:ss Z
and
* locale is always US-ASCII.
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPDateFormat extends SimpleDateFormat {
public IMAPDateFormat() {
super("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
}
public StringBuffer format(Date date, StringBuffer buffer, FieldPosition position) {
StringBuffer result = super.format(date, buffer, position);
// The RFC 2060 requires that the day in the date be formatted with either 2 digits
// or one digit. Our format specifies 2 digits, which pads with leading
// zeros. We need to check for this and whack it if it's there
if (result.charAt(0) == '0') {
result.deleteCharAt(0);
}
return result;
}
/**
* The calendar cannot be set
* @param calendar
* @throws UnsupportedOperationException
*/
public void setCalendar(Calendar calendar) {
throw new UnsupportedOperationException();
}
/**
* The format cannot be set
* @param format
* @throws UnsupportedOperationException
*/
public void setNumberFormat(NumberFormat format) {
throw new UnsupportedOperationException();
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxStatus.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000013562 10716317503 032275 0 ustar brian brian /**
* 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.util.List;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Utility class to aggregate status responses for a mailbox.
*/
public class IMAPMailboxStatus {
// the set of available flag values for this mailbox
public Flags availableFlags = null;
// the permanent flags for this mailbox.
public Flags permanentFlags = null;
// the open mode flags
public int mode = Folder.READ_WRITE;
// number of messages in the box
public int messages = -1;
// the number of newly added messages
public int recentMessages = -1;
// the number of unseen messages
public int unseenMessages = -1;
// the next UID for this mailbox
public long uidNext = -1L;
// the UID validity item
public long uidValidity = -1L;
public IMAPMailboxStatus() {
}
/**
* Merge information from a server status message. These
* messages are in the form "* NAME args". We only handle
* STATUS and FLAGS messages here.
*
* @param source The parsed status message.
*
* @exception MessagingException
*/
public void mergeStatus(IMAPStatusResponse source) throws MessagingException {
// update any of the values that have changed since the last.
if (source.messages != -1) {
messages = source.messages;
}
if (source.uidNext != -1L) {
uidNext = source.uidNext;
}
if (source.uidValidity != -1L) {
uidValidity = source.uidValidity;
}
if (source.recentMessages != -1) {
recentMessages = source.recentMessages;
}
if (source.unseenMessages != -1) {
unseenMessages = source.unseenMessages;
}
}
/**
* Merge in the FLAGS response from an EXAMINE or
* SELECT mailbox command.
*
* @param response The returned FLAGS item.
*
* @exception MessagingException
*/
public void mergeFlags(IMAPFlagsResponse response) throws MessagingException {
if (response != null) {
availableFlags = response.getFlags();
}
}
public void mergeSizeResponses(List responses) throws MessagingException
{
for (int i = 0; i < responses.size(); i++) {
mergeStatus((IMAPSizeResponse)responses.get(i));
}
}
public void mergeOkResponses(List responses) throws MessagingException {
for (int i = 0; i < responses.size(); i++) {
mergeStatus((IMAPOkResponse)responses.get(i));
}
}
/**
* Gather mailbox status information from mailbox status
* messages. These messages come in as untagged messages in the
* form "* nnn NAME".
*
* @param source The parse message information.
*
* @exception MessagingException
*/
public void mergeStatus(IMAPSizeResponse source) throws MessagingException {
if (source != null) {
String name = source.getKeyword();
// untagged exists response
if (source.isKeyword("EXISTS")) {
messages = source.getSize();
}
// untagged resent response
else if (source.isKeyword("RECENT")) {
recentMessages = source.getSize();
}
}
}
/**
* Gather mailbox status information from mailbox status
* messages. These messages come in as untagged messages in the
* form "* OK [NAME args]".
*
* @param source The parse message information.
*
* @exception MessagingException
*/
public void mergeStatus(IMAPOkResponse source) throws MessagingException {
if (source != null) {
String name = source.getKeyword();
// untagged UIDVALIDITY response
if (source.isKeyword("UIDVALIDITY")) {
List arguments = source.getStatus();
uidValidity = ((Token)arguments.get(0)).getLong();
}
// untagged UIDNEXT response
if (source.isKeyword("UIDNEXT")) {
List arguments = source.getStatus();
uidNext = ((Token)arguments.get(0)).getLong();
}
// untagged unseen response
else if (source.isKeyword("UNSEEN")) {
List arguments = source.getStatus();
uidValidity = ((Token)arguments.get(0)).getInteger();
}
}
}
/**
* Gather mailbox status information from mailbox status
* messages. These messages come in as untagged messages in the
* form "* OK [NAME args]".
*
* @param source The parse message information.
*
* @exception MessagingException
*/
public void mergeStatus(IMAPPermanentFlagsResponse source) throws MessagingException {
if (source != null) {
// this is already parsed.
permanentFlags = source.flags;
}
}
}
././@LongLink 0000000 0000000 0000000 00000000177 00000000000 011572 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002444 10716317503 032272 0 ustar brian brian /**
* 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;
/**
* Util class to represent a status response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPMailboxResponse {
// count/message number parameter from the response.
public int count;
// the name of the status code
public String name;
public IMAPMailboxResponse(int count, String name) {
this.count = count;
this.name = name;
}
}
././@LongLink 0000000 0000000 0000000 00000000207 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponseHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003061 10716317503 032266 0 ustar brian brian /**
* 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;
public interface IMAPUntaggedResponseHandler {
/**
* Handle an unsolicited untagged response receive back from a command. This
* will be any responses left over after the command has cherry picked the
* bits that are relevent to the command just issued. It is important
* that the unsolicited response be reacted to in order to keep the message
* caches in sync.
*
* @param response The response to handle.
*
* @return true if the handle took care of the response and it should not be sent
* to additional handlers. false if broadcasting of the response should continue.
*/
public boolean handleResponse(IMAPUntaggedResponse response);
}
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004677 11375537364 032317 0 ustar brian brian /**
* 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.UnsupportedEncodingException;
import javax.mail.MessagingException;
/**
* Base class for all response messages.
*
* @version $Rev: 947075 $ $Date: 2010-05-21 13:12:20 -0400 (Fri, 21 May 2010) $
*/
public class IMAPResponse {
// The original (raw) response data
protected byte[] response;
/**
* Create a response object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
protected IMAPResponse(byte [] response) {
// set this as the current message and parse.
this.response = response;
}
/**
* Retrieve the raw response line data for this
* response message. Normally, this will be a complete
* single line response, unless there are quoted
* literals in the response data containing octet
* data.
*
* @return The byte array containing the response information.
*/
public byte[] getResponseData() {
return response;
}
/**
* Return the response message as a string value.
* This is intended for debugging purposes only. The
* response data might contain octet data that
* might not convert to character data appropriately.
*
* @return The string version of the response.
*/
public String toString() {
try {
return new String(response, "US-ASCII");
} catch (UnsupportedEncodingException e) {
}
return new String(response);
}
}
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSizeResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003163 10716317503 032271 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.mail.MessagingException;
/**
* Util class to represent a server size response.
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPSizeResponse extends IMAPUntaggedResponse {
// the associated size
protected int size;
/**
* Create a size response item.
*
* @param keyword The KEYWORD item associated with the size.
* @param size The size value.
* @param response The raw response data.
*/
public IMAPSizeResponse(String keyword, int size, byte [] response) {
super(keyword, response);
this.size = size;
}
public int getSize() {
return size;
}
}
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternalDate.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002701 10716317503 032266 0 ustar brian brian /**
* 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.util.Date;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.util.ResponseFormatException;
public class IMAPInternalDate extends IMAPFetchDataItem {
// the parsed date.
protected Date date;
public IMAPInternalDate(IMAPResponseTokenizer source) throws MessagingException {
super(INTERNALDATE);
// read the date from the stream
date = source.readDate();
}
/**
* Retrieved the parsed internal date object.
*
* @return The parsed Date object.
*/
public Date getDate() {
return date;
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000005015 10716317503 032267 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import javax.mail.Quota;
/**
* Util class to represent a list response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPQuotaResponse extends IMAPUntaggedResponse {
// the returned quota item
public Quota quota;
/**
* Construct a LIST response item. This can be either
* a response from a LIST command or an LSUB command,
* and will be tagged accordingly.
*
* @param type The type of resonse (LIST or LSUB).
* @param data The raw response data.
* @param source The tokenizer source.
*
* @exception MessagingException
*/
public IMAPQuotaResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("QUOTA", data);
// first token is the root name, which can be either an atom or a string.
String tokenName = source.readString();
// create a quota item for this
quota = new Quota(tokenName);
source.checkLeftParen();
List resources = new ArrayList();
while (source.notListEnd()) {
// quotas are returned as a set of triplets. The first element is the
// resource name, followed by the current usage and the limit value.
Quota.Resource resource = new Quota.Resource(source.readAtom(), source.readLong(), source.readLong());
resources.add(resource);
}
quota.resources = (Quota.Resource[])resources.toArray(new Quota.Resource[resources.size()]);
}
}
././@LongLink 0000000 0000000 0000000 00000000201 00000000000 011556 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaRootResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003704 10716317503 032272 0 ustar brian brian /**
* 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.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent a list response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPQuotaRootResponse extends IMAPUntaggedResponse {
// the mailbox this applies to
public String mailbox;
// The list of quota roots
public List roots;
/**
* Construct a LIST response item. This can be either
* a response from a LIST command or an LSUB command,
* and will be tagged accordingly.
*
* @param type The type of resonse (LIST or LSUB).
* @param data The raw response data.
* @param source The tokenizer source.
*
* @exception MessagingException
*/
public IMAPQuotaRootResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("QUOTAROOT", data);
// first token is the mailbox
mailbox = source.readEncodedString();
// get the root name list as the remainder of the command.
roots = source.readStrings();
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchDataItem.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003616 10716317503 032274 0 ustar brian brian /**
* 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 javax.mail.internet.MailDateFormat;
public class IMAPFetchDataItem {
public static final int FETCH = 0;
public static final int ENVELOPE = 1;
public static final int BODY = 2;
public static final int BODYSTRUCTURE = 3;
public static final int INTERNALDATE = 4;
public static final int SIZE = 5;
public static final int UID = 6;
public static final int TEXT = 7;
public static final int HEADER = 8;
public static final int FLAGS = 9;
// the type of the FETCH response item.
protected int type;
public IMAPFetchDataItem(int type) {
this.type = type;
}
/**
* Get the type of the FetchResponse.
*
* @return The type indicator.
*/
public int getType() {
return type;
}
/**
* Test if this fetch response is of the correct type.
*
* @param t The type to test against.
*
* @return True if the Fetch response contains the requested type information.
*/
public boolean isType(int t) {
return type == t;
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000017554 11156270711 032300 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.mail.MessagingException;
import javax.mail.internet.ContentDisposition;
import javax.mail.internet.ContentType;
import org.apache.geronimo.javamail.util.ResponseFormatException;
public class IMAPBodyStructure extends IMAPFetchDataItem {
// the MIME type information
public ContentType mimeType = new ContentType();
// the content disposition info
public ContentDisposition disposition = null;
// the message ID
public String contentID;
public String contentDescription;
public String transferEncoding;
// size of the message
public int bodySize;
// number of lines, which only applies to text types.
public int lines = -1;
// "parts is parts". If this is a multipart message, we have a body structure item for each subpart.
public IMAPBodyStructure[] parts;
// optional dispostiion parameters
public Map dispositionParameters;
// language parameters
public List languages;
// the MD5 hash
public String md5Hash;
// references to nested message information.
public IMAPEnvelope nestedEnvelope;
public IMAPBodyStructure nestedBody;
public IMAPBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
super(BODYSTRUCTURE);
parseBodyStructure(source);
}
protected void parseBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
// the body structure needs to start with a left paren
source.checkLeftParen();
// if we start with a parentized item, we have a multipart content type. We need to
// recurse on each of those as appropriate
if (source.peek().getType() == '(') {
parseMultipartBodyStructure(source);
}
else {
parseSinglepartBodyStructure(source);
}
}
protected void parseMultipartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
mimeType.setPrimaryType("multipart");
ArrayList partList = new ArrayList();
do {
// parse the subpiece (which might also be a multipart).
IMAPBodyStructure part = new IMAPBodyStructure(source);
partList.add(part);
// we keep doing this as long as we seen parenthized items.
} while (source.peek().getType() == '(');
parts = (IMAPBodyStructure[])partList.toArray(new IMAPBodyStructure[partList.size()]);
// get the subtype (required)
mimeType.setSubType(source.readString());
// if the next token is the list terminator, we're done. Otherwise, we need to read extension
// data.
if (source.checkListEnd()) {
return;
}
// read the content parameter information and copy into the ContentType.
mimeType.setParameterList(source.readParameterList());
// more optional stuff
if (source.checkListEnd()) {
return;
}
// go parse the extensions that are common to both single- and multi-part messages.
parseMessageExtensions(source);
}
protected void parseSinglepartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
// get the primary and secondary types.
mimeType.setPrimaryType(source.readString());
mimeType.setSubType(source.readString());
// read the parameters associated with the content type.
mimeType.setParameterList(source.readParameterList());
// now a bunch of string value parameters
contentID = source.readStringOrNil();
contentDescription = source.readStringOrNil();
transferEncoding = source.readStringOrNil();
bodySize = source.readInteger();
// is this an embedded message type? Embedded messages include envelope and body structure
// information for the embedded message next.
if (mimeType.match("message/rfc822")) {
// parse the nested information
nestedEnvelope = new IMAPEnvelope(source);
nestedBody = new IMAPBodyStructure(source);
lines = source.readInteger();
}
// text types include a line count
else if (mimeType.match("text/*")) {
lines = source.readInteger();
}
// now the optional extension data. All of these are optional, but must be in the specified order.
if (source.checkListEnd()) {
return;
}
md5Hash = source.readString();
// go parse the extensions that are common to both single- and multi-part messages.
parseMessageExtensions(source);
}
/**
* Parse common message extension information shared between
* single part and multi part messages.
*
* @param source The source tokenizer..
*/
protected void parseMessageExtensions(IMAPResponseTokenizer source) throws MessagingException {
// now the optional extension data. All of these are optional, but must be in the specified order.
if (source.checkListEnd()) {
return;
}
disposition = new ContentDisposition();
// now the dispostion. This is a string, followed by a parameter list.
if (source.peek(true).getType() == '(') {
source.checkLeftParen();
disposition.setDisposition(source.readString());
disposition.setParameterList(source.readParameterList());
source.checkRightParen();
} else if (source.peek(true) == IMAPResponseTokenizer.NIL) {
source.next();
} else {
throw new ResponseFormatException("Expecting NIL or '(' in response");
}
// once more
if (source.checkListEnd()) {
return;
}
// read the language info.
languages = source.readStringList();
// next is the body location information. The Javamail APIs don't really expose that, so
// we'll just skip over that.
// once more
if (source.checkListEnd()) {
return;
}
// read the location info.
source.readStringList();
// we don't recognize any other forms of extension, so just skip over these.
while (source.notListEnd()) {
source.skipExtensionItem();
}
// step over the closing paren
source.next();
}
/**
* Tests if a body structure is for a multipart body.
*
* @return true if this is a multipart body part, false for a single part.
*/
public boolean isMultipart() {
return parts != null;
}
/**
* Test if this body structure represents an attached message. If it's a
* message, this will be a single part of MIME type message/rfc822.
*
* @return True if this is a nested message type, false for either a multipart or
* a single part of another type.
*/
public boolean isAttachedMessage() {
return !isMultipart() && mimeType.match("message/rfc822");
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000036611 11404405474 032275 0 ustar brian brian /**
* 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.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.mail.MessagingException;
import javax.mail.event.FolderEvent;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
import org.apache.geronimo.javamail.util.ConnectionException;
public class IMAPResponseStream {
protected final int BUFFER_SIZE = 1024;
// our source input stream
protected InputStream in;
// The response buffer
IMAPResponseBuffer out;
// the buffer array
protected byte[] buffer = new byte[BUFFER_SIZE];
// the current buffer position
int position;
// the current buffer read length
int length;
public IMAPResponseStream(InputStream in) {
this.in = in;
out = new IMAPResponseBuffer();
}
public int read() throws IOException {
// if we can't read any more, that's an EOF condition.
if (!fillBufferIfNeeded()) {
return -1;
}
// just grab the next character
return buffer[position++];
}
protected boolean fillBufferIfNeeded() throws IOException {
// used up all of the data in the buffer?
if (position >= length) {
int readLength = 0;
// a read from a network connection can return 0 bytes,
// so we need to be prepared to handle a spin loop.
while (readLength == 0) {
readLength = in.read(buffer, 0, buffer.length);
}
// we may have hit the EOF. Indicate the read failure
if (readLength == -1) {
return false;
}
// set our new buffer positions.
position = 0;
length = readLength;
}
return true;
}
/**
* Read a single response line from the input stream, returning
* a parsed and processed response line.
*
* @return A parsed IMAPResponse item using the response data.
* @exception MessagingException
*/
public IMAPResponse readResponse() throws MessagingException
{
// reset our accumulator
out.reset();
// now read a buffer of data
byte[] data = readData();
// and create a tokenizer for parsing this down.
IMAPResponseTokenizer tokenizer = new IMAPResponseTokenizer(data);
// get the first token.
Token token = tokenizer.next();
int type = token.getType();
// a continuation response. This will terminate a response set.
if (type == Token.CONTINUATION) {
return new IMAPContinuationResponse(data);
}
// unsolicited response. There are multiple forms of these, which might actually be
// part of the response for the last issued command.
else if (type == Token.UNTAGGED) {
// step to the next token, which will give us the type
token = tokenizer.next();
// if the token is numeric, then this is a size response in the
// form "* nn type"
if (token.isType(Token.NUMERIC)) {
int size = token.getInteger();
token = tokenizer.next();
String keyword = token.getValue();
// FETCH responses require fairly complicated parsing. Other
// size/message updates are fairly generic.
if (keyword.equals("FETCH")) {
return new IMAPFetchResponse(size, data, tokenizer);
}
return new IMAPSizeResponse(keyword, size, data);
}
// this needs to be an ATOM type, which will tell us what format this untagged
// response is in. There are many different untagged formats, some general, some
// specific to particular command types.
if (token.getType() != Token.ATOM) {
try {
throw new MessagingException("Unknown server response: " + new String(data, "ISO8859-1"));
} catch (UnsupportedEncodingException e) {
throw new MessagingException("Unknown server response: " + new String(data));
}
}
String keyword = token.getValue();
// many response are in the form "* OK [keyword value] message".
if (keyword.equals("OK")) {
return parseUntaggedOkResponse(data, tokenizer);
}
// preauth status response
else if (keyword.equals("PREAUTH")) {
return new IMAPServerStatusResponse("PREAUTH", tokenizer.getRemainder(), data);
}
// preauth status response
else if (keyword.equals("BYE")) {
return new IMAPServerStatusResponse("BYE", tokenizer.getRemainder(), data);
}
else if (keyword.equals("BAD")) {
// these are generally ignored.
return new IMAPServerStatusResponse("BAD", tokenizer.getRemainder(), data);
}
else if (keyword.equals("NO")) {
// these are generally ignored.
return new IMAPServerStatusResponse("NO", tokenizer.getRemainder(), data);
}
// a complex CAPABILITY response
else if (keyword.equals("CAPABILITY")) {
return new IMAPCapabilityResponse(tokenizer, data);
}
// a complex LIST response
else if (keyword.equals("LIST")) {
return new IMAPListResponse("LIST", data, tokenizer);
}
// a complex FLAGS response
else if (keyword.equals("FLAGS")) {
// parse this into a flags set.
return new IMAPFlagsResponse(data, tokenizer);
}
// a complex LSUB response (identical in format to LIST)
else if (keyword.equals("LSUB")) {
return new IMAPListResponse("LSUB", data, tokenizer);
}
// a STATUS response, which will contain a list of elements
else if (keyword.equals("STATUS")) {
return new IMAPStatusResponse(data, tokenizer);
}
// SEARCH requests return an variable length list of message matches.
else if (keyword.equals("SEARCH")) {
return new IMAPSearchResponse(data, tokenizer);
}
// ACL requests return an variable length list of ACL values .
else if (keyword.equals("ACL")) {
return new IMAPACLResponse(data, tokenizer);
}
// LISTRIGHTS requests return a variable length list of RIGHTS values .
else if (keyword.equals("LISTRIGHTS")) {
return new IMAPListRightsResponse(data, tokenizer);
}
// MYRIGHTS requests return a list of user rights for a mailbox name.
else if (keyword.equals("MYRIGHTS")) {
return new IMAPMyRightsResponse(data, tokenizer);
}
// QUOTAROOT requests return a list of mailbox quota root names
else if (keyword.equals("QUOTAROOT")) {
return new IMAPQuotaRootResponse(data, tokenizer);
}
// QUOTA requests return a list of quota values for a root name
else if (keyword.equals("QUOTA")) {
return new IMAPQuotaResponse(data, tokenizer);
}
else if (keyword.equals("NAMESPACE")) {
return new IMAPNamespaceResponse(data, tokenizer);
}
}
// begins with a word, this should be the tagged response from the last command.
else if (type == Token.ATOM) {
String tag = token.getValue();
token = tokenizer.next();
String status = token.getValue();
// primary information in one of these is the status field, which hopefully
// is 'OK'
return new IMAPTaggedResponse(tag, status, tokenizer.getRemainder(), data);
}
try {
throw new MessagingException("Unknown server response: " + new String(data, "ISO8859-1"));
} catch (UnsupportedEncodingException e) {
throw new MessagingException("Unknown server response: " + new String(data));
}
}
/**
* Parse an unsolicited OK status response. These
* responses are of the form:
*
* * OK [keyword arguments ...] message
*
* The part in the brackets are optional, but
* most OK messages will have some sort of update.
*
* @param data The raw message data
* @param tokenizer The tokenizer being used for this message.
*
* @return An IMAPResponse instance for this message.
*/
private IMAPResponse parseUntaggedOkResponse(byte [] data, IMAPResponseTokenizer tokenizer) throws MessagingException {
Token token = tokenizer.peek();
// we might have an optional value here
if (token.getType() != '[') {
// this has no tagging item, so there's nothing to be processed
// later.
return new IMAPOkResponse("OK", null, tokenizer.getRemainder(), data);
}
// skip over the "[" token
tokenizer.next();
token = tokenizer.next();
String keyword = token.getValue();
// Permanent flags gets special handling
if (keyword.equals("PERMANENTFLAGS")) {
return new IMAPPermanentFlagsResponse(data, tokenizer);
}
ArrayList arguments = new ArrayList();
// strip off all of the argument tokens until the "]" list terminator.
token = tokenizer.next();
while (token.getType() != ']') {
arguments.add(token);
token = tokenizer.next();
}
// this has a tagged keyword and arguments that will be processed later.
return new IMAPOkResponse(keyword, arguments, tokenizer.getRemainder(), data);
}
/**
* Read a "line" of server response data. An individual line
* may span multiple line breaks, depending on syntax implications.
*
* @return
* @exception MessagingException
*/
public byte[] readData() throws MessagingException {
// reset out buffer accumulator
out.reset();
// read until the end of the response into our buffer.
readBuffer();
// get the accumulated data.
return out.toByteArray();
}
/**
* Read a buffer of data. This accumulates the data into a
* ByteArrayOutputStream, terminating the processing at a line
* break. This also handles line breaks that are the result
* of literal continuations in the stream.
*
* @exception MessagingException
* @exception IOException
*/
public void readBuffer() throws MessagingException {
while (true) {
int ch = nextByte();
// potential end of line? Check the next character, and if it is an end of line,
// we need to do literal processing.
if (ch == '\r') {
int next = nextByte();
if (next == '\n') {
// had a line break, which might be part of a literal marker. Check for the signature,
// and if we found it, continue with the next line. In any case, we're done with processing here.
checkLiteral();
return;
}
}
// write this to the buffer.
out.write(ch);
}
}
/**
* Check the line just read to see if we're processing a line
* with a literal value. Literals are encoded as "{length}\r\n",
* so if we've read up to the line break, we can check to see
* if we need to continue reading.
*
* If a literal marker is found, we read that many characters
* from the reader without looking for line breaks. Once we've
* read the literal data, we just read the rest of the line
* as normal (which might also end with a literal marker).
*
* @exception MessagingException
*/
public void checkLiteral() throws MessagingException {
try {
// see if we have a literal length signature at the end.
int length = out.getLiteralLength();
// -1 means no literal length, so we're done reading this particular response.
if (length == -1) {
return;
}
// we need to write out the literal line break marker.
out.write('\r');
out.write('\n');
// have something we're supposed to read for the literal?
if (length > 0) {
byte[] bytes = new byte[length];
int offset = 0;
// The InputStream can return less than the requested length if it needs to block.
// This may take a couple iterations to get everything, particularly if it's long.
while (length > 0) {
int read = -1;
try {
read = in.read(bytes, offset, length);
} catch (IOException e) {
throw new MessagingException("Unexpected read error on server connection", e);
}
// premature EOF we can't ignore.
if (read == -1) {
throw new MessagingException("Unexpected end of stream");
}
length -= read;
offset += read;
}
// write this out to the output stream.
out.write(bytes);
}
// Now that we have the literal data, we need to read the rest of the response line (which might contain
// additional literals). Just recurse on the line reading logic.
readBuffer();
} catch (IOException e) {
e.printStackTrace();
// this is a byte array output stream...should never happen
}
}
/**
* Get the next byte from the input stream, handling read errors
* and EOF conditions as MessagingExceptions.
*
* @return The next byte read from the stream.
* @exception MessagingException
*/
protected int nextByte() throws MessagingException {
try {
int next = in.read();
if (next == -1) {
throw new MessagingException("Read error on IMAP server connection");
}
return next;
} catch (IOException e) {
throw new MessagingException("Unexpected error on server stream", e);
}
}
}
././@LongLink 0000000 0000000 0000000 00000000173 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageSize.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002306 10716317503 032267 0 ustar brian brian /**
* 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 javax.mail.MessagingException;
public class IMAPMessageSize extends IMAPFetchDataItem {
// the size information
public int size;
public IMAPMessageSize(IMAPResponseTokenizer source) throws MessagingException {
super(SIZE);
// the size is just a single integer
size = source.readInteger();
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000011131 10716317503 032263 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import org.apache.geronimo.mail.util.Base64;
/**
* Util class to represent a response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPTaggedResponse extends IMAPResponse {
// the reply state
protected String status;
// the tag associated with a reply.
protected String tag;
// the message associated with the completion response
protected String message;
/**
* Create a command completion response for a
* submitted command. The tag prefix identifies
* the command this response is for.
*
* @param tag The command tag value.
* @param status The Status response (OK, BAD, or NO).
* @param message The remainder of the response, as a string.
* @param response The response data used to create the reply object.
*/
public IMAPTaggedResponse(String tag, String status, String message, byte [] response) {
super(response);
this.tag = tag;
this.status = status;
this.message = message;
}
/**
* Create a continuation response for a
* submitted command.
*
* @param response The response data used to create the reply object.
*/
public IMAPTaggedResponse(byte [] response) {
super(response);
this.tag = "";
this.status = "CONTINUATION";
this.message = message;
}
/**
* Test if the response code was "OK".
*
* @return True if the response status was OK, false for any other status.
*/
public boolean isOK() {
return status.equals("OK");
}
/**
* Test for an error return from a command.
*
* @return True if the response status was BAD.
*/
public boolean isBAD() {
return status.equals("BAD");
}
/**
* Test for an error return from a command.
*
* @return True if the response status was NO.
*/
public boolean isNO() {
return status.equals("NO");
}
/**
* Get the message included on the tagged response.
*
* @return The String message data.
*/
public String getMessage() {
return message;
}
/**
* Decode the message portion of a continuation challenge response.
*
* @return The byte array containing the decoded data.
*/
public byte[] decodeChallengeResponse()
{
// we're passed back a challenge value, Base64 encoded. Decode that portion of the
// response data.
return Base64.decode(response, 2, response.length - 2);
}
/**
* Test if this is a continuation response.
*
* @return True if this a continuation. false for a normal tagged response.
*/
public boolean isContinuation() {
return status.equals("CONTINUATION");
}
/**
* Test if the untagged response includes a given
* status indicator. Mostly used for checking
* READ-ONLY or READ-WRITE status after selecting a
* mail box.
*
* @param name The status name to check.
*
* @return true if this is found in the "[" "]" delimited
* section of the response message.
*/
public boolean hasStatus(String name) {
// see if we have the status bits at all
int statusStart = message.indexOf('[');
if (statusStart == -1) {
return false;
}
int statusEnd = message.indexOf(']');
String statusString = message.substring(statusStart, statusEnd).toUpperCase();
// just search for the status token.
return statusString.indexOf(name) != -1;
}
}
././@LongLink 0000000 0000000 0000000 00000000200 00000000000 011555 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMyRightsResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002576 10716317503 032300 0 ustar brian brian /**
* 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 javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.Rights;
/**
* Utility class to aggregate status responses for a mailbox.
*/
public class IMAPMyRightsResponse extends IMAPUntaggedResponse {
public String mailbox;
public Rights rights;
public IMAPMyRightsResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("MYRIGHTS", data);
mailbox = source.readEncodedString();
rights = new Rights(source.readString());
}
}
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPEnvelope.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004760 10716317503 032275 0 ustar brian brian /**
* 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.util.Date;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
public class IMAPEnvelope extends IMAPFetchDataItem {
// the following are various fields from the FETCH ENVELOPE structure. These
// should be self-explanitory.
public Date date;
public String subject;
public InternetAddress[] from;
public InternetAddress[] sender;
public InternetAddress[] replyTo;
public InternetAddress[] to;
public InternetAddress[] cc;
public InternetAddress[] bcc;
public String inReplyTo;
public String messageID;
/**
* Parse an IMAP FETCH ENVELOPE response into the component pieces.
*
* @param source The tokenizer for the response we're processing.
*/
public IMAPEnvelope(IMAPResponseTokenizer source) throws MessagingException {
super(ENVELOPE);
// these should all be a parenthetical list
source.checkLeftParen();
// the following fields are all positional
// The envelope date is defined in the spec as being an "nstring" value, which
// means it is either a string value or NIL.
date = source.readDateOrNil();
subject = source.readStringOrNil();
from = source.readAddressList();
sender = source.readAddressList();
replyTo = source.readAddressList();
to = source.readAddressList();
cc = source.readAddressList();
bcc = source.readAddressList();
inReplyTo = source.readStringOrNil();
messageID = source.readStringOrNil();
// make sure we have a correct close on the field.
source.checkRightParen();
}
}
././@LongLink 0000000 0000000 0000000 00000000201 00000000000 011556 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000136575 11404405474 032307 0 ustar brian brian /**
* 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.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.mail.Flags;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MailDateFormat;
import javax.mail.internet.ParameterList;
import org.apache.geronimo.javamail.util.ResponseFormatException;
/**
* @version $Rev: 953638 $ $Date: 2010-06-11 06:09:00 -0400 (Fri, 11 Jun 2010) $
*/
public class IMAPResponseTokenizer {
/*
* set up the decoding table.
*/
protected static final byte[] decodingTable = new byte[256];
protected static void initializeDecodingTable()
{
for (int i = 0; i < IMAPCommand.encodingTable.length; i++)
{
decodingTable[IMAPCommand.encodingTable[i]] = (byte)i;
}
}
static {
initializeDecodingTable();
}
// a singleton formatter for header dates.
protected static MailDateFormat dateParser = new MailDateFormat();
public static class Token {
// Constant values from J2SE 1.4 API Docs (Constant values)
public static final int ATOM = -1;
public static final int QUOTEDSTRING = -2;
public static final int LITERAL = -3;
public static final int NUMERIC = -4;
public static final int EOF = -5;
public static final int NIL = -6;
// special single character markers
public static final int CONTINUATION = '+';
public static final int UNTAGGED = '*';
/**
* The type indicator. This will be either a specific type, represented by
* a negative number, or the actual character value.
*/
private int type;
/**
* The String value associated with this token. All tokens have a String value,
* except for the EOF and NIL tokens.
*/
private String value;
public Token(int type, String value) {
this.type = type;
this.value = value;
}
public int getType() {
return type;
}
public String getValue() {
return value;
}
public boolean isType(int type) {
return this.type == type;
}
/**
* Return the token as an integer value. If this can't convert, an exception is
* thrown.
*
* @return The integer value of the token.
* @exception ResponseFormatException
*/
public int getInteger() throws MessagingException {
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
}
}
throw new ResponseFormatException("Number value expected in response; fount: " + value);
}
/**
* Return the token as a long value. If it can't convert, an exception is
* thrown.
*
* @return The token as a long value.
* @exception ResponseFormatException
*/
public long getLong() throws MessagingException {
if (value != null) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
}
}
throw new ResponseFormatException("Number value expected in response; fount: " + value);
}
/**
* Handy debugging toString() method for token.
*
* @return The string value of the token.
*/
public String toString() {
if (type == NIL) {
return "NIL";
}
else if (type == EOF) {
return "EOF";
}
if (value == null) {
return "";
}
return value;
}
}
public static final Token EOF = new Token(Token.EOF, null);
public static final Token NIL = new Token(Token.NIL, null);
private static final String WHITE = " \t\n\r";
// The list of delimiter characters we process when
// handling parsing of ATOMs.
private static final String atomDelimiters = "(){}%*\"\\" + WHITE;
// this set of tokens is a slighly expanded set used for
// specific response parsing. When dealing with Body
// section names, there are sub pieces to the name delimited
// by "[", "]", ".", "<", ">" and SPACE, so reading these using
// a superset of the ATOM processing makes for easier parsing.
private static final String tokenDelimiters = "<>[].(){}%*\"\\" + WHITE;
// the response data read from the connection
private byte[] response;
// current parsing position
private int pos;
public IMAPResponseTokenizer(byte [] response) {
this.response = response;
}
/**
* Get the remainder of the response as a string.
*
* @return A string representing the remainder of the response.
*/
public String getRemainder() {
// make sure we're still in range
if (pos >= response.length) {
return "";
}
try {
return new String(response, pos, response.length - pos, "ISO8859-1");
} catch (UnsupportedEncodingException e) {
return null;
}
}
public Token next() throws MessagingException {
return next(false);
}
public Token next(boolean nilAllowed) throws MessagingException {
return readToken(nilAllowed, false);
}
public Token next(boolean nilAllowed, boolean expandedDelimiters) throws MessagingException {
return readToken(nilAllowed, expandedDelimiters);
}
public Token peek() throws MessagingException {
return peek(false, false);
}
public Token peek(boolean nilAllowed) throws MessagingException {
return peek(nilAllowed, false);
}
public Token peek(boolean nilAllowed, boolean expandedDelimiters) throws MessagingException {
int start = pos;
try {
return readToken(nilAllowed, expandedDelimiters);
} finally {
pos = start;
}
}
/**
* Read an ATOM token from the parsed response.
*
* @return A token containing the value of the atom token.
*/
private Token readAtomicToken(String delimiters) {
// skip to next delimiter
int start = pos;
while (++pos < response.length) {
// break on the first non-atom character.
byte ch = response[pos];
if (delimiters.indexOf(response[pos]) != -1 || ch < 32 || ch >= 127) {
break;
}
}
try {
// Numeric tokens we store as a different type.
String value = new String(response, start, pos - start, "ISO8859-1");
try {
int intValue = Integer.parseInt(value);
return new Token(Token.NUMERIC, value);
} catch (NumberFormatException e) {
}
return new Token(Token.ATOM, value);
} catch (UnsupportedEncodingException e) {
return null;
}
}
/**
* Read the next token from the response.
*
* @return The next token from the response. White space is skipped, and comment
* tokens are also skipped if indicated.
* @exception ResponseFormatException
*/
private Token readToken(boolean nilAllowed, boolean expandedDelimiters) throws MessagingException {
String delimiters = expandedDelimiters ? tokenDelimiters : atomDelimiters;
if (pos >= response.length) {
return EOF;
} else {
byte ch = response[pos];
if (ch == '\"') {
return readQuotedString();
// beginning of a length-specified literal?
} else if (ch == '{') {
return readLiteral();
// white space, eat this and find a real token.
} else if (WHITE.indexOf(ch) != -1) {
eatWhiteSpace();
return readToken(nilAllowed, expandedDelimiters);
// either a CTL or special. These characters have a self-defining token type.
} else if (ch < 32 || ch >= 127 || delimiters.indexOf(ch) != -1) {
pos++;
return new Token((int)ch, String.valueOf((char)ch));
} else {
// start of an atom, parse it off.
Token token = readAtomicToken(delimiters);
// now, if we've been asked to look at NIL tokens, check to see if it is one,
// and return that instead of the ATOM.
if (nilAllowed) {
if (token.getValue().equalsIgnoreCase("NIL")) {
return NIL;
}
}
return token;
}
}
}
/**
* Read the next token from the response, returning it as a byte array value.
*
* @return The next token from the response. White space is skipped, and comment
* tokens are also skipped if indicated.
* @exception ResponseFormatException
*/
private byte[] readData(boolean nilAllowed) throws MessagingException {
if (pos >= response.length) {
return null;
} else {
byte ch = response[pos];
if (ch == '\"') {
return readQuotedStringData();
// beginning of a length-specified literal?
} else if (ch == '{') {
return readLiteralData();
// white space, eat this and find a real token.
} else if (WHITE.indexOf(ch) != -1) {
eatWhiteSpace();
return readData(nilAllowed);
// either a CTL or special. These characters have a self-defining token type.
} else if (ch < 32 || ch >= 127 || atomDelimiters.indexOf(ch) != -1) {
throw new ResponseFormatException("Invalid string value: " + ch);
} else {
// only process this if we're allowing NIL as an option.
if (nilAllowed) {
// start of an atom, parse it off.
Token token = next(true);
if (token.isType(Token.NIL)) {
return null;
}
// invalid token type.
throw new ResponseFormatException("Invalid string value: " + token.getValue());
}
// invalid token type.
throw new ResponseFormatException("Invalid string value: " + ch);
}
}
}
/**
* Extract a substring from the response string and apply any
* escaping/folding rules to the string.
*
* @param start The starting offset in the response.
* @param end The response end offset + 1.
*
* @return The processed string value.
* @exception ResponseFormatException
*/
private byte[] getEscapedValue(int start, int end) throws MessagingException {
ByteArrayOutputStream value = new ByteArrayOutputStream();
for (int i = start; i < end; i++) {
byte ch = response[i];
// is this an escape character?
if (ch == '\\') {
i++;
if (i == end) {
throw new ResponseFormatException("Invalid escape character");
}
value.write(response[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 && response[i + 1] == '\n') {
i++;
}
}
else {
// just append the ch value.
value.write(ch);
}
}
return value.toByteArray();
}
/**
* Parse out a quoted string from the response, applying escaping
* rules to the value.
*
* @return The QUOTEDSTRING token with the value.
* @exception ResponseFormatException
*/
private Token readQuotedString() throws MessagingException {
try {
String value = new String(readQuotedStringData(), "ISO8859-1");
return new Token(Token.QUOTEDSTRING, value);
} catch (UnsupportedEncodingException e) {
return null;
}
}
/**
* Parse out a quoted string from the response, applying escaping
* rules to the value.
*
* @return The byte array with the resulting string bytes.
* @exception ResponseFormatException
*/
private byte[] readQuotedStringData() throws MessagingException {
int start = pos + 1;
boolean requiresEscaping = false;
// skip to end of comment/string
while (++pos < response.length) {
byte ch = response[pos];
if (ch == '"') {
byte[] value;
if (requiresEscaping) {
value = getEscapedValue(start, pos);
}
else {
value = subarray(start, pos);
}
// step over the delimiter for all cases.
pos++;
return value;
}
else if (ch == '\\') {
pos++;
requiresEscaping = true;
}
// we need to process line breaks also
else if (ch == '\r') {
requiresEscaping = true;
}
}
throw new ResponseFormatException("Missing '\"'");
}
/**
* Parse out a literal string from the response, using the length
* encoded before the listeral.
*
* @return The LITERAL token with the value.
* @exception ResponseFormatException
*/
protected Token readLiteral() throws MessagingException {
try {
String value = new String(readLiteralData(), "ISO8859-1");
return new Token(Token.LITERAL, value);
} catch (UnsupportedEncodingException e) {
return null;
}
}
/**
* Parse out a literal string from the response, using the length
* encoded before the listeral.
*
* @return The byte[] array with the value.
* @exception ResponseFormatException
*/
protected byte[] readLiteralData() throws MessagingException {
int lengthStart = pos + 1;
// see if we have a close marker.
int lengthEnd = indexOf("}\r\n", lengthStart);
if (lengthEnd == -1) {
throw new ResponseFormatException("Missing terminator on literal length");
}
int count = 0;
try {
count = Integer.parseInt(substring(lengthStart, lengthEnd));
} catch (NumberFormatException e) {
throw new ResponseFormatException("Invalid literal length " + substring(lengthStart, lengthEnd));
}
// step over the length
pos = lengthEnd + 3;
// too long?
if (pos + count > response.length) {
throw new ResponseFormatException("Invalid literal length: " + count);
}
byte[] value = subarray(pos, pos + count);
pos += count;
return value;
}
/**
* Extract a substring from the response buffer.
*
* @param start The starting offset.
* @param end The end offset (+ 1).
*
* @return A String extracted from the buffer.
*/
protected String substring(int start, int end ) {
try {
return new String(response, start, end - start, "ISO8859-1");
} catch (UnsupportedEncodingException e) {
return null;
}
}
/**
* Extract a subarray from the response buffer.
*
* @param start The starting offset.
* @param end The end offset (+ 1).
*
* @return A byte array string extracted rom the buffer.
*/
protected byte[] subarray(int start, int end ) {
byte[] result = new byte[end - start];
System.arraycopy(response, start, result, 0, end - start);
return result;
}
/**
* Test if the bytes in the response buffer match a given
* string value.
*
* @param position The compare position.
* @param needle The needle string we're testing for.
*
* @return True if the bytes match the needle value, false for any
* mismatch.
*/
public boolean match(int position, String needle) {
int length = needle.length();
if (response.length - position < length) {
return false;
}
for (int i = 0; i < length; i++) {
if (response[position + i ] != needle.charAt(i)) {
return false;
}
}
return true;
}
/**
* Search for a given string starting from the current position
* cursor.
*
* @param needle The search string.
*
* @return The index of a match (in absolute byte position in the
* response buffer).
*/
public int indexOf(String needle) {
return indexOf(needle, pos);
}
/**
* Search for a string in the response buffer starting from the
* indicated position.
*
* @param needle The search string.
* @param position The starting buffer position.
*
* @return The index of the match position. Returns -1 for no match.
*/
public int indexOf(String needle, int position) {
// get the last possible match position
int last = response.length - needle.length();
// no match possible
if (last < position) {
return -1;
}
for (int i = position; i <= last; i++) {
if (match(i, needle)) {
return i;
}
}
return -1;
}
/**
* Skip white space in the token string.
*/
private void eatWhiteSpace() {
// skip to end of whitespace
while (++pos < response.length
&& WHITE.indexOf(response[pos]) != -1)
;
}
/**
* Ensure that the next token in the parsed response is a
* '(' character.
*
* @exception ResponseFormatException
*/
public void checkLeftParen() throws MessagingException {
Token token = next();
if (token.getType() != '(') {
throw new ResponseFormatException("Missing '(' in response");
}
}
/**
* Ensure that the next token in the parsed response is a
* ')' character.
*
* @exception ResponseFormatException
*/
public void checkRightParen() throws MessagingException {
Token token = next();
if (token.getType() != ')') {
throw new ResponseFormatException("Missing ')' in response");
}
}
/**
* Read a string-valued token from the response. A string
* valued token can be either a quoted string, a literal value,
* or an atom. Any other token type is an error.
*
* @return The string value of the source token.
* @exception ResponseFormatException
*/
public String readString() throws MessagingException {
Token token = next(true);
int type = token.getType();
if (type == Token.NIL) {
return null;
}
if (type != Token.ATOM && type != Token.QUOTEDSTRING && type != Token.LITERAL && type != Token.NUMERIC) {
throw new ResponseFormatException("String token expected in response: " + token.getValue());
}
return token.getValue();
}
/**
* Read an encoded string-valued token from the response. A string
* valued token can be either a quoted string, a literal value,
* or an atom. Any other token type is an error.
*
* @return The string value of the source token.
* @exception ResponseFormatException
*/
public String readEncodedString() throws MessagingException {
String value = readString();
return decode(value);
}
/**
* Decode a Base 64 encoded string value.
*
* @param original The original encoded string.
*
* @return The decoded string.
* @exception MessagingException
*/
public String decode(String original) throws MessagingException {
StringBuffer result = new StringBuffer();
for (int i = 0; i < original.length(); i++) {
char ch = original.charAt(i);
if (ch == '&') {
i = decode(original, i, result);
}
else {
result.append(ch);
}
}
return result.toString();
}
/**
* Decode a section of an encoded string value.
*
* @param original The original source string.
* @param index The current working index.
* @param result The StringBuffer used for the decoded result.
*
* @return The new index for the decoding operation.
* @exception MessagingException
*/
public static int decode(String original, int index, StringBuffer result) throws MessagingException {
// look for the section terminator
int terminator = original.indexOf('-', index);
// unmatched?
if (terminator == -1) {
throw new MessagingException("Invalid UTF-7 encoded string");
}
// is this just an escaped "&"?
if (terminator == index + 1) {
// append and skip over this.
result.append('&');
return index + 2;
}
// step over the starting char
index++;
int chars = terminator - index;
int quads = chars / 4;
int residual = chars % 4;
// buffer for decoded characters
byte[] buffer = new byte[4];
int bufferCount = 0;
// process each of the full triplet pairs
for (int i = 0; i < quads; i++) {
byte b1 = decodingTable[original.charAt(index++) & 0xff];
byte b2 = decodingTable[original.charAt(index++) & 0xff];
byte b3 = decodingTable[original.charAt(index++) & 0xff];
byte b4 = decodingTable[original.charAt(index++) & 0xff];
buffer[bufferCount++] = (byte)((b1 << 2) | (b2 >> 4));
buffer[bufferCount++] = (byte)((b2 << 4) | (b3 >> 2));
buffer[bufferCount++] = (byte)((b3 << 6) | b4);
// we've written 3 bytes to the buffer, but we might have a residual from a previous
// iteration to deal with.
if (bufferCount == 4) {
// two complete chars here
b1 = buffer[0];
b2 = buffer[1];
result.append((char)((b1 << 8) + (b2 & 0xff)));
b1 = buffer[2];
b2 = buffer[3];
result.append((char)((b1 << 8) + (b2 & 0xff)));
bufferCount = 0;
}
else {
// we need to save the 3rd byte for the next go around
b1 = buffer[0];
b2 = buffer[1];
result.append((char)((b1 << 8) + (b2 & 0xff)));
buffer[0] = buffer[2];
bufferCount = 1;
}
}
// properly encoded, we should have an even number of bytes left.
switch (residual) {
// no residual...so we better not have an extra in the buffer
case 0:
// this is invalid...we have an odd number of bytes so far,
if (bufferCount == 1) {
throw new MessagingException("Invalid UTF-7 encoded string");
}
// one byte left. This shouldn't be valid. We need at least 2 bytes to
// encode one unprintable char.
case 1:
throw new MessagingException("Invalid UTF-7 encoded string");
// ok, we have two bytes left, which can only encode a single byte. We must have
// a dangling unhandled char.
case 2:
{
if (bufferCount != 1) {
throw new MessagingException("Invalid UTF-7 encoded string");
}
byte b1 = decodingTable[original.charAt(index++) & 0xff];
byte b2 = decodingTable[original.charAt(index++) & 0xff];
buffer[bufferCount++] = (byte)((b1 << 2) | (b2 >> 4));
b1 = buffer[0];
b2 = buffer[1];
result.append((char)((b1 << 8) + (b2 & 0xff)));
break;
}
// we have 2 encoded chars. In this situation, we can't have a leftover.
case 3:
{
// this is invalid...we have an odd number of bytes so far,
if (bufferCount == 1) {
throw new MessagingException("Invalid UTF-7 encoded string");
}
byte b1 = decodingTable[original.charAt(index++) & 0xff];
byte b2 = decodingTable[original.charAt(index++) & 0xff];
byte b3 = decodingTable[original.charAt(index++) & 0xff];
buffer[bufferCount++] = (byte)((b1 << 2) | (b2 >> 4));
buffer[bufferCount++] = (byte)((b2 << 4) | (b3 >> 2));
b1 = buffer[0];
b2 = buffer[1];
result.append((char)((b1 << 8) + (b2 & 0xff)));
break;
}
}
// return the new scan location
return terminator + 1;
}
/**
* Read a string-valued token from the response, verifying this is an ATOM token.
*
* @return The string value of the source token.
* @exception ResponseFormatException
*/
public String readAtom() throws MessagingException {
return readAtom(false);
}
/**
* Read a string-valued token from the response, verifying this is an ATOM token.
*
* @return The string value of the source token.
* @exception ResponseFormatException
*/
public String readAtom(boolean expandedDelimiters) throws MessagingException {
Token token = next(false, expandedDelimiters);
int type = token.getType();
if (type != Token.ATOM) {
throw new ResponseFormatException("ATOM token expected in response: " + token.getValue());
}
return token.getValue();
}
/**
* Read a number-valued token from the response. This must be an ATOM
* token.
*
* @return The integer value of the source token.
* @exception ResponseFormatException
*/
public int readInteger() throws MessagingException {
Token token = next();
return token.getInteger();
}
/**
* Read a number-valued token from the response. This must be an ATOM
* token.
*
* @return The long value of the source token.
* @exception ResponseFormatException
*/
public int readLong() throws MessagingException {
Token token = next();
return token.getInteger();
}
/**
* Read a string-valued token from the response. A string
* valued token can be either a quoted string, a literal value,
* or an atom. Any other token type is an error.
*
* @return The string value of the source token.
* @exception ResponseFormatException
*/
public String readStringOrNil() throws MessagingException {
// we need to recognize the NIL token.
Token token = next(true);
int type = token.getType();
if (type != Token.ATOM && type != Token.QUOTEDSTRING && type != Token.LITERAL && type != Token.NIL) {
throw new ResponseFormatException("String token or NIL expected in response: " + token.getValue());
}
// this returns null if the token is the NIL token.
return token.getValue();
}
/**
* Read a quoted string-valued token from the response.
* Any other token type other than NIL is an error.
*
* @return The string value of the source token.
* @exception ResponseFormatException
*/
protected String readQuotedStringOrNil() throws MessagingException {
// we need to recognize the NIL token.
Token token = next(true);
int type = token.getType();
if (type != Token.QUOTEDSTRING && type != Token.NIL) {
throw new ResponseFormatException("String token or NIL expected in response");
}
// this returns null if the token is the NIL token.
return token.getValue();
}
/**
* Read a date from a response string. This is expected to be in
* Internet Date format, but there's a lot of variation implemented
* out there. If we're unable to format this correctly, we'll
* just return null.
*
* @return A Date object created from the source date.
*/
public Date readDate() throws MessagingException {
String value = readString();
try {
return dateParser.parse(value);
} catch (Exception e) {
// we're just skipping over this, so return null
return null;
}
}
/**
* Read a date from a response string. This is expected to be in
* Internet Date format, but there's a lot of variation implemented
* out there. If we're unable to format this correctly, we'll
* just return null.
*
* @return A Date object created from the source date.
*/
public Date readDateOrNil() throws MessagingException {
String value = readStringOrNil();
// this might be optional
if (value == null) {
return null;
}
try {
return dateParser.parse(value);
} catch (Exception e) {
// we're just skipping over this, so return null
return null;
}
}
/**
* Read an internet address from a Fetch response. The
* addresses are returned as a set of string tokens in the
* order "personal list mailbox host". Any of these tokens
* can be NIL.
*
* The address may also be the start of a group list, which
* is indicated by the host being NIL. If we have found the
* start of a group, then we need to parse multiple elements
* until we find the group end marker (indicated by both the
* mailbox and the host being NIL), and create a group
* InternetAddress instance from this.
*
* @return An InternetAddress instance parsed from the
* element.
* @exception ResponseFormatException
*/
public InternetAddress readAddress() throws MessagingException {
// we recurse, expecting a null response back for sublists.
if (peek().getType() != '(') {
return null;
}
// must start with a paren
checkLeftParen();
// personal information
String personal = readStringOrNil();
// the domain routine information.
String routing = readStringOrNil();
// the target mailbox
String mailbox = readStringOrNil();
// and finally the host
String host = readStringOrNil();
// and validate the closing paren
checkRightParen();
// if this is a real address, we need to compose
if (host != null) {
StringBuffer address = new StringBuffer();
if (routing != null) {
address.append(routing);
address.append(':');
}
address.append(mailbox);
address.append('@');
address.append(host);
try {
return new InternetAddress(address.toString(), personal);
} catch (UnsupportedEncodingException e) {
throw new ResponseFormatException("Invalid Internet address format");
}
}
else {
// we're going to recurse on this. If the mailbox is null (the group name), this is the group item
// terminator.
if (mailbox == null) {
return null;
}
StringBuffer groupAddress = new StringBuffer();
groupAddress.append(mailbox);
groupAddress.append(':');
int count = 0;
while (true) {
// now recurse until we hit the end of the list
InternetAddress member = readAddress();
if (member == null) {
groupAddress.append(';');
try {
return new InternetAddress(groupAddress.toString(), personal);
} catch (UnsupportedEncodingException e) {
throw new ResponseFormatException("Invalid Internet address format");
}
}
else {
if (count != 0) {
groupAddress.append(',');
}
groupAddress.append(member.toString());
count++;
}
}
}
}
/**
* Parse out a list of addresses. This list of addresses is
* surrounded by parentheses, and each address is also
* parenthized (SP?).
*
* @return An array of the parsed addresses.
* @exception ResponseFormatException
*/
public InternetAddress[] readAddressList() throws MessagingException {
// must start with a paren, but can be NIL also.
Token token = next(true);
int type = token.getType();
// either of these results in a null address. The caller determines based on
// context whether this was optional or not.
if (type == Token.NIL) {
return null;
}
// non-nil address and no paren. This is a syntax error.
else if (type != '(') {
throw new ResponseFormatException("Missing '(' in response");
}
List addresses = new ArrayList();
// we have a list, now parse it.
while (notListEnd()) {
// go read the next address. If we had an address, add to the list.
// an address ITEM cannot be NIL inside the parens.
InternetAddress address = readAddress();
addresses.add(address);
}
// we need to skip over the peeked token.
checkRightParen();
return (InternetAddress[])addresses.toArray(new InternetAddress[addresses.size()]);
}
/**
* Check to see if we're at the end of a parenthized list
* without advancing the parsing pointer. If we are at the
* end, then this will step over the closing paren.
*
* @return True if the next token is a closing list paren, false otherwise.
* @exception ResponseFormatException
*/
public boolean checkListEnd() throws MessagingException {
Token token = peek(true);
if (token.getType() == ')') {
// step over this token.
next();
return true;
}
return false;
}
/**
* Reads a string item which can be encoded either as a single
* string-valued token or a parenthized list of string tokens.
*
* @return A List containing all of the strings.
* @exception ResponseFormatException
*/
public List readStringList() throws MessagingException {
Token token = peek(true);
if (token.getType() == '(') {
List list = new ArrayList();
next();
while (notListEnd()) {
String value = readString();
// this can be NIL, technically
if (value != null) {
list.add(value);
}
}
// step over the closing paren
next();
return list;
}
else if (token != NIL) {
List list = new ArrayList();
// just a single string value.
String value = readString();
// this can be NIL, technically
if (value != null) {
list.add(value);
}
return list;
} else {
next();
}
return null;
}
/**
* Reads all remaining tokens and returns them as a list of strings.
* NIL values are not supported.
*
* @return A List containing all of the strings.
* @exception ResponseFormatException
*/
public List readStrings() throws MessagingException {
List list = new ArrayList();
while (hasMore()) {
String value = readString();
list.add(value);
}
return list;
}
/**
* Skip over an extension item. This may be either a string
* token or a parenthized item (with potential nesting).
*
* At the point where this is called, we're looking for a closing
* ')', but we know it is not that. An EOF is an error, however,
*/
public void skipExtensionItem() throws MessagingException {
Token token = next();
int type = token.getType();
// list form? Scan to find the correct list closure.
if (type == '(') {
skipNestedValue();
}
// found an EOF? Big problem
else if (type == Token.EOF) {
throw new ResponseFormatException("Missing ')'");
}
}
/**
* Skip over a parenthized value that we're not interested in.
* These lists may contain nested sublists, so we need to
* handle the nesting properly.
*/
public void skipNestedValue() throws MessagingException {
Token token = next();
while (true) {
int type = token.getType();
// list terminator?
if (type == ')') {
return;
}
// unexpected end of the tokens.
else if (type == Token.EOF) {
throw new ResponseFormatException("Missing ')'");
}
// encountered a nested list?
else if (type == '(') {
// recurse and finish this list.
skipNestedValue();
}
// we're just skipping the token.
token = next();
}
}
/**
* Get the next token and verify that it's of the expected type
* for the context.
*
* @param type The type of token we're expecting.
*/
public void checkToken(int type) throws MessagingException {
Token token = next();
if (token.getType() != type) {
throw new ResponseFormatException("Unexpected token: " + token.getValue());
}
}
/**
* Read the next token as binary data. The next token can be a literal, a quoted string, or
* the token NIL (which returns a null result). Any other token throws a ResponseFormatException.
*
* @return A byte array representing the rest of the response data.
*/
public byte[] readByteArray() throws MessagingException {
return readData(true);
}
/**
* Determine what type of token encoding needs to be
* used for a string value.
*
* @param value The string to test.
*
* @return Either Token.ATOM, Token.QUOTEDSTRING, or
* Token.LITERAL, depending on the characters contained
* in the value.
*/
static public int getEncoding(byte[] value) {
// a null string always needs to be represented as a quoted literal.
if (value.length == 0) {
return Token.QUOTEDSTRING;
}
for (int i = 0; i < value.length; i++) {
int ch = value[i];
// make sure the sign extension is eliminated
ch = ch & 0xff;
// check first for any characters that would
// disqualify a quoted string
// NULL
if (ch == 0x00) {
return Token.LITERAL;
}
// non-7bit ASCII
if (ch > 0x7F) {
return Token.LITERAL;
}
// carriage return
if (ch == '\r') {
return Token.LITERAL;
}
// linefeed
if (ch == '\n') {
return Token.LITERAL;
}
// now check for ATOM disqualifiers
if (atomDelimiters.indexOf(ch) != -1) {
return Token.QUOTEDSTRING;
}
// CTL character. We've already eliminated the high characters
if (ch < 0x20) {
return Token.QUOTEDSTRING;
}
}
// this can be an ATOM token
return Token.ATOM;
}
/**
* Read a ContentType or ContentDisposition parameter
* list from an IMAP command response.
*
* @return A ParameterList instance containing the parameters.
* @exception MessagingException
*/
public ParameterList readParameterList() throws MessagingException {
ParameterList params = new ParameterList();
// read the tokens, taking NIL into account.
Token token = next(true, false);
// just return an empty list if this is NIL
if (token.isType(token.NIL)) {
return params;
}
// these are pairs of strings for each parameter value
while (notListEnd()) {
String name = readString();
String value = readString();
params.set(name, value);
}
// we need to consume the list terminator
checkRightParen();
return params;
}
/**
* Test if we have more data in the response buffer.
*
* @return true if there are more tokens to process. false if
* we've reached the end of the stream.
*/
public boolean hasMore() throws MessagingException {
// we need to eat any white space that might be in the stream.
eatWhiteSpace();
return pos < response.length;
}
/**
* Tests if we've reached the end of a parenthetical
* list in our parsing stream.
*
* @return true if the next token will be a ')'. false if the
* next token is anything else.
* @exception MessagingException
*/
public boolean notListEnd() throws MessagingException {
return peek().getType() != ')';
}
/**
* Read a list of Flag values from an IMAP response,
* returning a Flags instance containing the appropriate
* pieces.
*
* @return A Flags instance with the flag values.
* @exception MessagingException
*/
public Flags readFlagList() throws MessagingException {
Flags flags = new Flags();
// this should be a list here
checkLeftParen();
// run through the flag list
while (notListEnd()) {
// the flags are a bit of a pain. The flag names include "\" in the name, which
// is not a character allowed in an atom. This requires a bit of customized parsing
// to handle this.
Token token = next();
// flags can be specified as just atom tokens, so allow this as a user flag.
if (token.isType(token.ATOM)) {
// append the atom as a raw name
flags.add(token.getValue());
}
// all of the system flags start with a '\' followed by
// an atom. They also can be extension flags. IMAP has a special
// case of "\*" that we need to check for.
else if (token.isType('\\')) {
token = next();
// the next token is the real bit we need to process.
if (token.isType('*')) {
// this indicates USER flags are allowed.
flags.add(Flags.Flag.USER);
}
// if this is an atom name, handle as a system flag
else if (token.isType(Token.ATOM)) {
String name = token.getValue();
if (name.equalsIgnoreCase("Seen")) {
flags.add(Flags.Flag.SEEN);
}
else if (name.equalsIgnoreCase("RECENT")) {
flags.add(Flags.Flag.RECENT);
}
else if (name.equalsIgnoreCase("DELETED")) {
flags.add(Flags.Flag.DELETED);
}
else if (name.equalsIgnoreCase("ANSWERED")) {
flags.add(Flags.Flag.ANSWERED);
}
else if (name.equalsIgnoreCase("DRAFT")) {
flags.add(Flags.Flag.DRAFT);
}
else if (name.equalsIgnoreCase("FLAGGED")) {
flags.add(Flags.Flag.FLAGGED);
}
else {
// this is a server defined flag....just add the name with the
// flag thingy prepended.
flags.add("\\" + name);
}
}
else {
throw new MessagingException("Invalid Flag: " + token.getValue());
}
}
else {
throw new MessagingException("Invalid Flag: " + token.getValue());
}
}
// step over this for good practice.
checkRightParen();
return flags;
}
/**
* Read a list of Flag values from an IMAP response,
* returning a Flags instance containing the appropriate
* pieces.
*
* @return A Flags instance with the flag values.
* @exception MessagingException
*/
public List readSystemNameList() throws MessagingException {
List flags = new ArrayList();
// this should be a list here
checkLeftParen();
// run through the flag list
while (notListEnd()) {
// the flags are a bit of a pain. The flag names include "\" in the name, which
// is not a character allowed in an atom. This requires a bit of customized parsing
// to handle this.
Token token = next();
// all of the system flags start with a '\' followed by
// an atom. They also can be extension flags. IMAP has a special
// case of "\*" that we need to check for.
if (token.isType('\\')) {
token = next();
// if this is an atom name, handle as a system flag
if (token.isType(Token.ATOM)) {
// add the token value to the list WITH the
// flag indicator included. The attributes method returns
// these flag indicators, so we need to include it.
flags.add("\\" + token.getValue());
}
else {
throw new MessagingException("Invalid Flag: " + token.getValue());
}
}
else {
throw new MessagingException("Invalid Flag: " + token.getValue());
}
}
// step over this for good practice.
checkRightParen();
return flags;
}
}
././@LongLink 0000000 0000000 0000000 00000000173 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003515 10716317503 032272 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.ACL;
import org.apache.geronimo.javamail.store.imap.Rights;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Utility class to aggregate status responses for a mailbox.
*/
public class IMAPACLResponse extends IMAPUntaggedResponse {
public String mailbox;
public ACL[] acls;
public IMAPACLResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("ACL", data);
mailbox = source.readEncodedString();
List temp = new ArrayList();
while (source.hasMore()) {
String name = source.readString();
String rights = source.readString();
temp.add(new ACL(name, new Rights(rights)));
}
acls = new ACL[temp.size()];
acls = (ACL[])temp.toArray(acls);
}
}
././@LongLink 0000000 0000000 0000000 00000000204 00000000000 011561 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPContinuationResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002770 10716317503 032274 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent a continuation response from an IMAP server.
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPContinuationResponse extends IMAPTaggedResponse {
/**
* Create a continuation object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
protected IMAPContinuationResponse(byte [] response) {
super(response);
}
}
././@LongLink 0000000 0000000 0000000 00000000173 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000024073 10716317503 032274 0 ustar brian brian /**
* 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.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.util.ResponseFormatException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Class to represent a FETCH response BODY segment qualifier. The qualifier is
* of the form "BODY[]<>". The optional section qualifier is
* a "." separated part specifiers. A part specifier is either a number, or
* one of the tokens HEADER, HEADER.FIELD, HEADER.FIELD.NOT, MIME, and TEXT.
* The partial specification is in the form "".
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPBodySection {
// the section type qualifiers
static public final int BODY = 0;
static public final int HEADERS = 1;
static public final int HEADERSUBSET = 2;
static public final int MIME = 3;
static public final int TEXT = 4;
// the optional part number
public String partNumber = "1";
// the string name of the section
public String sectionName = "";
// the section qualifier
public int section;
// the starting substring position
public int start = -1;
// the substring length (requested)
public int length = -1;
// the list of any explicit header names
public List headers = null;
/**
* Construct a simple-toplevel BodySection tag.
*
* @param section The section identifier.
*/
public IMAPBodySection(int section) {
this.section = section;
partNumber = "1";
start = -1;
length = -1;
}
/**
* construct a BodySegment descriptor from the FETCH returned name.
*
* @param name The name code, which may be encoded with a section identifier and
* substring qualifiers.
*
* @exception MessagingException
*/
public IMAPBodySection(IMAPResponseTokenizer source) throws MessagingException {
// this could be just "BODY" alone.
if (!source.peek(false, true).isType('[')) {
// complete body, all other fields take default
section = BODY;
return;
}
// now we need to scan along this, building up the pieces as we go.
// NOTE: The section identifiers use "[", "]", "." as delimiters, which
// are normally acceptable in ATOM names. We need to use the expanded
// delimiter set to parse these tokens off.
Token token = source.next(false, true);
// the first token was the "[", now step to the next token in line.
token = source.next(false, true);
if (token.isType(Token.NUMERIC)) {
token = parsePartNumber(token, source);
}
// have a potential name here?
if (token.isType(Token.ATOM)) {
token = parseSectionName(token, source);
}
// the HEADER.FIELD and HEADER.FIELD.NOT section types
// are followed by a list of header names.
if (token.isType('(')) {
token = parseHeaderList(source);
}
// ok, in theory, our current token should be a ']'
if (!token.isType(']')) {
throw new ResponseFormatException("Invalid section identifier on FETCH response");
}
// do we have a substring qualifier?
// that needs to be stripped off too
parseSubstringValues(source);
// now fill in the type information
if (sectionName.equals("")) {
section = BODY;
}
else if (sectionName.equals("HEADER")) {
section = HEADERS;
}
else if (sectionName.equals("HEADER.FIELDS")) {
section = HEADERSUBSET;
}
else if (sectionName.equals("HEADER.FIELDS.NOT")) {
section = HEADERSUBSET;
}
else if (sectionName.equals("TEXT")) {
section = TEXT;
}
else if (sectionName.equals("MIME")) {
section = MIME;
}
}
/**
* Strip the part number off of a BODY section identifier. The part number
* is a series of "." separated tokens. So "BODY[3.2.1]" would be the BODY for
* section 3.2.1 of a multipart message. The section may also have a qualifier
* name on the end. "BODY[3.2.1.HEADER}" would be the HEADERS for that
* body section. The return value is the name of the section, which can
* be a "" or the the section qualifier (e.g., "HEADER").
*
* @param name The section name.
*
* @return The remainder of the section name after the numeric part number has
* been removed.
*/
private Token parsePartNumber(Token token, IMAPResponseTokenizer source) throws MessagingException {
StringBuffer part = new StringBuffer(token.getValue());
// NB: We're still parsing with the expanded delimiter set
token = source.next(false, true);
while (true) {
// Not a period? We've reached the end of the section number,
// finalize the part number and let the caller figure out what
// to do from here.
if (!token.isType('.')) {
partNumber = part.toString();
return token;
}
// might have another number section
else {
// step to the next token
token = source.next(false, true);
// another section number piece?
if (token.isType(Token.NUMERIC)) {
// add this to the collection, and continue
part.append('.');
part.append(token.getValue());
token = source.next(false, true);
}
else {
partNumber = part.toString();
// this is likely the start of the section name
return token;
}
}
}
}
/**
* Parse the section name, if any, in a BODY section qualifier. The
* section name may stand alone within the body section (e.g.,
* "BODY[HEADERS]" or follow the section number (e.g.,
* "BODY[1.2.3.HEADERS.FIELDS.NOT]".
*
* @param token The first token of the name sequence.
* @param source The source tokenizer.
*
* @return The first non-name token in the response.
*/
private Token parseSectionName(Token token, IMAPResponseTokenizer source) throws MessagingException {
StringBuffer part = new StringBuffer(token.getValue());
// NB: We're still parsing with the expanded delimiter set
token = source.next(false, true);
while (true) {
// Not a period? We've reached the end of the section number,
// finalize the part number and let the caller figure out what
// to do from here.
if (!token.isType('.')) {
sectionName = part.toString();
return token;
}
// might have another number section
else {
// add this to the collection, and continue
part.append('.');
part.append(source.readString());
token = source.next(false, true);
}
}
}
/**
* Parse a header list that may follow the HEADER.FIELD or HEADER.FIELD.NOT
* name qualifier. This is a list of string values enclosed in parens.
*
* @param source The source tokenizer.
*
* @return The next token in the response (which should be the section terminator, ']')
* @exception MessagingException
*/
private Token parseHeaderList(IMAPResponseTokenizer source) throws MessagingException {
headers = new ArrayList();
// normal parsing rules going on here
while (source.notListEnd()) {
String value = source.readString();
headers.add(value);
}
// step over the closing paren
source.next();
// NB, back to the expanded token rules again
return source.next(false, true);
}
/**
* Parse off the substring values following the section identifier, if
* any. If present, they will be in the format "".
*
* @param source The source tokenizer.
*
* @exception MessagingException
*/
private void parseSubstringValues(IMAPResponseTokenizer source) throws MessagingException {
// We rarely have one of these, so it's a quick out
if (!source.peek(false, true).isType('<')) {
return;
}
// step over the angle bracket.
source.next(false, true);
// pull out the start information
start = source.next(false, true).getInteger();
// step over the period
source.next(false, true);
// now the length bit
length = source.next(false, true).getInteger();
// and consume the closing angle bracket
source.next(false, true);
}
}
././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004453 11140646643 032276 0 ustar brian brian /**
* 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.ByteArrayInputStream;
import java.io.InputStream;
import javax.mail.MessagingException;
/**
* The full body content of a message.
*/
public class IMAPBody extends IMAPFetchBodyPart {
// the body content data
byte[] content = null;
/**
* Construct a top-level MessageText data item.
*
* @param data The data for the Message Text
*
* @exception MessagingException
*/
public IMAPBody(byte[] data) throws MessagingException {
this(new IMAPBodySection(IMAPBodySection.BODY), data);
}
/**
* Create a Message Text instance.
*
* @param section The section information. This may include substring information if this
* was just a partical fetch.
* @param data The message content data.
*
* @exception MessagingException
*/
public IMAPBody(IMAPBodySection section, byte[] data) throws MessagingException {
super(BODY, section);
// save the content
content = data;
}
/**
* Get the part content as a byte array.
*
* @return The part content as a byte array.
*/
public byte[] getContent() {
return content;
}
/**
* Get an input stream for reading the part content.
*
* @return An ByteArrayInputStream sourced to the part content.
*/
public InputStream getInputStream() {
return new ByteArrayInputStream(content);
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003561 10716317503 032273 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
/**
* Utility class to aggregate status responses for a mailbox.
*/
public class IMAPSearchResponse extends IMAPUntaggedResponse {
public int[] messageNumbers;
public IMAPSearchResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("SEARCH", data);
Token token = source.next();
List tokens = new ArrayList();
// just accumulate the list of tokens first
while (token.getType() != Token.EOF) {
tokens.add(token);
token = source.next();
}
messageNumbers = new int[tokens.size()];
// now parse these into numbers
for (int i = 0; i < messageNumbers.length; i++) {
token = (Token)tokens.get(i);
messageNumbers[i] = token.getInteger();
}
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlagsResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000002746 10716317503 032277 0 ustar brian brian /**
* 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 javax.mail.MessagingException;
import javax.mail.Flags;
import java.util.List;
/**
* A parsed FLAGS untagged response.
*/
public class IMAPFlagsResponse extends IMAPUntaggedResponse {
protected Flags flags = new Flags();
public IMAPFlagsResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("FLAGS", data);
// read the flags from the response tokenizer.
flags = source.readFlagList();
}
/**
* Get the parsed flags value.
*
* @return The accumulated flags setting.
*/
public Flags getFlags() {
return flags;
}
}
././@LongLink 0000000 0000000 0000000 00000000200 00000000000 011555 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchDateFormat.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004611 10716317503 032270 0 ustar brian brian /**
* 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.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* Formats ths date in the form used by the javamail IMAP SEARCH command,
*
* The format used is d MMM yyyy
and locale is always US-ASCII.
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPSearchDateFormat extends SimpleDateFormat {
public IMAPSearchDateFormat() {
super("dd-MMM-yyyy", Locale.US);
}
public StringBuffer format(Date date, StringBuffer buffer, FieldPosition position) {
StringBuffer result = super.format(date, buffer, position);
// The RFC 2060 requires that the day in the date be formatted with either 2 digits
// or one digit. Our format specifies 2 digits, which pads with leading
// zeros. We need to check for this and whack it if it's there
if (result.charAt(0) == '0') {
result.deleteCharAt(0);
}
return result;
}
/**
* The calendar cannot be set
* @param calendar
* @throws UnsupportedOperationException
*/
public void setCalendar(Calendar calendar) {
throw new UnsupportedOperationException();
}
/**
* The format cannot be set
* @param format
* @throws UnsupportedOperationException
*/
public void setNumberFormat(NumberFormat format) {
throw new UnsupportedOperationException();
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000056273 10721056121 032273 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.StoreClosedException;
import org.apache.geronimo.javamail.store.imap.IMAPStore;
import org.apache.geronimo.javamail.util.ProtocolProperties;
public class IMAPConnectionPool {
protected static final String MAIL_PORT = "port";
protected static final String MAIL_POOL_SIZE = "connectionpoolsize";
protected static final String MAIL_POOL_TIMEOUT = "connectionpooltimeout";
protected static final String MAIL_SEPARATE_STORE_CONNECTION = "separatestoreconnection";
protected static final String MAIL_SASL_REALM = "sasl.realm";
protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid";
// 45 seconds, by default.
protected static final int DEFAULT_POOL_TIMEOUT = 45000;
protected static final String DEFAULT_MAIL_HOST = "localhost";
protected static final int MAX_CONNECTION_RETRIES = 3;
protected static final int MAX_POOL_WAIT = 500;
// Our hosting Store instance
protected IMAPStore store;
// our Protocol abstraction
protected ProtocolProperties props;
// our list of created connections
protected List poolConnections = new ArrayList();
// our list of available connections
protected List availableConnections = new ArrayList();
// the dedicated Store connection (if we're configured that way)
protected IMAPConnection storeConnection = null;
// our dedicated Store connection attribute
protected boolean dedicatedStoreConnection;
// the size of our connection pool (by default, we only keep a single connection in the pool)
protected int poolSize = 1;
// the connection timeout property
protected long poolTimeout;
// our debug flag
protected boolean debug;
// the target host
protected String host;
// the target server port.
protected int port;
// the username we connect with
protected String username;
// the authentication password.
protected String password;
// the SASL realm name
protected String realm;
// the authorization id. With IMAP, it's possible to
// log on with another's authorization.
protected String authid;
// Turned on when the store is closed for business.
protected boolean closed = false;
// the connection capabilities map
protected Map capabilities;
/**
* Create a connection pool associated with a give IMAPStore instance. The
* connection pool manages handing out connections for both the Store and
* Folder and Message usage.
*
* Depending on the session properties, the Store may be given a dedicated
* connection, or will share connections with the Folders. Connections may
* be requested from either the Store or Folders. Messages must request
* their connections from their hosting Folder, and only one connection is
* allowed per folder.
*
* @param store The Store we're creating the pool for.
* @param props The property bundle that defines protocol properties
* that alter the connection behavior.
*/
public IMAPConnectionPool(IMAPStore store, ProtocolProperties props) {
this.store = store;
this.props = props;
// get the pool size. By default, we just use a single connection that's
// shared among Store and all of the Folders. Since most apps that use
// javamail tend to be single-threaded, this generally poses no great hardship.
poolSize = props.getIntProperty(MAIL_POOL_SIZE, 1);
// get the timeout property. Default is 45 seconds.
poolTimeout = props.getIntProperty(MAIL_POOL_TIMEOUT, DEFAULT_POOL_TIMEOUT);
// we can create a dedicated connection over and above the pool set that's
// reserved for the Store instance to use.
dedicatedStoreConnection = props.getBooleanProperty(MAIL_SEPARATE_STORE_CONNECTION, false);
// if we have a dedicated pool connection, we allocated that from the pool. Add this to
// the total pool size so we don't find ourselves stuck if the pool size is 1.
if (dedicatedStoreConnection) {
poolSize++;
}
}
/**
* Manage the initial connection to the IMAP server. This is the first
* point where we obtain the information needed to make an actual server
* connection. Like the Store protocolConnect method, we return false
* if there's any sort of authentication difficulties.
*
* @param host The host of the IMAP server.
* @param port The IMAP server connection port.
* @param user The connection user name.
* @param password The connection password.
*
* @return True if we were able to connect and authenticate correctly.
* @exception MessagingException
*/
public synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
// NOTE: We don't check for the username/password being null at this point. It's possible that
// the server will send back a PREAUTH response, which means we don't need to go through login
// processing. We'll need to check the capabilities response after we make the connection to decide
// if logging in is necesssary.
// save this for subsequent connections. All pool connections will use this info.
// if the port is defaulted, then see if we have something configured in the session.
// if not configured, we just use the default default.
if (port == -1) {
// check for a property and fall back on the default if it's not set.
port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
// it's possible that -1 might have been explicitly set, so one last check.
if (port == -1) {
port = props.getDefaultPort();
}
}
// Before we do anything, let's make sure that we succesfully received a host
if ( host == null ) {
host = DEFAULT_MAIL_HOST;
}
this.host = host;
this.port = port;
this.username = username;
this.password = password;
// make sure we have the realm information
realm = props.getProperty(MAIL_SASL_REALM);
// get an authzid value, if we have one. The default is to use the username.
authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
// go create a connection and just add it to the pool. If there is an authenticaton error,
// return the connect failure, and we may end up trying again.
IMAPConnection connection = createPoolConnection();
if (connection == null) {
return false;
}
// save the capabilities map from the first connection.
capabilities = connection.getCapabilities();
// if we're using a dedicated store connection, remove this from the pool and
// reserve it for the store.
if (dedicatedStoreConnection)
{
storeConnection = connection;
// make sure this is hooked up to the store.
connection.addResponseHandler(store);
}
else {
// just put this back in the pool. It's ready for anybody to use now.
synchronized(this) {
availableConnections.add(connection);
}
}
// we're connection, authenticated, and ready to go.
return true;
}
/**
* Creates an authenticated pool connection and adds it to
* the connection pool. If there is an existing connection
* already in the pool, this returns without creating a new
* connection.
*
* @exception MessagingException
*/
protected IMAPConnection createPoolConnection() throws MessagingException {
IMAPConnection connection = new IMAPConnection(props, this);
if (!connection.protocolConnect(host, port, authid, realm, username, password)) {
// we only add live connections to the pool. Sever the connections and
// allow it to go free.
connection.closeServerConnection();
return null;
}
// add this to the master list. We do NOT add this to the
// available queue because we're handing this out.
synchronized(this) {
// uh oh, we closed up shop while we were doing this...clean it up a
// get out of here
if (closed) {
connection.close();
throw new StoreClosedException(store, "No Store connections available");
}
poolConnections.add(connection);
}
// return that connection
return connection;
}
/**
* Get a connection from the pool. We try to retrieve a live
* connection, but we test the connection's liveness before
* returning one. If we don't have a viable connection in
* the pool, we'll create a new one. The returned connection
* will be in the authenticated state already.
*
* @return An IMAPConnection object that is connected to the server.
*/
protected IMAPConnection getConnection() throws MessagingException {
int retryCount = 0;
// To keep us from falling into a futile failure loop, we'll only allow
// a set number of connection failures.
while (retryCount < MAX_CONNECTION_RETRIES) {
// first try for an already created one. If this returns
// null, then we'll probably have to make a new one.
IMAPConnection connection = getPoolConnection();
// cool, we got one, the hard part is done.
if (connection != null) {
return connection;
}
// ok, create a new one. This *should* work, but the server might
// have gone down, or other problem may occur. If we have a problem,
// retry the entire process...but only for a bit. No sense
// being stubborn about it.
connection = createPoolConnection();
if (connection != null) {
return connection;
}
// step the retry count
retryCount++;
}
throw new MessagingException("Unable to get connection to IMAP server");
}
/**
* Obtain a connection from the existing connection pool. If none are
* available, and we've reached the connection pool limit, we'll wait for
* some other thread to return one. It generally doesn't take too long, as
* they're usually only held for the time required to execute a single
* command. If we're not at the pool limit, return null, which will signal
* the caller to go ahead and create a new connection outside of the
* lock.
*
* @return Either an active connection instance, or null if the caller should go
* ahead and try to create a new connection.
* @exception MessagingException
*/
protected synchronized IMAPConnection getPoolConnection() throws MessagingException {
// if the pool is closed, we can't process this
if (closed) {
throw new StoreClosedException(store, "No Store connections available");
}
// we'll retry this a few times if the connection pool is full, but
// after that, we'll just create a new connection.
for (int i = 0; i < MAX_CONNECTION_RETRIES; i++) {
Iterator it = availableConnections.iterator();
while (it.hasNext()) {
IMAPConnection connection = (IMAPConnection)it.next();
// live or dead, we're going to remove this from the
// available list.
it.remove();
if (connection.isAlive(poolTimeout)) {
// return the connection to the requestor
return connection;
}
else {
// remove this from the pool...it's toast.
poolConnections.remove(connection);
// make sure this cleans up after itself.
connection.closeServerConnection();
}
}
// we've not found something usable in the pool. Now see if
// we're allowed to add another connection, or must just wait for
// someone else to return one.
if (poolConnections.size() >= poolSize) {
// check to see if we've been told to shutdown before waiting
if (closed) {
throw new StoreClosedException(store, "No Store connections available");
}
// we need to wait for somebody to return a connection
// once woken up, we'll spin around and try to snag one from
// the pool again.
try {
wait(MAX_POOL_WAIT);
} catch (InterruptedException e) {
}
// check to see if we've been told to shutdown while we waited
if (closed) {
throw new StoreClosedException(store, "No Store connections available");
}
}
else {
// exit out and create a new connection. Since
// we're going to be outside the synchronized block, it's possible
// we'll go over our pool limit. We'll take care of that when connections start
// getting returned.
return null;
}
}
// we've hit the maximum number of retries...just create a new connection.
return null;
}
/**
* Return a connection to the connection pool.
*
* @param connection The connection getting returned.
*
* @exception MessagingException
*/
protected void returnPoolConnection(IMAPConnection connection) throws MessagingException
{
synchronized(this) {
// If we're still within the bounds of our connection pool,
// just add this to the active list and send out a notification
// in case somebody else is waiting for the connection.
if (availableConnections.size() < poolSize) {
availableConnections.add(connection);
notify();
return;
}
// remove this from the connection pool...we have too many.
poolConnections.remove(connection);
}
// the additional cleanup occurs outside the synchronized block
connection.close();
}
/**
* Release a closed connection.
*
* @param connection The connection getting released.
*
* @exception MessagingException
*/
protected void releasePoolConnection(IMAPConnection connection) throws MessagingException
{
synchronized(this) {
// remove this from the connection pool...it's no longer usable.
poolConnections.remove(connection);
}
// the additional cleanup occurs outside the synchronized block
connection.close();
}
/**
* Get a connection for the Store. This will be either a
* dedicated connection object, or one from the pool, depending
* on the mail.imap.separatestoreconnection property.
*
* @return An authenticated connection object.
*/
public synchronized IMAPConnection getStoreConnection() throws MessagingException {
if (closed) {
throw new StoreClosedException(store, "No Store connections available");
}
// if we have a dedicated connection created, return it.
if (storeConnection != null) {
return storeConnection;
}
else {
IMAPConnection connection = getConnection();
// add the store as a response handler while it has it.
connection.addResponseHandler(store);
return connection;
}
}
/**
* Return the Store connection to the connection pool. If we have a dedicated
* store connection, this is simple. Otherwise, the connection goes back
* into the general connection pool.
*
* @param connection The connection getting returned.
*/
public synchronized void releaseStoreConnection(IMAPConnection connection) throws MessagingException {
// have a server disconnect situation?
if (connection.isClosed()) {
// we no longer have a dedicated store connection.
// we need to return to the pool from now on.
storeConnection = null;
// throw this away.
releasePoolConnection(connection);
}
else {
// if we have a dedicated connection, nothing to do really. Otherwise,
// return this connection to the pool.
if (storeConnection == null) {
// unhook the store from the connection.
connection.removeResponseHandler(store);
returnPoolConnection(connection);
}
}
}
/**
* Get a connection for Folder.
*
* @return An authenticated connection object.
*/
public IMAPConnection getFolderConnection() throws MessagingException {
// just get a connection from the pool
return getConnection();
}
/**
* Return a Folder connection to the connection pool.
*
* @param connection The connection getting returned.
*/
public void releaseFolderConnection(IMAPConnection connection) throws MessagingException {
// potentially, the server may have decided to shut us down.
// In that case, the connection is no longer usable, so we need
// to remove it from the list of available ones.
if (!connection.isClosed()) {
// back into the pool with yee, matey....arrggghhh
returnPoolConnection(connection);
}
else {
// can't return this one to the pool. It's been stomped on
releasePoolConnection(connection);
}
}
/**
* Close the entire connection pool.
*
* @exception MessagingException
*/
public synchronized void close() throws MessagingException {
// first close each of the connections. This also closes the
// store connection.
for (int i = 0; i < poolConnections.size(); i++) {
IMAPConnection connection = (IMAPConnection)poolConnections.get(i);
connection.close();
}
// clear the pool
poolConnections.clear();
availableConnections.clear();
storeConnection = null;
// turn out the lights, hang the closed sign on the wall.
closed = true;
}
/**
* Flush any connections from the pool that have not been used
* for at least the connection pool timeout interval.
*/
protected synchronized void closeStaleConnections() {
Iterator i = poolConnections.iterator();
while (i.hasNext()) {
IMAPConnection connection = (IMAPConnection)i.next();
// if this connection is a stale one, remove it from the pool
// and close it out.
if (connection.isStale(poolTimeout)) {
i.remove();
try {
connection.close();
} catch (MessagingException e) {
// ignored. we're just closing connections that are probably timed out anyway, so errors
// on those shouldn't have an effect on the real operation we're dealing with.
}
}
}
}
/**
* Return a connection back to the connection pool. If we're not
* over our limit, the connection is kept around. Otherwise, it's
* given a nice burial.
*
* @param connection The returned connection.
*/
protected synchronized void releaseConnection(IMAPConnection connection) {
// before adding this to the pool, close any stale connections we may
// have. The connection we're adding is quite likely to be a fresh one,
// so we should cache that one if we can.
closeStaleConnections();
// still over the limit?
if (poolConnections.size() + 1 > poolSize) {
try {
// close this out and forget we ever saw it.
connection.close();
} catch (MessagingException e) {
// ignore....this is a non-critical problem if this fails now.
}
}
else {
// listen to alerts on this connection, and put it back in the pool.
poolConnections.add(connection);
}
}
/**
* Cleanup time. Sever and cleanup all of the pool connection
* objects, including the special Store connection, if we have one.
*/
protected synchronized void freeAllConnections() {
for (int i = 0; i < poolConnections.size(); i++) {
IMAPConnection connection = (IMAPConnection)poolConnections.get(i);
try {
// close this out and forget we ever saw it.
connection.close();
} catch (MessagingException e) {
// ignore....this is a non-critical problem if this fails now.
}
}
// everybody, out of the pool!
poolConnections.clear();
// don't forget the special store connection, if we have one.
if (storeConnection != null) {
try {
// close this out and forget we ever saw it.
storeConnection.close();
} catch (MessagingException e) {
// ignore....this is a non-critical problem if this fails now.
}
storeConnection = null;
}
}
/**
* Test if this connection has a given capability.
*
* @param capability The capability name.
*
* @return true if this capability is in the list, false for a mismatch.
*/
public boolean hasCapability(String capability) {
if (capabilities == null) {
return false;
}
return capabilities.containsKey(capability);
}
}
././@LongLink 0000000 0000000 0000000 00000000172 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000217442 11375023623 032277 0 ustar brian brian /**
* 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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Quota;
import javax.mail.Session;
import javax.mail.UIDFolder;
import javax.mail.URLName;
import javax.mail.internet.InternetHeaders;
import javax.mail.search.SearchTerm;
import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
import org.apache.geronimo.javamail.store.imap.ACL;
import org.apache.geronimo.javamail.store.imap.Rights;
import org.apache.geronimo.javamail.util.CommandFailedException;
import org.apache.geronimo.javamail.util.InvalidCommandException;
import org.apache.geronimo.javamail.util.MailConnection;
import org.apache.geronimo.javamail.util.ProtocolProperties;
import org.apache.geronimo.javamail.util.TraceInputStream;
import org.apache.geronimo.javamail.util.TraceOutputStream;
import org.apache.geronimo.mail.util.Base64;
/**
* Simple implementation of IMAP transport. Just does plain RFC977-ish
* delivery.
*
* There is no way to indicate failure for a given recipient (it's possible to have a
* recipient address rejected). The sun impl throws exceptions even if others successful),
* but maybe we do a different way...
*
*
* @version $Rev: 946314 $ $Date: 2010-05-19 14:01:55 -0400 (Wed, 19 May 2010) $
*/
public class IMAPConnection extends MailConnection {
protected static final String CAPABILITY_LOGIN_DISABLED = "LOGINDISABLED";
// The connection pool we're a member of. This keeps holds most of the
// connnection parameter information for us.
protected IMAPConnectionPool pool;
// special input stream for reading individual response lines.
protected IMAPResponseStream reader;
// connection pool connections.
protected long lastAccess = 0;
// our handlers for any untagged responses
protected LinkedList responseHandlers = new LinkedList();
// the list of queued untagged responses.
protected List queuedResponses = new LinkedList();
// this is set on if we had a forced disconnect situation from
// the server.
protected boolean closed = false;
/**
* Normal constructor for an IMAPConnection() object.
*
* @param props The protocol properties abstraction containing our
* property modifiers.
* @param pool
*/
public IMAPConnection(ProtocolProperties props, IMAPConnectionPool pool) {
super(props);
this.pool = pool;
}
/**
* Connect to the server and do the initial handshaking.
*
* @exception MessagingException
*/
public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
this.serverHost = host;
this.serverPort = port;
this.realm = realm;
this.authid = authid;
this.username = username;
this.password = password;
boolean preAuthorized = false;
try {
// create socket and connect to server.
getConnection();
// we need to ask the server what its capabilities are. This can be done
// before we login.
getCapability();
// do a preauthoriziation check.
if (extractResponse("PREAUTH") != null) {
preAuthorized = true;
}
// make sure we process these now
processPendingResponses();
// if we're not already using an SSL connection, and we have permission to issue STARTTLS, AND
// the server supports this, then switch to TLS mode before continuing.
if (!sslConnection && props.getBooleanProperty(MAIL_STARTTLS_ENABLE, false) && hasCapability(CAPABILITY_STARTTLS)) {
// if the server supports TLS, then use it for the connection.
// on our connection.
// tell the server of our intention to start a TLS session
sendSimpleCommand("STARTTLS");
// The connection is then handled by the superclass level.
getConnectedTLSSocket();
// create the special reader for pulling the responses.
reader = new IMAPResponseStream(inputStream);
// the IMAP spec states that the capability response is independent of login state or
// user, but I'm not sure I believe that to be the case. It doesn't hurt to refresh
// the information again after establishing a secure connection.
getCapability();
// and we need to repeat this check.
if (extractResponse("PREAUTH") != null) {
preAuthorized = true;
}
}
// damn, no login required.
if (preAuthorized) {
return true;
}
// go login with the server
return login();
} catch (IOException e) {
if (debug) {
debugOut("I/O exception establishing connection", e);
}
throw new MessagingException("Connection error", e);
}
finally {
// make sure the queue is cleared
processPendingResponses();
}
}
/**
* Update the last access time for the connection.
*/
protected void updateLastAccess() {
lastAccess = System.currentTimeMillis();
}
/**
* Test if the connection has been sitting idle for longer than
* the set timeout period.
*
* @param timeout The allowed "freshness" interval.
*
* @return True if the connection has been active within the required
* interval, false if it has been sitting idle for too long.
*/
public boolean isStale(long timeout) {
return (System.currentTimeMillis() - lastAccess) > timeout;
}
/**
* Close the connection. On completion, we'll be disconnected from
* the server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// if we're already closed, get outta here.
if (socket == null) {
return;
}
try {
// say goodbye
logout();
} finally {
// and close up the connection. We do this in a finally block to make sure the connection
// is shut down even if quit gets an error.
closeServerConnection();
// get rid of our response processor too.
reader = null;
}
}
/**
* Create a transport connection object and connect it to the
* target server.
*
* @exception MessagingException
*/
protected void getConnection() throws IOException, MessagingException
{
// do all of the non-protocol specific set up. This will get our socket established
// and ready use.
super.getConnection();
// create the special reader for pulling the responses.
reader = new IMAPResponseStream(inputStream);
// set the initial access time stamp
updateLastAccess();
}
/**
* Process a simple command/response sequence between the
* client and the server. These are commands where the
* client is expecting them to "just work", and also will not
* directly process the reply information. Unsolicited untagged
* responses are dispatched to handlers, and a MessagingException
* will be thrown for any non-OK responses from the server.
*
* @param data The command data we're writing out.
*
* @exception MessagingException
*/
public void sendSimpleCommand(String data) throws MessagingException {
// create a command object and issue the command with that.
IMAPCommand command = new IMAPCommand(data);
sendSimpleCommand(command);
}
/**
* Process a simple command/response sequence between the
* client and the server. These are commands where the
* client is expecting them to "just work", and also will not
* directly process the reply information. Unsolicited untagged
* responses are dispatched to handlers, and a MessagingException
* will be thrown for any non-OK responses from the server.
*
* @param data The command data we're writing out.
*
* @exception MessagingException
*/
public void sendSimpleCommand(IMAPCommand data) throws MessagingException {
// the command sending process will raise exceptions for bad responses....
// we just need to send the command and forget about it.
sendCommand(data);
}
/**
* Sends a command down the socket, returning the server response.
*
* @param data The String form of the command.
*
* @return The tagged response information that terminates the command interaction.
* @exception MessagingException
*/
public IMAPTaggedResponse sendCommand(String data) throws MessagingException {
IMAPCommand command = new IMAPCommand(data);
return sendCommand(command);
}
/**
* Sends a command down the socket, returning the server response.
*
* @param data An IMAPCommand object with the prepared command information.
*
* @return The tagged (or continuation) response information that terminates the
* command response sequence.
* @exception MessagingException
*/
public synchronized IMAPTaggedResponse sendCommand(IMAPCommand data) throws MessagingException {
// check first
checkConnected();
try {
// have the command write the command data. This also prepends a tag.
data.writeTo(outputStream, this);
outputStream.flush();
// update the activity timestamp
updateLastAccess();
// get the received response
return receiveResponse();
} catch (IOException e) {
throw new MessagingException(e.toString(), e);
}
}
/**
* Sends a message down the socket and terminates with the
* appropriate CRLF
*
* @param data The string data to send.
*
* @return An IMAPTaggedResponse item returned from the server.
* @exception MessagingException
*/
public IMAPTaggedResponse sendLine(String data) throws MessagingException {
try {
return sendLine(data.getBytes("ISO8859-1"));
} catch (UnsupportedEncodingException e) {
// should never happen
return null;
}
}
/**
* Sends a message down the socket and terminates with the
* appropriate CRLF
*
* @param data The array of data to send to the server.
*
* @return The response item returned from the IMAP server.
* @exception MessagingException
*/
public IMAPTaggedResponse sendLine(byte[] data) throws MessagingException {
return sendLine(data, 0, data.length);
}
/**
* Sends a message down the socket and terminates with the
* appropriate CRLF
*
* @param data The source data array.
* @param offset The offset within the data array.
* @param length The length of data to send.
*
* @return The response line returned from the IMAP server.
* @exception MessagingException
*/
public synchronized IMAPTaggedResponse sendLine(byte[] data, int offset, int length) throws MessagingException {
// check first
checkConnected();
try {
outputStream.write(data, offset, length);
outputStream.write(CR);
outputStream.write(LF);
outputStream.flush();
// update the activity timestamp
updateLastAccess();
return receiveResponse();
} catch (IOException e) {
throw new MessagingException(e.toString(), e);
}
}
/**
* Get a reply line for an IMAP command.
*
* @return An IMAP reply object from the stream.
*/
public IMAPTaggedResponse receiveResponse() throws MessagingException {
while (true) {
// read and parse a response from the server.
IMAPResponse response = reader.readResponse();
// The response set is terminated by either a continuation response or a
// tagged response (we only have a single command active at one time).
if (response instanceof IMAPTaggedResponse) {
// update the access time stamp for later timeout processing.
updateLastAccess();
IMAPTaggedResponse tagged = (IMAPTaggedResponse)response;
// we turn these into exceptions here, which means the issuer doesn't have to
// worry about checking status.
if (tagged.isBAD()) {
throw new InvalidCommandException("Unexpected command IMAP command error");
}
else if (tagged.isNO()) {
throw new CommandFailedException("Unexpected error executing IMAP command");
}
return tagged;
}
else {
// all other unsolicited responses are either async status updates or
// additional elements of a command we just sent. These will be processed
// either during processing of the command response, or at the end of the
// current command processing.
queuePendingResponse((IMAPUntaggedResponse)response);
}
}
}
/**
* Get the servers capabilities from the wire....
*/
public void getCapability() throws MessagingException {
sendCommand("CAPABILITY");
// get the capabilities from the response.
IMAPCapabilityResponse response = (IMAPCapabilityResponse)extractResponse("CAPABILITY");
capabilities = response.getCapabilities();
authentications = response.getAuthentications();
}
/**
* Logs out from the server.
*/
public void logout() throws MessagingException {
// We can just send the command and generally ignore the
// status response.
sendCommand("LOGOUT");
}
/**
* Deselect a mailbox when a folder returns a connection.
*
* @exception MessagingException
*/
public void closeMailbox() throws MessagingException {
// We can just send the command and generally ignore the
// status response.
sendCommand("CLOSE");
}
/**
* Authenticate with the server, if necessary (or possible).
*
* @return true if we were able to authenticate correctly, false for authentication failures.
* @exception MessagingException
*/
protected boolean login() throws MessagingException
{
// if no username or password, fail this immediately.
// the base connect property should resolve a username/password combo for us and
// try again.
if (username == null || password == null) {
return false;
}
// are we permitted to use SASL mechanisms?
if (props.getBooleanProperty(MAIL_SASL_ENABLE, false)) {
// we might be enable for SASL, but the client and the server might
// not have any supported mechanisms in common. Try again with another
// mechanism.
if (processSaslAuthentication()) {
return true;
}
}
// see if we're allowed to try plain.
if (!props.getBooleanProperty(MAIL_PLAIN_DISABLE, false) && supportsMechanism(AUTHENTICATION_PLAIN)) {
return processPlainAuthentication();
}
// see if we're allowed to try login.
if (!props.getBooleanProperty(MAIL_LOGIN_DISABLE, false) && supportsMechanism(AUTHENTICATION_LOGIN)) {
// no authzid capability with this authentication method.
return processLoginAuthentication();
}
// the server can choose to disable the LOGIN command. If not disabled, try
// using LOGIN rather than AUTHENTICATE.
if (!hasCapability(CAPABILITY_LOGIN_DISABLED)) {
return processLogin();
}
throw new MessagingException("No supported LOGIN methods enabled");
}
/**
* Process SASL-type authentication.
*
* @return Returns true if the server support a SASL authentication mechanism and
* accepted reponse challenges.
* @exception MessagingException
*/
protected boolean processSaslAuthentication() throws MessagingException {
// if unable to get an appropriate authenticator, just fail it.
ClientAuthenticator authenticator = getSaslAuthenticator();
if (authenticator == null) {
return false;
}
// go process the login.
return processLogin(authenticator);
}
protected ClientAuthenticator getSaslAuthenticator() {
return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
}
/**
* Process SASL-type PLAIN authentication.
*
* @return Returns true if the login is accepted.
* @exception MessagingException
*/
protected boolean processPlainAuthentication() throws MessagingException {
// go process the login.
return processLogin(new PlainAuthenticator(username, password));
}
/**
* Process SASL-type LOGIN authentication.
*
* @return Returns true if the login is accepted.
* @exception MessagingException
*/
protected boolean processLoginAuthentication() throws MessagingException {
// go process the login.
return processLogin(new LoginAuthenticator(username, password));
}
/**
* Process a LOGIN using the LOGIN command instead of AUTHENTICATE.
*
* @return true if the command succeeded, false for any authentication failures.
* @exception MessagingException
*/
protected boolean processLogin() throws MessagingException {
// arguments are "LOGIN userid password"
IMAPCommand command = new IMAPCommand("LOGIN");
command.appendAtom(username);
command.appendAtom(password);
// go issue the command
try {
sendCommand(command);
} catch (CommandFailedException e) {
// we'll get a NO response for a rejected login
return false;
}
// seemed to work ok....
return true;
}
/**
* Process a login using the provided authenticator object.
*
* NB: This method is synchronized because we have a multi-step process going on
* here. No other commands should be sent to the server until we complete.
*
* @return Returns true if the server support a SASL authentication mechanism and
* accepted reponse challenges.
* @exception MessagingException
*/
protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
if (debug) {
debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
}
IMAPCommand command = new IMAPCommand("AUTHENTICATE");
// and tell the server which mechanism we're using.
command.appendAtom(authenticator.getMechanismName());
// send the command now
try {
IMAPTaggedResponse response = sendCommand(command);
// now process the challenge sequence. We get a 235 response back when the server accepts the
// authentication, and a 334 indicates we have an additional challenge.
while (true) {
// this should be a continuation reply, if things are still good.
if (response.isContinuation()) {
// we're passed back a challenge value, Base64 encoded.
byte[] challenge = response.decodeChallengeResponse();
// have the authenticator evaluate and send back the encoded response.
response = sendLine(Base64.encode(authenticator.evaluateChallenge(challenge)));
}
else {
// there are only two choices here, OK or a continuation. OK means
// we've passed muster and are in.
return true;
}
}
} catch (CommandFailedException e ) {
// a failure at any point in this process will result in a "NO" response.
// That causes an exception to get thrown, so just fail the login
// if we get one.
return false;
}
}
/**
* Return the server host for this connection.
*
* @return The String name of the server host.
*/
public String getHost() {
return serverHost;
}
/**
* Attach a handler for untagged responses to this connection.
*
* @param h The new untagged response handler.
*/
public synchronized void addResponseHandler(IMAPUntaggedResponseHandler h) {
responseHandlers.add(h);
}
/**
* Remove a response handler from the connection.
*
* @param h The handler to remove.
*/
public synchronized void removeResponseHandler(IMAPUntaggedResponseHandler h) {
responseHandlers.remove(h);
}
/**
* Add a response to the pending untagged response queue.
*
* @param response The response to add.
*/
public synchronized void queuePendingResponse(IMAPUntaggedResponse response) {
queuedResponses.add(response);
}
/**
* Process any untagged responses in the queue. This will clear out
* the queue, and send each response to the registered
* untagged response handlers.
*/
public void processPendingResponses() throws MessagingException {
List pendingResponses = null;
List handlerList = null;
synchronized(this) {
if (queuedResponses.isEmpty()) {
return;
}
pendingResponses = queuedResponses;
queuedResponses = new LinkedList();
// get a copy of the response handlers so we can
// release the connection lock before broadcasting
handlerList = (List)responseHandlers.clone();
}
for (int i = 0; i < pendingResponses.size(); i++) {
IMAPUntaggedResponse response = (IMAPUntaggedResponse)pendingResponses.get(i);
for (int j = 0; j < handlerList.size(); j++) {
// broadcast to each handler. If a handler returns true, then it
// handled whatever this message required and we should skip sending
// it to other handlers.
IMAPUntaggedResponseHandler h = (IMAPUntaggedResponseHandler)handlerList.get(j);
if (h.handleResponse(response)) {
break;
}
}
}
}
/**
* Extract a single response from the pending queue that
* match a give keyword type. All matching responses
* are removed from the pending queue.
*
* @param type The string name of the keyword.
*
* @return A List of all matching queued responses.
*/
public IMAPUntaggedResponse extractResponse(String type) {
Iterator i = queuedResponses.iterator();
while (i.hasNext()) {
IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
// if this is of the target type, move it to the response set.
if (response.isKeyword(type)) {
i.remove();
return response;
}
}
return null;
}
/**
* Extract all responses from the pending queue that
* match a give keyword type. All matching responses
* are removed from the pending queue.
*
* @param type The string name of the keyword.
*
* @return A List of all matching queued responses.
*/
public List extractResponses(String type) {
List responses = new ArrayList();
Iterator i = queuedResponses.iterator();
while (i.hasNext()) {
IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
// if this is of the target type, move it to the response set.
if (response.isKeyword(type)) {
i.remove();
responses.add(response);
}
}
return responses;
}
/**
* Extract all responses from the pending queue that
* are "FETCH" responses for a given message number. All matching responses
* are removed from the pending queue.
*
* @param type The string name of the keyword.
*
* @return A List of all matching queued responses.
*/
public List extractFetchResponses(int sequenceNumber) {
List responses = new ArrayList();
Iterator i = queuedResponses.iterator();
while (i.hasNext()) {
IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
// if this is of the target type, move it to the response set.
if (response.isKeyword("FETCH")) {
IMAPFetchResponse fetch = (IMAPFetchResponse)response;
// a response for the correct message number?
if (fetch.sequenceNumber == sequenceNumber) {
// pluck these from the list and add to the response set.
i.remove();
responses.add(response);
}
}
}
return responses;
}
/**
* Extract a fetch response data item from the queued elements.
*
* @param sequenceNumber
* The message number we're interested in. Fetch responses for other messages
* will be skipped.
* @param type The type of body element we need. It is assumed that only one item for
* the given message number will exist in the queue. The located item will
* be returned, and that fetch response will be removed from the pending queue.
*
* @return The target data item, or null if a match is not found.
*/
protected IMAPFetchDataItem extractFetchDataItem(long sequenceNumber, int type)
{
Iterator i = queuedResponses.iterator();
while (i.hasNext()) {
IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
// if this is of the target type, move it to the response set.
if (response.isKeyword("FETCH")) {
IMAPFetchResponse fetch = (IMAPFetchResponse)response;
// a response for the correct message number?
if (fetch.sequenceNumber == sequenceNumber) {
// does this response have the item we're looking for?
IMAPFetchDataItem item = fetch.getDataItem(type);
if (item != null) {
// remove this from the pending queue and return the
// located item
i.remove();
return item;
}
}
}
}
// not located, sorry
return null;
}
/**
* Extract a all fetch responses that contain a given data item.
*
* @param type The type of body element we need. It is assumed that only one item for
* the given message number will exist in the queue. The located item will
* be returned, and that fetch response will be removed from the pending queue.
*
* @return A List of all matching Fetch responses.
*/
protected List extractFetchDataItems(int type)
{
Iterator i = queuedResponses.iterator();
List items = new ArrayList();
while (i.hasNext()) {
IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
// if this is of the target type, move it to the response set.
if (response.isKeyword("FETCH")) {
IMAPFetchResponse fetch = (IMAPFetchResponse)response;
// does this response have the item we're looking for?
IMAPFetchDataItem item = fetch.getDataItem(type);
if (item != null) {
// remove this from the pending queue and return the
// located item
i.remove();
// we want the fetch response, not the data item, because
// we're going to require the message sequence number information
// too.
items.add(fetch);
}
}
}
// return whatever we have.
return items;
}
/**
* Make sure we have the latest status information available. We
* retreive this by sending a NOOP command to the server, and
* processing any untagged responses we get back.
*/
public void updateMailboxStatus() throws MessagingException {
sendSimpleCommand("NOOP");
}
/**
* check to see if this connection is truely alive.
*
* @param timeout The timeout value to control how often we ping
* the server to see if we're still good.
*
* @return true if the server is responding to requests, false for any
* connection errors. This will also update the folder status
* by processing returned unsolicited messages.
*/
public synchronized boolean isAlive(long timeout) {
long lastUsed = System.currentTimeMillis() - lastAccess;
if (lastUsed < timeout) {
return true;
}
try {
sendSimpleCommand("NOOP");
return true;
} catch (MessagingException e) {
// the NOOP command will throw a MessagingException if we get anything
// other than an OK response back from the server.
}
return false;
}
/**
* Issue a fetch command to retrieve the message ENVELOPE structure.
*
* @param sequenceNumber The sequence number of the message.
*
* @return The IMAPResponse item containing the ENVELOPE information.
*/
public synchronized List fetchEnvelope(int sequenceNumber) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.startList();
command.appendAtom("ENVELOPE INTERNALDATE RFC822.SIZE");
command.endList();
// we want all of the envelope information about the message, which involves multiple FETCH chunks.
sendCommand(command);
// these are fairly involved sets, so the caller needs to handle these.
// we just return all of the FETCH results matching the target message number.
return extractFetchResponses(sequenceNumber);
}
/**
* Issue a FETCH command to retrieve the message BODYSTRUCTURE structure.
*
* @param sequenceNumber The sequence number of the message.
*
* @return The IMAPBodyStructure item for the message.
* All other untagged responses are queued for processing.
*/
public synchronized IMAPBodyStructure fetchBodyStructure(int sequenceNumber) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.startList();
command.appendAtom("BODYSTRUCTURE");
command.endList();
// we want all of the envelope information about the message, which involves multiple FETCH chunks.
sendCommand(command);
// locate the response from this
IMAPBodyStructure bodyStructure = (IMAPBodyStructure)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODYSTRUCTURE);
if (bodyStructure == null) {
throw new MessagingException("No BODYSTRUCTURE information received from IMAP server");
}
// and return the body structure directly.
return bodyStructure;
}
/**
* Issue a FETCH command to retrieve the message RFC822.HEADERS structure containing the message headers (using PEEK).
*
* @param sequenceNumber The sequence number of the message.
*
* @return The IMAPRFC822Headers item for the message.
* All other untagged responses are queued for processing.
*/
public synchronized InternetHeaders fetchHeaders(int sequenceNumber, String part) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.startList();
command.appendAtom("BODY.PEEK");
command.appendBodySection(part, "HEADER");
command.endList();
// we want all of the envelope information about the message, which involves multiple FETCH chunks.
sendCommand(command);
IMAPInternetHeader header = (IMAPInternetHeader)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.HEADER);
if (header == null) {
throw new MessagingException("No HEADER information received from IMAP server");
}
// and return the body structure directly.
return header.headers;
}
/**
* Issue a FETCH command to retrieve the message text
*
* @param sequenceNumber The sequence number of the message.
*
* @return The IMAPMessageText item for the message.
* All other untagged responses are queued for processing.
*/
public synchronized IMAPMessageText fetchText(int sequenceNumber) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.startList();
command.appendAtom("BODY.PEEK");
command.appendBodySection("TEXT");
command.endList();
// we want all of the envelope information about the message, which involves multiple FETCH chunks.
sendCommand(command);
IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);
if (text == null) {
throw new MessagingException("No TEXT information received from IMAP server");
}
// and return the body structure directly.
return text;
}
/**
* Issue a FETCH command to retrieve the message text
*
* @param sequenceNumber The sequence number of the message.
*
* @return The IMAPMessageText item for the message.
* All other untagged responses are queued for processing.
*/
public synchronized IMAPMessageText fetchBodyPartText(int sequenceNumber, String section) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.startList();
command.appendAtom("BODY.PEEK");
command.appendBodySection(section, "TEXT");
command.endList();
// we want all of the envelope information about the message, which involves multiple FETCH chunks.
sendCommand(command);
IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);
if (text == null) {
throw new MessagingException("No TEXT information received from IMAP server");
}
// and return the body structure directly.
return text;
}
/**
* Issue a FETCH command to retrieve the entire message body in one shot.
* This may also be used to fetch an embedded message part as a unit.
*
* @param sequenceNumber
* The sequence number of the message.
* @param section The section number to fetch. If null, the entire body of the message
* is retrieved.
*
* @return The IMAPBody item for the message.
* All other untagged responses are queued for processing.
* @exception MessagingException
*/
public synchronized IMAPBody fetchBody(int sequenceNumber, String section) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.startList();
command.appendAtom("BODY.PEEK");
// no part name here, only the section identifier. This will fetch
// the entire body, with all of the bits in place.
command.appendBodySection(section, null);
command.endList();
// we want all of the envelope information about the message, which involves multiple FETCH chunks.
sendCommand(command);
IMAPBody body = (IMAPBody)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODY);
if (body == null) {
throw new MessagingException("No BODY information received from IMAP server");
}
// and return the body structure directly.
return body;
}
/**
* Fetch the message content. This sorts out which method should be used
* based on the server capability.
*
* @param sequenceNumber
* The sequence number of the target message.
*
* @return The byte[] content information.
* @exception MessagingException
*/
public byte[] fetchContent(int sequenceNumber) throws MessagingException {
// fetch the text item and return the data
IMAPMessageText text = fetchText(sequenceNumber);
return text.getContent();
}
/**
* Fetch the message content. This sorts out which method should be used
* based on the server capability.
*
* @param sequenceNumber
* The sequence number of the target message.
*
* @return The byte[] content information.
* @exception MessagingException
*/
public byte[] fetchContent(int sequenceNumber, String section) throws MessagingException {
if (section == null) {
IMAPMessageText text = fetchText(sequenceNumber);
return text.getContent();
} else {
IMAPBody body = fetchBody(sequenceNumber, section);
return body.getContent();
}
}
/**
* Send an LIST command to the IMAP server, returning all LIST
* response information.
*
* @param mailbox The reference mailbox name sent on the command.
* @param pattern The match pattern used on the name.
*
* @return A List of all LIST response information sent back from the server.
*/
public synchronized List list(String mailbox, String pattern) throws MessagingException {
IMAPCommand command = new IMAPCommand("LIST");
// construct the command, encoding the tokens as required by the content.
command.appendEncodedString(mailbox);
command.appendEncodedString(pattern);
sendCommand(command);
// pull out the ones we're interested in
return extractResponses("LIST");
}
/**
* Send an LSUB command to the IMAP server, returning all LSUB
* response information.
*
* @param mailbox The reference mailbox name sent on the command.
* @param pattern The match pattern used on the name.
*
* @return A List of all LSUB response information sent back from the server.
*/
public List listSubscribed(String mailbox, String pattern) throws MessagingException {
IMAPCommand command = new IMAPCommand("LSUB");
// construct the command, encoding the tokens as required by the content.
command.appendEncodedString(mailbox);
command.appendEncodedString(pattern);
sendCommand(command);
// pull out the ones we're interested in
return extractResponses("LSUB");
}
/**
* Subscribe to a give mailbox.
*
* @param mailbox The desired mailbox name.
*
* @exception MessagingException
*/
public void subscribe(String mailbox) throws MessagingException {
IMAPCommand command = new IMAPCommand("SUBSCRIBE");
// add on the encoded mailbox name, as the appropriate token type.
command.appendEncodedString(mailbox);
// send this, and ignore the response.
sendSimpleCommand(command);
}
/**
* Unsubscribe from a mailbox.
*
* @param mailbox The mailbox to remove.
*
* @exception MessagingException
*/
public void unsubscribe(String mailbox) throws MessagingException {
IMAPCommand command = new IMAPCommand("UNSUBSCRIBE");
// add on the encoded mailbox name, as the appropriate token type.
command.appendEncodedString(mailbox);
// send this, and ignore the response.
sendSimpleCommand(command);
}
/**
* Create a mailbox.
*
* @param mailbox The desired new mailbox name (fully qualified);
*
* @exception MessagingException
*/
public void createMailbox(String mailbox) throws MessagingException {
IMAPCommand command = new IMAPCommand("CREATE");
// add on the encoded mailbox name, as the appropriate token type.
command.appendEncodedString(mailbox);
// send this, and ignore the response.
sendSimpleCommand(command);
}
/**
* Delete a mailbox.
*
* @param mailbox The target mailbox name (fully qualified);
*
* @exception MessagingException
*/
public void deleteMailbox(String mailbox) throws MessagingException {
IMAPCommand command = new IMAPCommand("DELETE");
// add on the encoded mailbox name, as the appropriate token type.
command.appendEncodedString(mailbox);
// send this, and ignore the response.
sendSimpleCommand(command);
}
/**
* Rename a mailbox.
*
* @param mailbox The target mailbox name (fully qualified);
*
* @exception MessagingException
*/
public void renameMailbox(String oldName, String newName) throws MessagingException {
IMAPCommand command = new IMAPCommand("RENAME");
// add on the encoded mailbox name, as the appropriate token type.
command.appendEncodedString(oldName);
command.appendEncodedString(newName);
// send this, and ignore the response.
sendSimpleCommand(command);
}
/**
* Retrieve a complete set of status items for a mailbox.
*
* @param mailbox The mailbox name.
*
* @return An IMAPMailboxStatus item filled in with the STATUS responses.
* @exception MessagingException
*/
public synchronized IMAPMailboxStatus getMailboxStatus(String mailbox) throws MessagingException {
IMAPCommand command = new IMAPCommand("STATUS");
// construct the command, encoding the tokens as required by the content.
command.appendEncodedString(mailbox);
// request all of the status items
command.append(" (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)");
sendCommand(command);
// now harvest each of the respon
IMAPMailboxStatus status = new IMAPMailboxStatus();
status.mergeSizeResponses(extractResponses("EXISTS"));
status.mergeSizeResponses(extractResponses("RECENT"));
status.mergeOkResponses(extractResponses("UIDNEXT"));
status.mergeOkResponses(extractResponses("UIDVALIDITY"));
status.mergeOkResponses(extractResponses("UNSEEN"));
status.mergeStatus((IMAPStatusResponse)extractResponse("STATUS"));
status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS"));
return status;
}
/**
* Select a mailbox, returning the accumulated status information
* about the mailbox returned with the response.
*
* @param mailbox The desired mailbox name.
* @param readOnly The open mode. If readOnly is true, the mailbox is opened
* using EXAMINE rather than SELECT.
*
* @return A status object containing the mailbox particulars.
* @exception MessagingException
*/
public synchronized IMAPMailboxStatus openMailbox(String mailbox, boolean readOnly) throws MessagingException {
IMAPCommand command = new IMAPCommand();
// if readOnly is required, we use EXAMINE to switch to the mailbox rather than SELECT.
// This returns the same response information, but the mailbox will not accept update operations.
if (readOnly) {
command.appendAtom("EXAMINE");
}
else {
command.appendAtom("SELECT");
}
// construct the command, encoding the tokens as required by the content.
command.appendEncodedString(mailbox);
// issue the select
IMAPTaggedResponse response = sendCommand(command);
IMAPMailboxStatus status = new IMAPMailboxStatus();
// set the mode to the requested open mode.
status.mode = readOnly ? Folder.READ_ONLY : Folder.READ_WRITE;
// the server might disagree on the mode, so check to see if
// it's telling us READ-ONLY.
if (response.hasStatus("READ-ONLY")) {
status.mode = Folder.READ_ONLY;
}
// some of these are required, some are optional.
status.mergeFlags((IMAPFlagsResponse)extractResponse("FLAGS"));
status.mergeStatus((IMAPSizeResponse)extractResponse("EXISTS"));
status.mergeStatus((IMAPSizeResponse)extractResponse("RECENT"));
status.mergeStatus((IMAPOkResponse)extractResponse("UIDVALIDITY"));
status.mergeStatus((IMAPOkResponse)extractResponse("UNSEEN"));
status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS"));
// mine the response for status information about the selected mailbox.
return status;
}
/**
* Tells the IMAP server to expunge messages marked for deletion.
* The server will send us an untagged EXPUNGE message back for
* each deleted message. For explicit expunges we request, we'll
* grabbed the untagged responses here, rather than force them to
* be handled as pending responses. The caller will handle the
* updates directly.
*
* @exception MessagingException
*/
public synchronized List expungeMailbox() throws MessagingException {
// send the message, and make sure we got an OK response
sendCommand("EXPUNGE");
// extract all of the expunged responses and return.
return extractResponses("EXPUNGED");
}
public int[] searchMailbox(SearchTerm term) throws MessagingException {
return searchMailbox("ALL", term);
}
/**
* Send a search to the IMAP server using the specified
* messages selector and search term. This figures out what
* to do with CHARSET on the SEARCH command.
*
* @param messages The list of messages (comma-separated numbers or "ALL").
* @param term The desired search criteria
*
* @return Returns an int[] array of message numbers for all matched messages.
* @exception MessagingException
*/
public int[] searchMailbox(String messages, SearchTerm term) throws MessagingException {
// don't use a charset by default, but we need to look at the data to see if we have a problem.
String charset = null;
if (IMAPCommand.checkSearchEncoding(term)) {
// not sure exactly how to decide what to use here. Two immediate possibilities come to mind,
// UTF-8 or the MimeUtility.getDefaultJavaCharset() value. Running a small test against the
// Sun impl shows them sending a CHARSET value of UTF-8, so that sounds like the winner. I don't
// believe there's anything in the CAPABILITY response that would tell us what to use.
charset = "UTF-8";
}
return searchMailbox(messages, term, charset);
}
/**
* Send a search to the IMAP server using the specified
* messages selector and search term.
*
* @param messages The list of messages (comma-separated numbers or "ALL").
* @param charset The charset specifier to send to the server. If null, then
* the CHARSET keyword is omitted.
* @param term The desired search criteria
*
* @return Returns an int[] array of message numbers for all matched messages.
* @exception MessagingException
*/
public synchronized int[] searchMailbox(String messages, SearchTerm term, String charset) throws MessagingException {
IMAPCommand command = new IMAPCommand("SEARCH");
// if we have an explicit charset to use, append that.
if (charset != null) {
command.appendAtom("CHARSET");
command.appendAtom(charset);
}
// now go through the process of translating the javamail SearchTerm objects into
// the IMAP command sequence. The SearchTerm sequence may be a complex tree of comparison terms,
// so this is not a simple process.
command.appendSearchTerm(term, charset);
// need to append the message set
command.appendAtom(messages);
// now issue the composed command.
sendCommand(command);
// get the list of search responses
IMAPSearchResponse hits = (IMAPSearchResponse)extractResponse("SEARCH");
// and return the message hits
return hits.messageNumbers;
}
/**
* Append a message to a mailbox, given the direct message data.
*
* @param mailbox The target mailbox name.
* @param messageFlags
* The initial flag set for the appended message.
* @param messageDate
* The received date the message is created with,
* @param messageData
* The RFC822 Message data stored on the server.
*
* @exception MessagingException
*/
public void appendMessage(String mailbox, Date messageDate, Flags messageFlags, byte[] messageData) throws MessagingException {
IMAPCommand command = new IMAPCommand("APPEND");
// the mailbox is encoded.
command.appendEncodedString(mailbox);
if (messageFlags != null) {
// the flags are pulled from an existing object. We can set most flag values, but the servers
// reserve RECENT for themselves. We need to force that one off.
messageFlags.remove(Flags.Flag.RECENT);
// and add the flag list to the commmand.
command.appendFlags(messageFlags);
}
if (messageDate != null) {
command.appendDate(messageDate);
}
// this gets appended as a literal.
command.appendLiteral(messageData);
// just send this as a simple command...we don't deal with the response other than to verifiy
// it was ok.
sendSimpleCommand(command);
}
/**
* Fetch the flag set for a given message sequence number.
*
* @param sequenceNumber
* The message sequence number.
*
* @return The Flags defined for this message.
* @exception MessagingException
*/
public synchronized Flags fetchFlags(int sequenceNumber) throws MessagingException {
// we want just the flag item here.
sendCommand("FETCH " + String.valueOf(sequenceNumber) + " (FLAGS)");
// get the return data item, and get the flags from within it
IMAPFlags flags = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
return flags.flags;
}
/**
* Set the flags for a range of messages.
*
* @param messageSet The set of message numbers.
* @param flags The new flag settings.
* @param set true if the flags should be set, false for a clear operation.
*
* @return A list containing all of the responses with the new flag values.
* @exception MessagingException
*/
public synchronized List setFlags(String messageSet, Flags flags, boolean set) throws MessagingException {
IMAPCommand command = new IMAPCommand("STORE");
command.appendAtom(messageSet);
// the command varies depending on whether this is a set or clear operation
if (set) {
command.appendAtom("+FLAGS");
}
else {
command.appendAtom("-FLAGS");
}
// append the flag set
command.appendFlags(flags);
// we want just the flag item here.
sendCommand(command);
// we should have a FETCH response for each of the updated messages. Return this
// response, and update the message numbers.
return extractFetchDataItems(IMAPFetchDataItem.FLAGS);
}
/**
* Set the flags for a single message.
*
* @param sequenceNumber
* The sequence number of target message.
* @param flags The new flag settings.
* @param set true if the flags should be set, false for a clear operation.
*
* @exception MessagingException
*/
public synchronized Flags setFlags(int sequenceNumber, Flags flags, boolean set) throws MessagingException {
IMAPCommand command = new IMAPCommand("STORE");
command.appendInteger(sequenceNumber);
// the command varies depending on whether this is a set or clear operation
if (set) {
command.appendAtom("+FLAGS");
}
else {
command.appendAtom("-FLAGS");
}
// append the flag set
command.appendFlags(flags);
// we want just the flag item here.
sendCommand(command);
// get the return data item, and get the flags from within it
IMAPFlags flagResponse = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
return flagResponse.flags;
}
/**
* Copy a range of messages to a target mailbox.
*
* @param messageSet The set of message numbers.
* @param target The target mailbox name.
*
* @exception MessagingException
*/
public void copyMessages(String messageSet, String target) throws MessagingException {
IMAPCommand command = new IMAPCommand("COPY");
// the auth command initiates the handshaking.
command.appendAtom(messageSet);
// the mailbox is encoded.
command.appendEncodedString(target);
// just send this as a simple command...we don't deal with the response other than to verifiy
// it was ok.
sendSimpleCommand(command);
}
/**
* Fetch the message number for a give UID.
*
* @param uid The target UID
*
* @return An IMAPUid object containing the mapping information.
*/
public synchronized IMAPUid getSequenceNumberForUid(long uid) throws MessagingException {
IMAPCommand command = new IMAPCommand("UID FETCH");
command.appendLong(uid);
command.appendAtom("(UID)");
// this situation is a little strange, so it deserves a little explanation.
// We need the message sequence number for this message from a UID value.
// we're going to send a UID FETCH command, requesting the UID value back.
// That seems strange, but the * nnnn FETCH response for the request will
// be tagged with the message sequence number. THAT'S the information we
// really want, and it will be included in the IMAPUid object.
sendCommand(command);
// ok, now we need to search through these looking for a FETCH response with a UID element.
List responses = extractResponses("FETCH");
// we're looking for a fetch response with a UID data item with the UID information
// inside of it.
for (int i = 0; i < responses.size(); i++) {
IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID);
// is this the response we're looking for? The information we
// need is the message number returned with the response, which is
// also contained in the UID item.
if (item != null && item.uid == uid) {
return item;
}
// not one meant for us, add it back to the pending queue.
queuePendingResponse(response);
}
// didn't find this one
return null;
}
/**
* Fetch the message numbers for a consequetive range
* of UIDs.
*
* @param start The start of the range.
* @param end The end of the uid range.
*
* @return A list of UID objects containing the mappings.
*/
public synchronized List getSequenceNumbersForUids(long start, long end) throws MessagingException {
IMAPCommand command = new IMAPCommand("UID FETCH");
// send the request for the range "start:end" so we can fetch all of the info
// at once.
command.appendLong(start);
command.append(":");
// not the special range marker? Just append the
// number. The LASTUID value needs to be "*" on the command.
if (end != UIDFolder.LASTUID) {
command.appendLong(end);
}
else {
command.append("*");
}
command.appendAtom("(UID)");
// this situation is a little strange, so it deserves a little explanation.
// We need the message sequence number for this message from a UID value.
// we're going to send a UID FETCH command, requesting the UID value back.
// That seems strange, but the * nnnn FETCH response for the request will
// be tagged with the message sequence number. THAT'S the information we
// really want, and it will be included in the IMAPUid object.
sendCommand(command);
// ok, now we need to search through these looking for a FETCH response with a UID element.
List responses = extractResponses("FETCH");
List uids = new ArrayList((int)(end - start + 1));
// we're looking for a fetch response with a UID data item with the UID information
// inside of it.
for (int i = 0; i < responses.size(); i++) {
IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID);
// is this the response we're looking for? The information we
// need is the message number returned with the response, which is
// also contained in the UID item.
if (item != null) {
uids.add(item);
}
else {
// not one meant for us, add it back to the pending queue.
queuePendingResponse(response);
}
}
// return the list of uids we located.
return uids;
}
/**
* Fetch the UID value for a target message number
*
* @param sequenceNumber
* The target message number.
*
* @return An IMAPUid object containing the mapping information.
*/
public synchronized IMAPUid getUidForSequenceNumber(int sequenceNumber) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendInteger(sequenceNumber);
command.appendAtom("(UID)");
// similar to the other fetches, but without the strange bit. We're starting
// with the message number in this case.
sendCommand(command);
// ok, now we need to search through these looking for a FETCH response with a UID element.
return (IMAPUid)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.UID);
}
/**
* Retrieve the user name space info from the server.
*
* @return An IMAPNamespace response item with the information. If the server
* doesn't support the namespace extension, an empty one is returned.
*/
public synchronized IMAPNamespaceResponse getNamespaces() throws MessagingException {
// if no namespace capability, then return an empty
// response, which will trigger the default behavior.
if (!hasCapability("NAMESPACE")) {
return new IMAPNamespaceResponse();
}
// no arguments on this command, so just send an hope it works.
sendCommand("NAMESPACE");
// this should be here, since it's a required response when the
// command worked. Just extract, and return.
return (IMAPNamespaceResponse)extractResponse("NAMESPACE");
}
/**
* Prefetch message information based on the request profile. We'll return
* all of the fetch information to the requesting Folder, which will sort
* out what goes where.
*
* @param messageSet The set of message numbers we need to fetch.
* @param profile The profile of the required information.
*
* @return All FETCH responses resulting from the command.
* @exception MessagingException
*/
public synchronized List fetch(String messageSet, FetchProfile profile) throws MessagingException {
IMAPCommand command = new IMAPCommand("FETCH");
command.appendAtom(messageSet);
// this is the set of items to append
command.appendFetchProfile(profile);
// now send the fetch command, which will likely send back a lot of "FETCH" responses.
// Suck all of those reponses out of the queue and send them back for processing.
sendCommand(command);
// we can have a large number of messages here, so just grab all of the fetches
// we get back, and let the Folder sort out who gets what.
return extractResponses("FETCH");
}
/**
* Set the ACL rights for a mailbox. This replaces
* any existing ACLs defined.
*
* @param mailbox The target mailbox.
* @param acl The new ACL to be used for the mailbox.
*
* @exception MessagingException
*/
public synchronized void setACLRights(String mailbox, ACL acl) throws MessagingException {
IMAPCommand command = new IMAPCommand("SETACL");
command.appendEncodedString(mailbox);
command.appendACL(acl);
sendSimpleCommand(command);
}
/**
* Add a set of ACL rights to a mailbox.
*
* @param mailbox The mailbox to alter.
* @param acl The ACL to add.
*
* @exception MessagingException
*/
public synchronized void addACLRights(String mailbox, ACL acl) throws MessagingException {
if (!hasCapability("ACL")) {
throw new MethodNotSupportedException("ACL not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("SETACL");
command.appendEncodedString(mailbox);
command.appendACL(acl, "+");
sendSimpleCommand(command);
}
/**
* Remove an ACL from a given mailbox.
*
* @param mailbox The mailbox to alter.
* @param acl The particular ACL to revoke.
*
* @exception MessagingException
*/
public synchronized void removeACLRights(String mailbox, ACL acl) throws MessagingException {
if (!hasCapability("ACL")) {
throw new MethodNotSupportedException("ACL not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("SETACL");
command.appendEncodedString(mailbox);
command.appendACL(acl, "-");
sendSimpleCommand(command);
}
/**
* Get the ACL rights assigned to a given mailbox.
*
* @param mailbox The target mailbox.
*
* @return The an array of ACL items describing the access
* rights to the mailbox.
* @exception MessagingException
*/
public synchronized ACL[] getACLRights(String mailbox) throws MessagingException {
if (!hasCapability("ACL")) {
throw new MethodNotSupportedException("ACL not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("GETACL");
command.appendEncodedString(mailbox);
// now send the GETACL command, which will return a single ACL untagged response.
sendCommand(command);
// there should be just a single ACL response back from this command.
IMAPACLResponse response = (IMAPACLResponse)extractResponse("ACL");
return response.acls;
}
/**
* Get the current user's ACL rights to a given mailbox.
*
* @param mailbox The target mailbox.
*
* @return The Rights associated with this mailbox.
* @exception MessagingException
*/
public synchronized Rights getMyRights(String mailbox) throws MessagingException {
if (!hasCapability("ACL")) {
throw new MethodNotSupportedException("ACL not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("MYRIGHTS");
command.appendEncodedString(mailbox);
// now send the MYRIGHTS command, which will return a single MYRIGHTS untagged response.
sendCommand(command);
// there should be just a single MYRIGHTS response back from this command.
IMAPMyRightsResponse response = (IMAPMyRightsResponse)extractResponse("MYRIGHTS");
return response.rights;
}
/**
* List the ACL rights that a particular user has
* to a mailbox.
*
* @param mailbox The target mailbox.
* @param name The user we're querying.
*
* @return An array of rights the use has to this mailbox.
* @exception MessagingException
*/
public synchronized Rights[] listACLRights(String mailbox, String name) throws MessagingException {
if (!hasCapability("ACL")) {
throw new MethodNotSupportedException("ACL not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("LISTRIGHTS");
command.appendEncodedString(mailbox);
command.appendString(name);
// now send the GETACL command, which will return a single ACL untagged response.
sendCommand(command);
// there should be just a single ACL response back from this command.
IMAPListRightsResponse response = (IMAPListRightsResponse)extractResponse("LISTRIGHTS");
return response.rights;
}
/**
* Delete an ACL item for a given user name from
* a target mailbox.
*
* @param mailbox The mailbox we're altering.
* @param name The user name.
*
* @exception MessagingException
*/
public synchronized void deleteACL(String mailbox, String name) throws MessagingException {
if (!hasCapability("ACL")) {
throw new MethodNotSupportedException("ACL not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("DELETEACL");
command.appendEncodedString(mailbox);
command.appendString(name);
// just send the command. No response to handle.
sendSimpleCommand(command);
}
/**
* Fetch the quota root information for a target mailbox.
*
* @param mailbox The mailbox of interest.
*
* @return An array of quotas describing all of the quota roots
* that apply to the target mailbox.
* @exception MessagingException
*/
public synchronized Quota[] fetchQuotaRoot(String mailbox) throws MessagingException {
if (!hasCapability("QUOTA")) {
throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("GETQUOTAROOT");
command.appendEncodedString(mailbox);
// This will return a single QUOTAROOT response, plust a series of QUOTA responses for
// each root names in the first response.
sendCommand(command);
// we don't really need this, but pull it from the response queue anyway.
extractResponse("QUOTAROOT");
// now get the real meat of the matter
List responses = extractResponses("QUOTA");
// now copy all of the returned quota items into the response array.
Quota[] quotas = new Quota[responses.size()];
for (int i = 0; i < quotas.length; i++) {
IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i);
quotas[i] = q.quota;
}
return quotas;
}
/**
* Fetch QUOTA information from a named QUOTE root.
*
* @param root The target root name.
*
* @return An array of Quota items associated with that root name.
* @exception MessagingException
*/
public synchronized Quota[] fetchQuota(String root) throws MessagingException {
if (!hasCapability("QUOTA")) {
throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("GETQUOTA");
command.appendString(root);
// This will return a single QUOTAROOT response, plust a series of QUOTA responses for
// each root names in the first response.
sendCommand(command);
// now get the real meat of the matter
List responses = extractResponses("QUOTA");
// now copy all of the returned quota items into the response array.
Quota[] quotas = new Quota[responses.size()];
for (int i = 0; i < quotas.length; i++) {
IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i);
quotas[i] = q.quota;
}
return quotas;
}
/**
* Set a Quota item for the currently accessed
* userid/folder resource.
*
* @param quota The new QUOTA information.
*
* @exception MessagingException
*/
public synchronized void setQuota(Quota quota) throws MessagingException {
if (!hasCapability("QUOTA")) {
throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
}
IMAPCommand command = new IMAPCommand("GETQUOTA");
// this gets appended as a list of resource values
command.appendQuota(quota);
// This will return a single QUOTAROOT response, plust a series of QUOTA responses for
// each root names in the first response.
sendCommand(command);
// we don't really need this, but pull it from the response queue anyway.
extractResponses("QUOTA");
}
/**
* Test if this connection has a given capability.
*
* @param capability The capability name.
*
* @return true if this capability is in the list, false for a mismatch.
*/
public boolean hasCapability(String capability) {
if (capabilities == null) {
return false;
}
return capabilities.containsKey(capability);
}
/**
* Tag this connection as having been closed by the
* server. This will not be returned to the
* connection pool.
*/
public void setClosed() {
closed = true;
}
/**
* Test if the connnection has been forcibly closed.
*
* @return True if the server disconnected the connection.
*/
public boolean isClosed() {
return closed;
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternetHeader.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000004226 10716317503 032272 0 ustar brian brian /**
* 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.ByteArrayInputStream;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
public class IMAPInternetHeader extends IMAPFetchBodyPart {
// the parsed headers
public InternetHeaders headers;
/**
* Construct a top-level HEADER data item.
*
* @param data The data for the InternetHeaders.
*
* @exception MessagingException
*/
public IMAPInternetHeader(byte[] data) throws MessagingException {
this(new IMAPBodySection(IMAPBodySection.HEADERS), data);
}
/**
* Construct a HEADER request data item.
*
* @param section The Section identifier information.
* @param data The raw data for the internet headers.
*
* @exception MessagingException
*/
public IMAPInternetHeader(IMAPBodySection section, byte[] data) throws MessagingException {
super(HEADER, section);
// and convert these into real headers
ByteArrayInputStream in = new ByteArrayInputStream(data);
headers = new InternetHeaders(in);
}
/**
* Test if this is a complete header fetch, or just a partial list fetch.
*
* @return
*/
public boolean isComplete() {
return section.section == IMAPBodySection.HEADERS;
}
}
././@LongLink 0000000 0000000 0000000 00000000201 00000000000 011556 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespaceResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000007475 10716317503 032303 0 ustar brian brian /**
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.mail.MessagingException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
import org.apache.geronimo.javamail.util.ResponseFormatException;
/**
* Util class to represent a NAMESPACE response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPNamespaceResponse extends IMAPUntaggedResponse {
// the personal namespaces defined
public List personalNamespaces;
// the other use name spaces this user has access to.
public List otherUserNamespaces;
// the list of shared namespaces
public List sharedNamespaces;
// construct a default IMAPNamespace response for return when the server doesn't support this.
public IMAPNamespaceResponse()
{
super("NAMESPACE", null);
// fill in default lists to simplify processing
personalNamespaces = Collections.EMPTY_LIST;
otherUserNamespaces = Collections.EMPTY_LIST;
sharedNamespaces = Collections.EMPTY_LIST;
}
/**
* Construct a LIST response item. This can be either
* a response from a LIST command or an LSUB command,
* and will be tagged accordingly.
*
* @param type The type of resonse (LIST or LSUB).
* @param data The raw response data.
* @param source The tokenizer source.
*
* @exception MessagingException
*/
public IMAPNamespaceResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("NAMESPACE", data);
// the namespace response is a set of 3 items, which will be either NIL or a "list of lists".
// if the item exists, then there will be a set of list parens, with 1 or more subitems inside.
// Each of the subitems will consist of a namespace prefix and the hierarchy delimiter for that
// particular namespace.
personalNamespaces = parseNamespace(source);
otherUserNamespaces = parseNamespace(source);
sharedNamespaces = parseNamespace(source);
}
private List parseNamespace(IMAPResponseTokenizer source) throws MessagingException {
Token token = source.next(true);
// is this token the NIL token?
if (token.getType() == Token.NIL) {
// no items at this position.
return null;
}
if (token.getType() != '(') {
throw new ResponseFormatException("Missing '(' in response");
}
// ok, we're processing a namespace list. Create a list and populate it with IMAPNamespace
// items.
List namespaces = new ArrayList();
while (source.notListEnd()) {
namespaces.add(new IMAPNamespace(source));
}
// this should always pass, since it terminated the loop
source.checkRightParen();
return namespaces;
}
}
././@LongLink 0000000 0000000 0000000 00000000175 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchBodyPart.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000005137 10716317503 032274 0 ustar brian brian /**
* 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;
public class IMAPFetchBodyPart extends IMAPFetchDataItem {
// the parse body section information.
protected IMAPBodySection section;
/**
* Construct a base BODY section subpiece.
*
* @param type The fundamental type of the body section. This will be either BODY, TEXT,
* or HEADER, depending on the subclass.
* @param section The section information. This will contain the section numbering information,
* the section name, and and substring information if this was a partial fetch
* request.
*/
public IMAPFetchBodyPart(int type, IMAPBodySection section) {
super(type);
this.section = section;
}
/**
* Get the part number information associated with this request.
*
* @return The string form of the part number.
*/
public String getPartNumber() {
return section.partNumber;
}
/**
* Get the section type information. This is the qualifier that appears
* within the "[]" of the body sections.
*
* @return The numeric identifier for the type from the IMAPBodySection.
*/
public int getSectionType() {
return section.section;
}
/**
* Get the substring start location.
*
* @return The start location for the substring. Returns -1 if this is not a partial
* fetch.
*/
public int getSubstringStart() {
return section.start;
}
/**
* Returns the length of the substring section.
*
* @return The length of the substring section. Returns -1 if this was not a partial
* fetch.
*/
public int getSubstringLength() {
return section.length;
}
}
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000006207 10716317503 032273 0 ustar brian brian /**
* 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.util.List;
import javax.mail.MessagingException;
/**
* Util class to represent a list response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPListResponse extends IMAPUntaggedResponse {
// parsed flag responses
public boolean noinferiors = false;
public boolean noselect = false;
public boolean marked = false;
public boolean unmarked = false;
// the name separator character
public char separator;
// the mail box name
public String mailboxName;
// this is for support of the get attributes command
public String[] attributes;
/**
* Construct a LIST response item. This can be either
* a response from a LIST command or an LSUB command,
* and will be tagged accordingly.
*
* @param type The type of resonse (LIST or LSUB).
* @param data The raw response data.
* @param source The tokenizer source.
*
* @exception MessagingException
*/
public IMAPListResponse(String type, byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super(type, data);
// parse the list of flag values
List flags = source.readSystemNameList();
// copy this into the attributes array.
attributes = new String[flags.size()];
attributes = (String[])flags.toArray(attributes);
for (int i = 0; i < flags.size(); i++) {
String flag = ((String)flags.get(i));
if (flag.equalsIgnoreCase("\\Marked")) {
marked = true;
}
else if (flag.equalsIgnoreCase("\\Unmarked")) {
unmarked = true;
}
else if (flag.equalsIgnoreCase("\\Noselect")) {
noselect = true;
}
else if (flag.equalsIgnoreCase("\\Noinferiors")) {
noinferiors = true;
}
}
// set a default sep value
separator = '\0';
// get the separator and name tokens
String separatorString = source.readQuotedStringOrNil();
if (separatorString != null && separatorString.length() == 1) {
separator = separatorString.charAt(0);
}
mailboxName = source.readEncodedString();
}
}
././@LongLink 0000000 0000000 0000000 00000000206 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPPermanentFlagsResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003203 10716317503 032264 0 ustar brian brian /**
* 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 javax.mail.Flags;
import javax.mail.MessagingException;
/**
* Util class to represent an untagged response from a IMAP server
*
* @version $Rev: 594520 $ $Date: 2007-11-13 07:57:39 -0500 (Tue, 13 Nov 2007) $
*/
public class IMAPPermanentFlagsResponse extends IMAPUntaggedResponse {
// the response flags value
public Flags flags;
/**
* Create a reply object from a server response line (normally, untagged). This includes
* doing the parsing of the response line.
*
* @param response The response line used to create the reply object.
*/
public IMAPPermanentFlagsResponse(byte [] response, IMAPResponseTokenizer source) throws MessagingException {
super("PERMANENTFLAGS", response);
flags = source.readFlagList();
}
}
././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000141526 11375023623 032276 0 ustar brian brian /**
* 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) {
try {
// work off the byte values
appendString(value.getBytes("ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
/**
* 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) {
try {
// work off the byte values
appendQuotedString(value.getBytes("ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
/**
* 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);
try {
// work off the byte values
appendString(value.getBytes("ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
/**
* 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) {
try {
// work off the byte values
appendString(value.getBytes("ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
else {
try {
// use the charset to extract the bytes
appendString(value.getBytes(charset));
throw new MessagingException("Invalid text encoding");
} catch (UnsupportedEncodingException e) {
}
}
}
/**
* 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) {
try {
appendAtom(value.getBytes("ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
/**
* 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]".
*
* @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
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);
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000010447 11375023623 032273 0 ustar brian brian /**
* 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.UnsupportedEncodingException;
/**
* Simple extension to the ByteArrayOutputStream to allow inspection
* of the data while it is being accumulated.
*/
public class IMAPResponseBuffer extends ByteArrayOutputStream {
public IMAPResponseBuffer() {
super();
}
/**
* Read a character from the byte array output stream buffer
* at the give position.
*
* @param index The requested index.
*
* @return The byte at the target index, or -1 if the index is out of
* bounds.
*/
public int read(int index) {
if (index >= size()) {
return -1;
}
return buf[index];
}
/**
* Read a buffer of data from the output stream's accumulator
* buffer. This will copy the data into a target byte arrain.
*
* @param buffer The target byte array for returning the data.
* @param offset The offset of the source data within the output stream buffer.
* @param length The desired length.
*
* @return The count of bytes transferred into the buffer.
*/
public int read(byte[] buffer, int offset, int length) {
int available = size() - offset;
length = Math.min(length, available);
// nothing to return? quit now.
if (length <= 0) {
return 0;
}
System.arraycopy(buf, offset, buffer, 0, length);
return length;
}
/**
* Search backwards through the buffer for a given byte.
*
* @param target The search character.
*
* @return The index relative to the buffer start of the given byte.
* Returns -1 if not found.
*/
public int lastIndex(byte target) {
for (int i = size() - 1; i > 0; i--) {
if (buf[i] == target) {
return i;
}
}
return -1;
}
/**
* Return the last byte written to the output stream. Returns
* -1 if the stream is empty.
*
* @return The last byte written (or -1 if the stream is empty).
*/
public int lastByte() {
if (size() > 0) {
return buf[size() - 1];
}
return -1;
}
/**
* Retrieve an IMAP literal length value from the buffer. We
* have a literal length value IFF the last characters written
* to the buffer have the form "{nnnn}". This returns the
* integer value of the info inside the curly braces. Returns -1
* if a valid literal length is not found.
*
* @return A literal length value, or -1 if we don't have a literal
* signature at the end.
*/
public int getLiteralLength() {
// was the last byte before the line break the close of the literal length?
if (lastByte() == '}') {
// locate the length start
int literalStart = lastIndex((byte)'{');
// no matching start, this can't be a literal.
if (literalStart == -1) {
return -1;
}
try {
String lenString = new String(buf, literalStart + 1, size() - (literalStart + 2), "US-ASCII");
try {
return Integer.parseInt(lenString);
} catch (NumberFormatException e) {
}
} catch (UnsupportedEncodingException ex) {
// should never happen
}
}
// not a literal
return -1;
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000005425 11156270711 032272 0 ustar brian brian /**
* 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.util.List;
import javax.mail.MessagingException;
/**
* Utility class to aggregate status responses for a mailbox.
*/
public class IMAPStatusResponse extends IMAPUntaggedResponse {
// the mail box name
public String mailbox;
// number of messages in the box
public int messages = -1;
// number of recent messages
public int recentMessages = -1;
// the number of unseen messages
public int unseenMessages = -1;
// the next UID for this mailbox
public long uidNext = -1L;
// the UID validity item
public long uidValidity = -1L;
public IMAPStatusResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
super("STATUS", data);
// the mail box name is supposed to be encoded, so decode it now.
mailbox = source.readEncodedString();
// parse the list of flag values
List flags = source.readStringList();
if (flags == null) {
return;
}
for (int i = 0; i < flags.size(); i += 2) {
String field = ((String)flags.get(i)).toUpperCase();
String stringValue = ((String)flags.get(i + 1));
long value;
try {
value = Long.parseLong(stringValue);
} catch (NumberFormatException e) {
throw new MessagingException("Invalid IMAP Status response", e);
}
if (field.equals("MESSAGES")) {
messages = (int)value;
}
else if (field.equals("RECENT")) {
recentMessages = (int)value;
}
else if (field.equals("UIDNEXT")) {
uidNext = value;
}
else if (field.equals("UIDVALIDITY")) {
uidValidity = value;
}
else if (field.equals("UNSEEN")) {
unseenMessages = (int)value;
}
}
}
}
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMultipartDataSource.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMulti0000664 0001750 0001750 00000004312 10716317503 032046 0 ustar brian brian /**
* 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;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.MultipartDataSource;
import javax.mail.internet.MimePart;
import javax.mail.internet.MimePartDataSource;
import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
public class IMAPMultipartDataSource extends MimePartDataSource implements MultipartDataSource {
// the list of parts
protected BodyPart[] parts;
IMAPMultipartDataSource(IMAPMessage message, MimePart parent, String section, IMAPBodyStructure bodyStructure) {
super(parent);
parts = new BodyPart[bodyStructure.parts.length];
// We're either created from the parent message, in which case we're the top level
// of the hierarchy, or we're created from a nested message, so we need to apply the
// parent numbering prefix.
String sectionBase = section == null ? "" : section + ".";
for (int i = 0; i < parts.length; i++) {
// create a section id. This is either the count (origin zero) or a subpart of the previous section.
parts[i] = new IMAPMimeBodyPart(message, (IMAPBodyStructure)bodyStructure.parts[i], sectionBase + (i + 1));
}
}
public int getCount() {
return parts.length;
}
public BodyPart getBodyPart(int index) throws MessagingException {
return parts[index];
}
}
././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNamespaceFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNames0000664 0001750 0001750 00000003437 10716317503 032026 0 ustar brian brian /**
* 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;
import org.apache.geronimo.javamail.store.imap.connection.IMAPNamespace;
/**
* An override of the base IMAPFolder class for folders representing namespace roots.
* @see javax.mail.Folder
*
* @version $Rev: 594520 $
*/
public class IMAPNamespaceFolder extends IMAPFolder {
IMAPNamespaceFolder(IMAPStore store, IMAPNamespace namespace) {
// initialize with the namespace information
super(store, namespace.prefix, namespace.separator);
}
/**
* Override of the default IMAPFolder method to provide the mailbox name
* as the prefix + delimiter.
*
* @return The string name to use as the mailbox name for exists() and issubscribed()
* calls.
*/
protected String getMailBoxName() {
// no delimiter is a possibility, so
// we need to check.
if (separator == '\0') {
return fullname;
}
return fullname + separator;
}
}
././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore0000664 0001750 0001750 00000055703 11077607735 032074 0 ustar brian brian /**
* 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;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.mail.AuthenticationFailedException;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Quota;
import javax.mail.QuotaAwareStore;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.event.StoreEvent;
import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
import org.apache.geronimo.javamail.store.imap.connection.IMAPConnectionPool;
import org.apache.geronimo.javamail.store.imap.connection.IMAPOkResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPNamespaceResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPNamespace;
import org.apache.geronimo.javamail.store.imap.connection.IMAPServerStatusResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler;
import org.apache.geronimo.javamail.util.ProtocolProperties;
/**
* IMAP implementation of javax.mail.Store
* POP protocol spec is implemented in
* org.apache.geronimo.javamail.store.pop3.IMAPConnection
*
* @version $Rev: 707037 $ $Date: 2008-10-22 07:34:53 -0400 (Wed, 22 Oct 2008) $
*/
public class IMAPStore extends Store implements QuotaAwareStore, IMAPUntaggedResponseHandler {
// the default connection ports for secure and non-secure variations
protected static final int DEFAULT_IMAP_PORT = 143;
protected static final int DEFAULT_IMAP_SSL_PORT = 993;
protected static final String MAIL_STATUS_TIMEOUT = "statuscacheimeout";
protected static final int DEFAULT_STATUS_TIMEOUT = 1000;
// our accessor for protocol properties and the holder of
// protocol-specific information
protected ProtocolProperties props;
// the connection pool we use for access
protected IMAPConnectionPool connectionPool;
// the root folder
protected IMAPRootFolder root;
// the list of open folders (which also represents an open connection).
protected List openFolders = new LinkedList();
// our session provided debug output stream.
protected PrintStream debugStream;
// the debug flag
protected boolean debug;
// until we're connected, we're closed
boolean closedForBusiness = true;
// The timeout value for our status cache
long statusCacheTimeout = 0;
/**
* Construct an IMAPStore item.
*
* @param session The owning javamail Session.
* @param urlName The Store urlName, which can contain server target information.
*/
public IMAPStore(Session session, URLName urlName) {
// we're the imap protocol, our default connection port is 119, and don't use
// an SSL connection for the initial hookup
this(session, urlName, "imap", false, DEFAULT_IMAP_PORT);
}
/**
* Protected common constructor used by both the IMAPStore and the IMAPSSLStore
* to initialize the Store instance.
*
* @param session The Session we're attached to.
* @param urlName The urlName.
* @param protocol The protocol name.
* @param sslConnection
* The sslConnection flag.
* @param defaultPort
* The default connection port.
*/
protected IMAPStore(Session session, URLName urlName, String protocol, boolean sslConnection, int defaultPort) {
super(session, urlName);
// create the protocol property holder. This gives an abstraction over the different
// flavors of the protocol.
props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
// get the status timeout value for the folders.
statusCacheTimeout = props.getIntProperty(MAIL_STATUS_TIMEOUT, DEFAULT_STATUS_TIMEOUT);
// get our debug settings
debugStream = session.getDebugOut();
debug = session.getDebug();
// create a connection pool we can retrieve connections from
connectionPool = new IMAPConnectionPool(this, props);
}
/**
* Attempt the protocol-specific connection; subclasses should override this to establish
* a connection in the appropriate manner.
*
* This method should return true if the connection was established.
* It may return false to cause the {@link #connect(String, int, String, String)} method to
* reattempt the connection after trying to obtain user and password information from the user.
* Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt.
*
* @param host The target host name of the service.
* @param port The connection port for the service.
* @param user The user name used for the connection.
* @param password The password used for the connection.
*
* @return true if a connection was established, false if there was authentication
* error with the connection.
* @throws AuthenticationFailedException
* if authentication fails
* @throws MessagingException
* for other failures
*/
protected synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
if (debug) {
debugOut("Connecting to server " + host + ":" + port + " for user " + username);
}
// the connection pool handles all of the details here.
if (connectionPool.protocolConnect(host, port, username, password))
{
// the store is now open
closedForBusiness = false;
return true;
}
return false;
}
/**
* Close this service and terminate its physical connection.
* The default implementation simply calls setConnected(false) and then
* sends a CLOSED event to all registered ConnectionListeners.
* Subclasses overriding this method should still ensure it is closed; they should
* also ensure that it is called if the connection is closed automatically, for
* for example in a finalizer.
*
*@throws MessagingException if there were errors closing; the connection is still closed
*/
public synchronized void close() throws MessagingException{
// if already closed, nothing to do.
if (closedForBusiness) {
return;
}
// close the folders first, then shut down the Store.
closeOpenFolders();
connectionPool.close();
connectionPool = null;
// make sure we do the superclass close operation first so
// notification events get broadcast properly.
super.close();
}
/**
* Return a Folder object that represents the root of the namespace for the current user.
*
* Note that in some store configurations (such as IMAP4) the root folder might
* not be the INBOX folder.
*
* @return the root Folder
* @throws MessagingException if there was a problem accessing the store
*/
public Folder getDefaultFolder() throws MessagingException {
checkConnectionStatus();
// if no root yet, create a root folder instance.
if (root == null) {
return new IMAPRootFolder(this);
}
return root;
}
/**
* Return the Folder corresponding to the given name.
* The folder might not physically exist; the {@link Folder#exists()} method can be used
* to determine if it is real.
*
* @param name the name of the Folder to return
*
* @return the corresponding folder
* @throws MessagingException
* if there was a problem accessing the store
*/
public Folder getFolder(String name) throws MessagingException {
return getDefaultFolder().getFolder(name);
}
/**
* Return the folder identified by the URLName; the URLName must refer to this Store.
* Implementations may use the {@link URLName#getFile()} method to determined the folder name.
*
* @param url
*
* @return the corresponding folder
* @throws MessagingException
* if there was a problem accessing the store
*/
public Folder getFolder(URLName url) throws MessagingException {
return getDefaultFolder().getFolder(url.getFile());
}
/**
* Return the root folders of the personal namespace belonging to the current user.
*
* The default implementation simply returns an array containing the folder returned by {@link #getDefaultFolder()}.
* @return the root folders of the user's peronal namespaces
* @throws MessagingException if there was a problem accessing the store
*/
public Folder[] getPersonalNamespaces() throws MessagingException {
IMAPNamespaceResponse namespaces = getNamespaces();
// if nothing is returned, then use the API-defined default for this
if (namespaces.personalNamespaces.size() == 0) {
return super.getPersonalNamespaces();
}
// convert the list into an array of Folders.
return getNamespaceFolders(namespaces.personalNamespaces);
}
/**
* Return the root folders of the personal namespaces belonging to the supplied user.
*
* The default implementation simply returns an empty array.
*
* @param user the user whose namespaces should be returned
* @return the root folders of the given user's peronal namespaces
* @throws MessagingException if there was a problem accessing the store
*/
public Folder[] getUserNamespaces(String user) throws MessagingException {
IMAPNamespaceResponse namespaces = getNamespaces();
// if nothing is returned, then use the API-defined default for this
if (namespaces.otherUserNamespaces == null || namespaces.otherUserNamespaces.isEmpty()) {
return super.getUserNamespaces(user);
}
// convert the list into an array of Folders.
return getNamespaceFolders(namespaces.otherUserNamespaces);
}
/**
* Return the root folders of namespaces that are intended to be shared between users.
*
* The default implementation simply returns an empty array.
* @return the root folders of all shared namespaces
* @throws MessagingException if there was a problem accessing the store
*/
public Folder[] getSharedNamespaces() throws MessagingException {
IMAPNamespaceResponse namespaces = getNamespaces();
// if nothing is returned, then use the API-defined default for this
if (namespaces.sharedNamespaces == null || namespaces.sharedNamespaces.isEmpty()) {
return super.getSharedNamespaces();
}
// convert the list into an array of Folders.
return getNamespaceFolders(namespaces.sharedNamespaces);
}
/**
* Get the quotas for the specified root element.
*
* @param root The root name for the quota information.
*
* @return An array of Quota objects defined for the root.
* @throws MessagingException if the quotas cannot be retrieved
*/
public Quota[] getQuota(String root) throws javax.mail.MessagingException {
// get our private connection for access
IMAPConnection connection = getStoreConnection();
try {
// request the namespace information from the server
return connection.fetchQuota(root);
} finally {
releaseStoreConnection(connection);
}
}
/**
* Set a quota item. The root contained in the Quota item identifies
* the quota target.
*
* @param quota The source quota item.
* @throws MessagingException if the quota cannot be set
*/
public void setQuota(Quota quota) throws javax.mail.MessagingException {
// get our private connection for access
IMAPConnection connection = getStoreConnection();
try {
// request the namespace information from the server
connection.setQuota(quota);
} finally {
releaseStoreConnection(connection);
}
}
/**
* Verify that the server is in a connected state before
* performing operations that required that status.
*
* @exception MessagingException
*/
private void checkConnectionStatus() throws MessagingException {
// we just check the connection status with the superclass. This
// tells us we've gotten a connection. We don't want to do the
// complete connection checks that require pinging the server.
if (!super.isConnected()){
throw new MessagingException("Not connected ");
}
}
/**
* Test to see if we're still connected. This will ping the server
* to see if we're still alive.
*
* @return true if we have a live, active culture, false otherwise.
*/
public synchronized boolean isConnected() {
// check if we're in a presumed connected state. If not, we don't really have a connection
// to check on.
if (!super.isConnected()) {
return false;
}
try {
IMAPConnection connection = getStoreConnection();
try {
// check with the connecition to see if it's still alive.
// we use a zero timeout value to force it to check.
return connection.isAlive(0);
} finally {
releaseStoreConnection(connection);
}
} catch (MessagingException e) {
return false;
}
}
/**
* Internal debug output routine.
*
* @param value The string value to output.
*/
void debugOut(String message) {
debugStream.println("IMAPStore DEBUG: " + message);
}
/**
* Internal debugging routine for reporting exceptions.
*
* @param message A message associated with the exception context.
* @param e The received exception.
*/
void debugOut(String message, Throwable e) {
debugOut("Received exception -> " + message);
debugOut("Exception message -> " + e.getMessage());
e.printStackTrace(debugStream);
}
/**
* Retrieve the server connection created by this store.
*
* @return The active connection object.
*/
protected IMAPConnection getStoreConnection() throws MessagingException {
return connectionPool.getStoreConnection();
}
protected void releaseStoreConnection(IMAPConnection connection) throws MessagingException {
// This is a bit of a pain. We need to delay processing of the
// unsolicited responses until after each user of the connection has
// finished processing the expected responses. We need to do this because
// the unsolicited responses may include EXPUNGED messages. The EXPUNGED
// messages will alter the message sequence numbers for the messages in the
// cache. Processing the EXPUNGED messages too early will result in
// updates getting applied to the wrong message instances. So, as a result,
// we delay that stage of the processing until all expected responses have
// been handled.
// process any pending messages before returning.
connection.processPendingResponses();
// return this to the connectin pool
connectionPool.releaseStoreConnection(connection);
}
synchronized IMAPConnection getFolderConnection(IMAPFolder folder) throws MessagingException {
IMAPConnection connection = connectionPool.getFolderConnection();
openFolders.add(folder);
return connection;
}
synchronized void releaseFolderConnection(IMAPFolder folder, IMAPConnection connection) throws MessagingException {
openFolders.remove(folder);
// return this to the connectin pool
// NB: It is assumed that the Folder has already triggered handling of
// unsolicited responses on this connection before returning it.
connectionPool.releaseFolderConnection(connection);
}
/**
* Retrieve the Session object this Store is operating under.
*
* @return The attached Session instance.
*/
Session getSession() {
return session;
}
/**
* Close all open folders. We have a small problem here with a race condition. There's no safe, single
* synchronization point for us to block creation of new folders while we're closing. So we make a copy of
* the folders list, close all of those folders, and keep repeating until we're done.
*/
protected void closeOpenFolders() {
// we're no longer accepting additional opens. Any folders that open after this point will get an
// exception trying to get a connection.
closedForBusiness = true;
while (true) {
List folders = null;
// grab our lock, copy the open folders reference, and null this out. Once we see a null
// open folders ref, we're done closing.
synchronized(connectionPool) {
folders = openFolders;
openFolders = new LinkedList();
}
// null folder, we're done
if (folders.isEmpty()) {
return;
}
// now close each of the open folders.
for (int i = 0; i < folders.size(); i++) {
IMAPFolder folder = (IMAPFolder)folders.get(i);
try {
folder.close(false);
} catch (MessagingException e) {
}
}
}
}
/**
* Get the namespace information from the IMAP server.
*
* @return An IMAPNamespaceResponse with the namespace information.
* @exception MessagingException
*/
protected IMAPNamespaceResponse getNamespaces() throws MessagingException {
// get our private connection for access
IMAPConnection connection = getStoreConnection();
try {
// request the namespace information from the server
return connection.getNamespaces();
} finally {
releaseStoreConnection(connection);
}
}
/**
* Convert a List of IMAPNamespace definitions into an array of Folder
* instances.
*
* @param namespaces The namespace List
*
* @return An array of the same size as the namespace list containing a Folder
* instance for each defined namespace.
* @exception MessagingException
*/
protected Folder[] getNamespaceFolders(List namespaces) throws MessagingException {
Folder[] folders = new Folder[namespaces.size()];
// convert each of these to a Folder instance.
for (int i = 0; i < namespaces.size(); i++) {
IMAPNamespace namespace = (IMAPNamespace)namespaces.get(i);
folders[i] = new IMAPNamespaceFolder(this, namespace);
}
return folders;
}
/**
* Test if this connection has a given capability.
*
* @param capability The capability name.
*
* @return true if this capability is in the list, false for a mismatch.
*/
public boolean hasCapability(String capability) {
return connectionPool.hasCapability(capability);
}
/**
* Handle an unsolicited response from the server. Most unsolicited responses
* are replies to specific commands sent to the server. The remainder must
* be handled by the Store or the Folder using the connection. These are
* critical to handle, as events such as expunged messages will alter the
* sequence numbers of the live messages. We need to keep things in sync.
*
* @param response The UntaggedResponse to process.
*
* @return true if we handled this response and no further handling is required. false
* means this one wasn't one of ours.
*/
public boolean handleResponse(IMAPUntaggedResponse response) {
// Some sort of ALERT response from the server?
// we need to broadcast this to any of the listeners
if (response.isKeyword("ALERT")) {
notifyStoreListeners(StoreEvent.ALERT, ((IMAPOkResponse)response).getMessage());
return true;
}
// potentially some sort of unsolicited OK notice. This is also an event.
else if (response.isKeyword("OK")) {
String message = ((IMAPOkResponse)response).getMessage();
if (message.length() > 0) {
notifyStoreListeners(StoreEvent.NOTICE, message);
}
return true;
}
// potentially some sort of unsolicited notice. This is also an event.
else if (response.isKeyword("BAD") || response.isKeyword("NO")) {
String message = ((IMAPServerStatusResponse)response).getMessage();
if (message.length() > 0) {
notifyStoreListeners(StoreEvent.NOTICE, message);
}
return true;
}
// this is a BYE response on our connection. Folders should be handling the
// BYE events on their connections, so we should only be seeing this if
// it's on the store connection.
else if (response.isKeyword("BYE")) {
// this is essentially a close event. We need to clean everything up
try {
close();
} catch (MessagingException e) {
}
return true;
}
return false;
}
/**
* Finalizer to perform IMAPStore() cleanup when
* no longer in use.
*
* @exception Throwable
*/
protected void finalize() throws Throwable {
super.finalize();
close();
}
/**
* Retrieve the protocol properties for the Store.
*
* @return The protocol properties bundle.
*/
ProtocolProperties getProperties() {
return props;
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootF0000664 0001750 0001750 00000010530 10716317503 032004 0 ustar brian brian /**
* 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;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Store;
import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
/**
* An IMAP folder instance for the root of IMAP folder tree. This has
* some of the folder operations disabled.
*/
public class IMAPRootFolder extends IMAPFolder {
/**
* Create a default IMAPRootFolder attached to a specific Store instance.
*
* @param store The Store instance this is the root for.
*/
public IMAPRootFolder(IMAPStore store) {
// create a folder with a null string name and the default separator.
super(store, "", '/');
// this only holds folders
folderType = HOLDS_FOLDERS;
}
/**
* Get the Folder determined by the supplied name; if the name is relative
* then it is interpreted relative to this folder. This does not check that
* the named folder actually exists.
*
* @param name the name of the folder to return
* @return the named folder
* @throws MessagingException if there was a problem accessing the store
*/
public Folder getFolder(String name) throws MessagingException {
// The root folder is a dummy one. Any getFolder() request starting
// at the root will use the request name for the full name. The separator
// used in that folder's namespace will be determined when the folder is
// first opened.
return new IMAPFolder((IMAPStore)store, name, UNDETERMINED);
}
public Folder getParent() {
// we never have a parent folder
return null;
}
public boolean exists() throws MessagingException {
// this always exists
return true;
}
public boolean hasNewMessages() {
// we don't really exist, so the answer is always false.
return false;
}
public int getMessagesCount() {
// we don't really exist, so the answer is always 0;
return 0;
}
public int getNewMessagesCount() {
// we don't really exist, so the answer is always 0;
return 0;
}
public int getUnreadMessagesCount() {
// we don't really exist, so the answer is always 0;
return 0;
}
public int getDeletedMessagesCount() {
// we don't really exist, so the answer is always 0;
return 0;
}
public boolean create(int newType) throws MessagingException {
throw new MethodNotSupportedException("Default IMAP folder cannot be created");
}
public boolean delete(boolean recurse) throws MessagingException {
throw new MethodNotSupportedException("Default IMAP folder cannot be deleted");
}
public boolean rename(boolean recurse) throws MessagingException {
throw new MethodNotSupportedException("Default IMAP folder cannot be renamed");
}
public void appendMessages(Message[] msgs) throws MessagingException {
throw new MethodNotSupportedException("Messages cannot be appended to Default IMAP folder");
}
public Message[] expunge() throws MessagingException {
throw new MethodNotSupportedException("Messages cannot be expunged from Default IMAP folder");
}
}
././@LongLink 0000000 0000000 0000000 00000000154 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessa0000664 0001750 0001750 00000136310 11375023623 032027 0 ustar brian brian /**
* 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;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Header;
import javax.mail.IllegalWriteException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MessageRemovedException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.UIDFolder;
import javax.mail.event.MessageChangedEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MailDateFormat;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import org.apache.geronimo.javamail.store.imap.connection.IMAPBody;
import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPInternalDate;
import org.apache.geronimo.javamail.store.imap.connection.IMAPInternetHeader;
import org.apache.geronimo.javamail.store.imap.connection.IMAPMessageSize;
import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
import org.apache.geronimo.javamail.util.MailConnection;
/**
* IMAP implementation of javax.mail.internet.MimeMessage
*
* Only the most basic information is given and
* Message objects created here is a light-weight reference to the actual Message
* As per the JavaMail spec items from the actual message will get filled up on demand
*
* If some other items are obtained from the server as a result of one call, then the other
* details are also processed and filled in. For ex if RETR is called then header information
* will also be processed in addition to the content
*
* @version $Rev: 946314 $ $Date: 2010-05-19 14:01:55 -0400 (Wed, 19 May 2010) $
*/
public class IMAPMessage extends MimeMessage {
private static final byte[] CRLF = new byte[]{'\r', '\n'};
// the Store we're stored in (which manages the connection and other stuff).
protected IMAPStore store;
// the IMAP server sequence number (potentially updated during the life of this message object).
protected int sequenceNumber;
// the IMAP uid value;
protected long uid = -1;
// the section identifier. This is only really used for nested messages. The toplevel version
// will be null, and each nested message will set the appropriate part identifier
protected String section;
// the loaded message envelope (delayed until needed)
protected IMAPEnvelope envelope;
// the body structure information (also lazy loaded).
protected IMAPBodyStructure bodyStructure;
// the IMAP INTERNALDATE value.
protected Date receivedDate;
// the size item, which is maintained separately from the body structure
// as it can be retrieved without getting the body structure
protected int size;
// turned on once we've requested the entire header set.
protected boolean allHeadersRetrieved = false;
// singleton date formatter for this class.
static protected MailDateFormat dateFormat = new MailDateFormat();
/**
* Contruct an IMAPMessage instance.
*
* @param folder The hosting folder for the message.
* @param store The Store owning the article (and folder).
* @param msgnum The article message number. This is assigned by the Folder, and is unique
* for each message in the folder. The message numbers are only valid
* as long as the Folder is open.
* @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to
* change whenever messages are expunged. This is the server retrieval number
* of the message, which needs to be synchronized with status updates
* sent from the server.
*
* @exception MessagingException
*/
IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) {
super(folder, msgnum);
this.sequenceNumber = sequenceNumber;
this.store = store;
// The default constructor creates an empty Flags item. We need to clear this out so we
// know if the flags need to be fetched from the server when requested.
flags = null;
// make sure this is a totally fresh set of headers. We'll fill things in as we retrieve them.
headers = new InternetHeaders();
}
/**
* Override for the Message class setExpunged() method to allow
* us to do additional cleanup for expunged messages.
*
* @param value The new expunge setting.
*/
public void setExpunged(boolean value) {
// super class handles most of the details
super.setExpunged(value);
// if we're now expunged, this removes us from the server message sequencing scheme, so
// we need to invalidate the sequence number.
if (isExpunged()) {
sequenceNumber = -1;
}
}
/**
* Return a copy the flags associated with this message.
*
* @return a copy of the flags for this message
* @throws MessagingException if there was a problem accessing the Store
*/
public synchronized Flags getFlags() throws MessagingException {
// load the flags, if needed
loadFlags();
return super.getFlags();
}
/**
* Check whether the supplied flag is set.
* The default implementation checks the flags returned by {@link #getFlags()}.
*
* @param flag the flags to check for
* @return true if the flags is set
* @throws MessagingException if there was a problem accessing the Store
*/
public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
// load the flags, if needed
loadFlags();
return super.isSet(flag);
}
/**
* Set or clear a flag value.
*
* @param flags The set of flags to effect.
* @param set The value to set the flag to (true or false).
*
* @exception MessagingException
*/
public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
// make sure this is in a valid state.
checkValidity();
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
synchronized (folder) {
IMAPConnection connection = getConnection();
try {
// set the flags for this item and update the
// internal state with the new values returned from the
// server.
flags = connection.setFlags(sequenceNumber, flag, set);
} finally {
releaseConnection(connection);
}
}
}
/**
* Return an InputStream instance for accessing the
* message content.
*
* @return An InputStream instance for accessing the content
* (body) of the message.
* @exception MessagingException
* @see javax.mail.internet.MimeMessage#getContentStream()
*/
protected InputStream getContentStream() throws MessagingException {
// no content loaded yet?
if (content == null) {
// make sure we're still valid
checkValidity();
// make sure the content is fully loaded
loadContent();
}
// allow the super class to handle creating it from the loaded content.
return super.getContentStream();
}
/**
* Write out the byte data to the provided output stream.
*
* @param out The target stream.
*
* @exception IOException
* @exception MessagingException
*/
public void writeTo(OutputStream out) throws IOException, MessagingException {
// no content loaded yet?
if (content == null) {
// make sure we're still valid
checkValidity();
// make sure the content is fully loaded
loadContent();
}
loadHeaders();
Enumeration e = headers.getAllHeaderLines();
while(e.hasMoreElements()) {
String line = (String)e.nextElement();
out.write(line.getBytes("ISO8859-1"));
out.write(CRLF);
}
out.write(CRLF);
out.write(CRLF);
out.write(content);
}
/******************************************************************
* Following is a set of methods that deal with information in the
* envelope. These methods ensure the enveloper is loaded and
* retrieve the information.
********************************************************************/
/**
* Get the message "From" addresses. This looks first at the
* "From" headers, and no "From" header is found, the "Sender"
* header is checked. Returns null if not found.
*
* @return An array of addresses identifying the message from target. Returns
* null if this is not resolveable from the headers.
* @exception MessagingException
*/
public Address[] getFrom() throws MessagingException {
// make sure we've retrieved the envelope information.
loadEnvelope();
// make sure we return a copy of the array so this can't be changed.
Address[] addresses = envelope.from;
if (addresses == null) {
return null;
}
return (Address[])addresses.clone();
}
/**
* Return the "Sender" header as an address.
*
* @return the "Sender" header as an address, or null if not present
* @throws MessagingException if there was a problem parsing the header
*/
public Address getSender() throws MessagingException {
// make sure we've retrieved the envelope information.
loadEnvelope();
// make sure we return a copy of the array so this can't be changed.
Address[] addresses = envelope.sender;
if (addresses == null) {
return null;
}
// There's only a single sender, despite IMAP potentially returning a list
return addresses[0];
}
/**
* Gets the recipients by type. Returns null if there are no
* headers of the specified type. Acceptable RecipientTypes are:
*
* javax.mail.Message.RecipientType.TO
* javax.mail.Message.RecipientType.CC
* javax.mail.Message.RecipientType.BCC
* javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
*
* @param type The message RecipientType identifier.
*
* @return The array of addresses for the specified recipient types.
* @exception MessagingException
*/
public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
// make sure we've retrieved the envelope information.
loadEnvelope();
Address[] addresses = null;
if (type == Message.RecipientType.TO) {
addresses = envelope.to;
}
else if (type == Message.RecipientType.CC) {
addresses = envelope.cc;
}
else if (type == Message.RecipientType.BCC) {
addresses = envelope.bcc;
}
else {
// this could be a newsgroup type, which will tickle the message headers.
return super.getRecipients(type);
}
// make sure we return a copy of the array so this can't be changed.
if (addresses == null) {
return null;
}
return (Address[])addresses.clone();
}
/**
* Get the ReplyTo address information. The headers are parsed
* using the "mail.mime.address.strict" setting. If the "Reply-To" header does
* not have any addresses, then the value of the "From" field is used.
*
* @return An array of addresses obtained from parsing the header.
* @exception MessagingException
*/
public Address[] getReplyTo() throws MessagingException {
// make sure we've retrieved the envelope information.
loadEnvelope();
// make sure we return a copy of the array so this can't be changed.
Address[] addresses = envelope.replyTo;
if (addresses == null) {
return null;
}
return (Address[])addresses.clone();
}
/**
* Returns the value of the "Subject" header. If the subject
* is encoded as an RFC 2047 value, the value is decoded before
* return. If decoding fails, the raw string value is
* returned.
*
* @return The String value of the subject field.
* @exception MessagingException
*/
public String getSubject() throws MessagingException {
// make sure we've retrieved the envelope information.
loadEnvelope();
if (envelope.subject == null) {
return null;
}
// the subject could be encoded. If there is a decoding error,
// return the raw subject string.
try {
return MimeUtility.decodeText(envelope.subject);
} catch (UnsupportedEncodingException e) {
return envelope.subject;
}
}
/**
* Get the value of the "Date" header field. Returns null if
* if the field is absent or the date is not in a parseable format.
*
* @return A Date object parsed according to RFC 822.
* @exception MessagingException
*/
public Date getSentDate() throws MessagingException {
// make sure we've retrieved the envelope information.
loadEnvelope();
// just return that directly
return envelope.date;
}
/**
* Get the message received date.
*
* @return Always returns the formatted INTERNALDATE, if available.
* @exception MessagingException
*/
public Date getReceivedDate() throws MessagingException {
loadEnvelope();
return receivedDate;
}
/**
* Retrieve the size of the message content. The content will
* be retrieved from the server, if necessary.
*
* @return The size of the content.
* @exception MessagingException
*/
public int getSize() throws MessagingException {
// make sure we've retrieved the envelope information. We load the
// size when we retrieve that.
loadEnvelope();
return size;
}
/**
* Get a line count for the IMAP message. This is potentially
* stored in the Lines article header. If not there, we return
* a default of -1.
*
* @return The header line count estimate, or -1 if not retrieveable.
* @exception MessagingException
*/
public int getLineCount() throws MessagingException {
loadBodyStructure();
return bodyStructure.lines;
}
/**
* Return the IMAP in reply to information (retrieved with the
* ENVELOPE).
*
* @return The in reply to String value, if available.
* @exception MessagingException
*/
public String getInReplyTo() throws MessagingException {
loadEnvelope();
return envelope.inReplyTo;
}
/**
* Returns the current content type (defined in the "Content-Type"
* header. If not available, "text/plain" is the default.
*
* @return The String name of the message content type.
* @exception MessagingException
*/
public String getContentType() throws MessagingException {
loadBodyStructure();
return bodyStructure.mimeType.toString();
}
/**
* Tests to see if this message has a mime-type match with the
* given type name.
*
* @param type The tested type name.
*
* @return If this is a type match on the primary and secondare portion of the types.
* @exception MessagingException
*/
public boolean isMimeType(String type) throws MessagingException {
loadBodyStructure();
return bodyStructure.mimeType.match(type);
}
/**
* Retrieve the message "Content-Disposition" header field.
* This value represents how the part should be represented to
* the user.
*
* @return The string value of the Content-Disposition field.
* @exception MessagingException
*/
public String getDisposition() throws MessagingException {
loadBodyStructure();
if (bodyStructure.disposition != null) {
return bodyStructure.disposition.getDisposition();
}
return null;
}
/**
* Decode the Content-Transfer-Encoding header to determine
* the transfer encoding type.
*
* @return The string name of the required encoding.
* @exception MessagingException
*/
public String getEncoding() throws MessagingException {
loadBodyStructure();
return bodyStructure.transferEncoding;
}
/**
* Retrieve the value of the "Content-ID" header. Returns null
* if the header does not exist.
*
* @return The current header value or null.
* @exception MessagingException
*/
public String getContentID() throws MessagingException {
loadBodyStructure();
return bodyStructure.contentID;
}
public String getContentMD5() throws MessagingException {
loadBodyStructure();
return bodyStructure.md5Hash;
}
public String getDescription() throws MessagingException {
loadBodyStructure();
if (bodyStructure.contentDescription == null) {
return null;
}
// the subject could be encoded. If there is a decoding error,
// return the raw subject string.
try {
return MimeUtility.decodeText(bodyStructure.contentDescription);
} catch (UnsupportedEncodingException e) {
return bodyStructure.contentDescription;
}
}
/**
* Return the content languages associated with this
* message.
*
* @return
* @exception MessagingException
*/
public String[] getContentLanguage() throws MessagingException {
loadBodyStructure();
if (!bodyStructure.languages.isEmpty()) {
return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]);
}
return null;
}
public String getMessageID() throws MessagingException {
loadEnvelope();
return envelope.messageID;
}
public void setFrom(Address address) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void addFrom(Address[] address) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setSender(Address address) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setReplyTo(Address[] address) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setSubject(String subject) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setSubject(String subject, String charset) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setSentDate(Date sent) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setDisposition(String disposition) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setContentID(String cid) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setContentMD5(String md5) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setDescription(String description) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setDescription(String description, String charset) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setContentLanguage(String[] languages) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
/******************************************************************
* Following is a set of methods that deal with headers
* These methods are just overrides on the superclass methods to
* allow lazy loading of the header information.
********************************************************************/
public String[] getHeader(String name) throws MessagingException {
loadHeaders();
return headers.getHeader(name);
}
public String getHeader(String name, String delimiter) throws MessagingException {
loadHeaders();
return headers.getHeader(name, delimiter);
}
public Enumeration getAllHeaders() throws MessagingException {
loadHeaders();
return headers.getAllHeaders();
}
public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaders(names);
}
public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaders(names);
}
public Enumeration getAllHeaderLines() throws MessagingException {
loadHeaders();
return headers.getAllHeaderLines();
}
public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaderLines(names);
}
public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaderLines(names);
}
// the following are overrides for header modification methods. These messages are read only,
// so the headers cannot be modified.
public void addHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void removeHeader(String name) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void addHeaderLine(String line) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
/**
* We cannot modify these messages
*/
public void saveChanges() throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
/**
* Utility method for synchronizing IMAP envelope information and
* the message headers.
*
* @param header The target header name.
* @param addresses The update addresses.
*/
protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException {
if (addresses != null) {
headers.addHeader(header, InternetAddress.toString(addresses));
}
}
/**
* Utility method for synchronizing IMAP envelope information and
* the message headers.
*
* @param header The target header name.
* @param address The update address.
*/
protected void updateHeader(String header, Address address) throws MessagingException {
if (address != null) {
headers.setHeader(header, address.toString());
}
}
/**
* Utility method for synchronizing IMAP envelope information and
* the message headers.
*
* @param header The target header name.
* @param value The update value.
*/
protected void updateHeader(String header, String value) throws MessagingException {
if (value != null) {
headers.setHeader(header, value);
}
}
/**
* Create the DataHandler object for this message.
*
* @return The DataHandler object that processes the content set for this
* message.
* @exception MessagingException
*/
public synchronized DataHandler getDataHandler() throws MessagingException {
// check the validity and make sure we have the body structure information.
checkValidity();
loadBodyStructure();
if (dh == null) {
// are we working with a multipart message here?
if (bodyStructure.isMultipart()) {
dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure));
return dh;
}
else if (bodyStructure.isAttachedMessage()) {
dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody),
bodyStructure.mimeType.toString());
return dh;
}
}
// single part messages get handled the normal way.
return super.getDataHandler();
}
public void setDataHandler(DataHandler content) throws MessagingException {
throw new IllegalWriteException("IMAP body parts are read-only");
}
/**
* Update the message headers from an input stream.
*
* @param in The InputStream source for the header information.
*
* @exception MessagingException
*/
public void updateHeaders(InputStream in) throws MessagingException {
// wrap a stream around the reply data and read as headers.
headers = new InternetHeaders(in);
allHeadersRetrieved = true;
}
/**
* Load the flag set for this message from the server.
*
* @exception MessagingeException
*/
public void loadFlags() throws MessagingException {
// make sure this is in a valid state.
checkValidity();
// if the flags are already loaded, nothing to do
if (flags != null) {
return;
}
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
synchronized (folder) {
IMAPConnection connection = getConnection();
try {
// fetch the flags for this item.
flags = connection.fetchFlags(sequenceNumber);
} finally {
releaseConnection(connection);
}
}
}
/**
* Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set.
*
* @exception MessagingException
*/
protected synchronized void loadHeaders() throws MessagingException {
// don't retrieve if already loaded.
if (allHeadersRetrieved) {
return;
}
// make sure this is in a valid state.
checkValidity();
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
synchronized (folder) {
IMAPConnection connection = getConnection();
try {
// get the headers and set
headers = connection.fetchHeaders(sequenceNumber, section);
// we have the entire header set, not just a subset.
allHeadersRetrieved = true;
} finally {
releaseConnection(connection);
}
}
}
/**
* Retrieve the message envelope from the IMAP server, synchronizing the headers with the
* information.
*
* @exception MessagingException
*/
protected synchronized void loadEnvelope() throws MessagingException {
// don't retrieve if already loaded.
if (envelope != null) {
return;
}
// make sure this is in a valid state.
checkValidity();
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
synchronized (folder) {
IMAPConnection connection = getConnection();
try {
// fetch the envelope information for this
List fetches = connection.fetchEnvelope(sequenceNumber);
// now process all of the fetch responses before releasing the folder lock.
// it's possible that an unsolicited update on another thread might try to
// make an update, causing a potential deadlock.
for (int i = 0; i < fetches.size(); i++) {
// get the returned data items from each of the fetch responses
// and process.
IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i);
// update the internal info
updateMessageInformation(fetch);
}
} finally {
releaseConnection(connection);
}
}
}
/**
* Retrieve the message envelope from the IMAP server, synchronizing the headers with the
* information.
*
* @exception MessagingException
*/
protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException {
// set the envelope item
this.envelope = envelope;
// copy header type information from the envelope into the headers.
updateHeader("From", envelope.from);
if (envelope.sender != null) {
// we can only have a single sender, even though the envelope theoretically supports more.
updateHeader("Sender", envelope.sender[0]);
}
updateHeader("To", envelope.to);
updateHeader("Cc", envelope.cc);
updateHeader("Bcc", envelope.bcc);
updateHeader("Reply-To", envelope.replyTo);
// NB: This is already in encoded form, if needed.
updateHeader("Subject", envelope.subject);
updateHeader("Message-ID", envelope.messageID);
}
/**
* Retrieve the BODYSTRUCTURE information from the IMAP server.
*
* @exception MessagingException
*/
protected synchronized void loadBodyStructure() throws MessagingException {
// don't retrieve if already loaded.
if (bodyStructure != null) {
return;
}
// make sure this is in a valid state.
checkValidity();
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
synchronized (folder) {
IMAPConnection connection = getConnection();
try {
// fetch the envelope information for this
bodyStructure = connection.fetchBodyStructure(sequenceNumber);
// go update all of the information
} finally {
releaseConnection(connection);
}
// update this before we release the folder lock so we can avoid
// deadlock.
updateBodyStructure(bodyStructure);
}
}
/**
* Update the BODYSTRUCTURE information from the IMAP server.
*
* @exception MessagingException
*/
protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException {
// save the reference.
bodyStructure = structure;
// now update various headers with the information from the body structure
// now update header information with the body structure data.
if (bodyStructure.lines != -1) {
updateHeader("Lines", Integer.toString(bodyStructure.lines));
}
// languages are a little more complicated
if (bodyStructure.languages != null) {
// this is a duplicate of what happens in the super class, but
// the superclass methods call setHeader(), which we override and
// throw an exception for. We need to set the headers ourselves.
if (bodyStructure.languages.size() == 1) {
updateHeader("Content-Language", (String)bodyStructure.languages.get(0));
}
else {
StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20);
buf.append(bodyStructure.languages.get(0));
for (int i = 1; i < bodyStructure.languages.size(); i++) {
buf.append(',').append(bodyStructure.languages.get(i));
}
updateHeader("Content-Language", buf.toString());
}
}
updateHeader("Content-Type", bodyStructure.mimeType.toString());
if (bodyStructure.disposition != null) {
updateHeader("Content-Disposition", bodyStructure.disposition.toString());
}
updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding);
updateHeader("Content-ID", bodyStructure.contentID);
// NB: This is already in encoded form, if needed.
updateHeader("Content-Description", bodyStructure.contentDescription);
}
/**
* Load the message content into the Message object.
*
* @exception MessagingException
*/
protected void loadContent() throws MessagingException {
// if we've loaded this already, just return
if (content != null) {
return;
}
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
synchronized (folder) {
IMAPConnection connection = getConnection();
try {
// load the content from the server.
content = connection.fetchContent(getSequenceNumber(), section);
} finally {
releaseConnection(connection);
}
}
}
/**
* Retrieve the sequence number assigned to this message.
*
* @return The messages assigned sequence number. This maps back to the server's assigned number for
* this message.
*/
int getSequenceNumber() {
return sequenceNumber;
}
/**
* Set the sequence number for the message. This
* is updated whenever messages get expunged from
* the folder.
*
* @param s The new sequence number.
*/
void setSequenceNumber(int s) {
sequenceNumber = s;
}
/**
* Retrieve the message UID value.
*
* @return The assigned UID value, if we have the information.
*/
long getUID() {
return uid;
}
/**
* Set the message UID value.
*
* @param uid The new UID value.
*/
void setUID(long uid) {
this.uid = uid;
}
/**
* get the current connection pool attached to the folder. We need
* to do this dynamically, to A) ensure we're only accessing an
* currently open folder, and B) to make sure we're using the
* correct connection attached to the folder.
*
* @return A connection attached to the hosting folder.
*/
protected IMAPConnection getConnection() throws MessagingException {
// the folder owns everything.
return ((IMAPFolder)folder).getMessageConnection();
}
/**
* Release the connection back to the Folder after performing an operation
* that requires a connection.
*
* @param connection The previously acquired connection.
*/
protected void releaseConnection(IMAPConnection connection) throws MessagingException {
// the folder owns everything.
((IMAPFolder)folder).releaseMessageConnection(connection);
}
/**
* Check the validity of the current message. This ensures that
* A) the folder is currently open, B) that the message has not
* been expunged (after getting the latest status from the server).
*
* @exception MessagingException
*/
protected void checkValidity() throws MessagingException {
checkValidity(false);
}
/**
* Check the validity of the current message. This ensures that
* A) the folder is currently open, B) that the message has not
* been expunged (after getting the latest status from the server).
*
* @exception MessagingException
*/
protected void checkValidity(boolean update) throws MessagingException {
// we need to ensure that we're the only ones with access to the folder's
// message cache any time we need to talk to the server. This needs to be
// held until after we release the connection so that any pending EXPUNGE
// untagged responses are processed before the next time the folder connection is
// used.
if (update) {
synchronized (folder) {
// have the connection update the folder status. This might result in this message
// changing its state to expunged. It might also result in an exception if the
// folder has been closed.
IMAPConnection connection = getConnection();
try {
connection.updateMailboxStatus();
} finally {
// this will force any expunged messages to be processed before we release
// the lock.
releaseConnection(connection);
}
}
}
// now see if we've been expunged, this is a bad op on the message.
if (isExpunged()) {
throw new MessageRemovedException("Illegal opertion on a deleted message");
}
}
/**
* Evaluate whether this message requires any of the information
* in a FetchProfile to be fetched from the server. If the messages
* already contains the information in the profile, it returns false.
* This allows IMAPFolder to optimize fetch() requests to just the
* messages that are missing any of the requested information.
*
* NOTE: If any of the items in the profile are missing, then this
* message will be updated with ALL of the items.
*
* @param profile The FetchProfile indicating the information that should be prefetched.
*
* @return true if any of the profile information requires fetching. false if this
* message already contains the given information.
*/
protected boolean evaluateFetch(FetchProfile profile) {
// the fetch profile can contain a number of different item types. Validate
// whether we need any of these and return true on the first mismatch.
// the UID is a common fetch request, put it first.
if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) {
return true;
}
if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) {
return true;
}
if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) {
return true;
}
if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) {
return true;
}
// The following profile items are our implementation of items that the
// Sun IMAPFolder implementation supports.
if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) {
return true;
}
if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) {
return true;
}
// last bit after checking each of the information types is to see if
// particular headers have been requested and whether those are on the
// set we do have loaded.
String [] requestedHeaders = profile.getHeaderNames();
// ok, any missing header in the list is enough to force us to request the
// information.
for (int i = 0; i < requestedHeaders.length; i++) {
if (headers.getHeader(requestedHeaders[i]) == null) {
return true;
}
}
// this message, at least, does not need anything fetched.
return false;
}
/**
* Update a message instance with information retrieved via an IMAP FETCH
* command. The command response for this message may contain multiple pieces
* that we need to process.
*
* @param response The response line, which may contain multiple data items.
*
* @exception MessagingException
*/
void updateMessageInformation(IMAPFetchResponse response) throws MessagingException {
// get the list of data items associated with this response. We can have
// a large number of items returned in a single update.
List items = response.getDataItems();
for (int i = 0; i < items.size(); i++) {
IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i);
switch (item.getType()) {
// if the envelope has been requested, we'll end up with all of these items.
case IMAPFetchDataItem.ENVELOPE:
// update the envelope and map the envelope items into the headers.
updateEnvelope((IMAPEnvelope)item);
break;
case IMAPFetchDataItem.INTERNALDATE:
receivedDate = ((IMAPInternalDate)item).getDate();;
break;
case IMAPFetchDataItem.SIZE:
size = ((IMAPMessageSize)item).size;
break;
case IMAPFetchDataItem.UID:
uid = ((IMAPUid)item).uid;
// make sure the folder knows about the UID update.
((IMAPFolder)folder).addToUidCache(new Long(uid), this);
break;
case IMAPFetchDataItem.BODYSTRUCTURE:
updateBodyStructure((IMAPBodyStructure)item);
break;
// a partial or full header update
case IMAPFetchDataItem.HEADER:
{
// if we've fetched the complete set, then replace what we have
IMAPInternetHeader h = (IMAPInternetHeader)item;
if (h.isComplete()) {
// we've got a complete header set now.
this.headers = h.headers;
allHeadersRetrieved = true;
}
else {
// need to merge the requested headers in with
// our existing set. We need to be careful, since we
// don't want to add duplicates.
mergeHeaders(h.headers);
}
}
default:
}
}
}
/**
* Merge a subset of the requested headers with our existing partial set.
* The new set will contain all headers requested from the server, plus
* any of our existing headers that were not included in the retrieved set.
*
* @param newHeaders The retrieved set of headers.
*/
protected synchronized void mergeHeaders(InternetHeaders newHeaders) {
// This is sort of tricky to manage. The input headers object is a fresh set
// retrieved from the server, but it's a subset of the headers. Our existing set
// might not be complete, but it may contain duplicates of information in the
// retrieved set, plus headers that are not in the retrieved set. To keep from
// adding duplicates, we'll only add headers that are not in the retrieved set to
// that set.
// start by running through the list of headers
Enumeration e = headers.getAllHeaders();
while (e.hasMoreElements()) {
Header header = (Header)e.nextElement();
// if there are no headers with this name in the new set, then
// we can add this. Note that to add the header, we need to
// retrieve all instances by this name and add them as a unit.
// When we hit one of the duplicates again with the enumeration,
// we'll skip it then because the merge target will have everything.
if (newHeaders.getHeader(header.getName()) == null) {
// get all occurrences of this name and stuff them into the
// new list
String name = header.getName();
String[] a = headers.getHeader(name);
for (int i = 0; i < a.length; i++) {
newHeaders.addHeader(name, a[i]);
}
}
}
// and replace the current header set
headers = newHeaders;
}
}
././@LongLink 0000000 0000000 0000000 00000000161 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeB0000664 0001750 0001750 00000027601 10716317503 031753 0 ustar brian brian /**
* 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;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import javax.activation.DataHandler;
import javax.mail.IllegalWriteException;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeUtility;
import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
public class IMAPMimeBodyPart extends MimeBodyPart {
// the message we're part of
protected IMAPMessage message;
// the retrieved BODYSTRUCTURE information for this part.
protected IMAPBodyStructure bodyStructure;
// the section identifier. This will be in a format such as 1.2.3, which
// would refer to the "third part contained in the second part of the first part"...
// got all that? There will be a quiz at the end of class :-)
protected String section;
// flag to indicate whether the body part headers have been loaded.
boolean headersLoaded = false;
/**
* Create an instance of a MimeBodyPart within an
* IMAP message.
*
* @param message The parent Message instance containing this part.
* @param bodyStructure
* The IMAPBodyStructure information describing the part.
* @param section The numeric section identifier string for this part.
* This is a hierarchical set of numbers describing
* how to navigate to the message part on the IMAP
* server. For example, "2.1.3" would be the third
* subpart of the first subpart of the second main
* message part.
*/
public IMAPMimeBodyPart(IMAPMessage message, IMAPBodyStructure bodyStructure, String section) {
super();
this.message = message;
this.bodyStructure = bodyStructure;
this.section = section;
}
/**
* Get the size of the message part.
*
* @return The size information returned in the IMAP body structure.
* @exception MessagingException
*/
public int getSize() throws MessagingException {
return bodyStructure.bodySize;
}
/**
* Get the estimated line count for the body part.
*
* @return The line count information returned by the IMAP
* server.
* @exception MessagingException
*/
public int getLineCount() throws MessagingException {
return bodyStructure.lines;
}
/**
* Get the content type for the body part.
*
* @return The mimetype for the body part, in string format.
* @exception MessagingException
*/
public String getContentType() throws MessagingException {
return bodyStructure.mimeType.toString();
}
/**
* Test if the body part is of a particular MIME type.
*
* @param type The string MIME-type name. A wild card * can be
* specified for the subpart type.
*
* @return true if the body part matches the give MIME-type.
* @exception MessagingException
*/
public boolean isMimeType(String type) throws MessagingException {
return bodyStructure.mimeType.match(type);
}
/**
* Retrieve the disposition information about this
* body part.
*
* @return The disposition information, as a string value.
* @exception MessagingException
*/
public String getDisposition() throws MessagingException {
return bodyStructure.disposition.getDisposition();
}
/**
* Set the disposition information. The IMAP message
* is read-only, so this is an error.
*
* @param disposition
* The disposition string.
*
* @exception MessagingException
*/
public void setDisposition(String disposition) throws MessagingException {
throw new IllegalWriteException("IMAP message parts are read-only");
}
public String getEncoding() throws MessagingException {
return bodyStructure.transferEncoding;
}
public String getContentID() throws MessagingException {
return bodyStructure.contentID;
}
public void setContentID(String id) throws MessagingException {
throw new IllegalWriteException("IMAP message parts are read-only");
}
public String getContentMD5() throws MessagingException {
return bodyStructure.md5Hash;
}
public void setContentMD5(String id) throws MessagingException {
throw new IllegalWriteException("IMAP message parts are read-only");
}
public String getDescription() throws MessagingException {
String description = bodyStructure.contentDescription;
if (description != null) {
try {
// this could be both folded and encoded. Return this to usable form.
return MimeUtility.decodeText(MimeUtility.unfold(description));
} catch (UnsupportedEncodingException e) {
// ignore
}
}
// return the raw version for any errors (this might be null also)
return description;
}
public void setDescription(String d, String charset) throws MessagingException {
throw new IllegalWriteException("IMAP message parts are read-only");
}
public String getFileName() throws MessagingException {
String filename = bodyStructure.disposition.getParameter("filename");
if (filename == null) {
filename = bodyStructure.mimeType.getParameter("name");
}
return filename;
}
public void setFileName(String name) throws MessagingException {
throw new IllegalWriteException("IMAP message parts are read-only");
}
protected InputStream getContentStream() throws MessagingException {
// no content loaded yet?
if (content == null) {
// make sure we're still valid
message.checkValidity();
// make sure the content is fully loaded
loadContent();
}
// allow the super class to handle creating it from the loaded content.
return super.getContentStream();
}
/**
* Create the DataHandler object for this message.
*
* @return The DataHandler object that processes the content set for this
* message.
* @exception MessagingException
*/
public synchronized DataHandler getDataHandler() throws MessagingException {
if (dh == null) {
// are we working with a multipart message here?
if (bodyStructure.isMultipart()) {
dh = new DataHandler(new IMAPMultipartDataSource(message, this, section, bodyStructure));
return dh;
}
else if (bodyStructure.isAttachedMessage()) {
dh = new DataHandler(new IMAPAttachedMessage(message, section, bodyStructure.nestedEnvelope,
bodyStructure.nestedBody), bodyStructure.mimeType.toString());
return dh;
}
}
// single part messages get handled the normal way.
return super.getDataHandler();
}
public void setDataHandler(DataHandler content) throws MessagingException {
throw new IllegalWriteException("IMAP body parts are read-only");
}
public void setContent(Object o, String type) throws MessagingException {
throw new IllegalWriteException("IMAP body parts are read-only");
}
public void setContent(Multipart mp) throws MessagingException {
throw new IllegalWriteException("IMAP body parts are read-only");
}
/******************************************************************
* Following is a set of methods that deal with headers
* These methods are just overrides on the superclass methods to
* allow lazy loading of the header information.
********************************************************************/
public String[] getHeader(String name) throws MessagingException {
loadHeaders();
return headers.getHeader(name);
}
public String getHeader(String name, String delimiter) throws MessagingException {
loadHeaders();
return headers.getHeader(name, delimiter);
}
public Enumeration getAllHeaders() throws MessagingException {
loadHeaders();
return headers.getAllHeaders();
}
public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaders(names);
}
public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaders(names);
}
public Enumeration getAllHeaderLines() throws MessagingException {
loadHeaders();
return headers.getAllHeaderLines();
}
public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaderLines(names);
}
public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaderLines(names);
}
// the following are overrides for header modification methods. These messages are read only,
// so the headers cannot be modified.
public void addHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void setHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void removeHeader(String name) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
public void addHeaderLine(String line) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
/**
* Load the mime part headers into this body part.
*
* @exception MessagingException
*/
protected synchronized void loadHeaders() throws MessagingException {
// have them already? Super..
if (headers != null) {
return;
}
IMAPConnection connection = message.getConnection();
try {
// this asks for the MIME subsection of the given section piece.
headers = connection.fetchHeaders(message.getSequenceNumber(), section);
} finally {
message.releaseConnection(connection);
}
}
/**
* Load the message content into the BodyPart object.
*
* @exception MessagingException
*/
protected void loadContent() throws MessagingException {
// if we've loaded this already, just return
if (content != null) {
return;
}
IMAPConnection connection = message.getConnection();
try {
// load the content from the server.
content = connection.fetchContent(message.getSequenceNumber(), section);
} finally {
message.releaseConnection(connection);
}
}
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLSt0000664 0001750 0001750 00000003056 10721056121 031720 0 ustar brian brian /**
* 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;
import javax.mail.Session;
import javax.mail.URLName;
/**
* IMAP implementation of javax.mail.Store for SSL connections.
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class IMAPSSLStore extends IMAPStore {
/**
* Construct an IMAPSSLStore item.
*
* @param session The owning javamail Session.
* @param urlName The Store urlName, which can contain server target information.
*/
public IMAPSSLStore(Session session, URLName urlName) {
// we're the imaps protocol, our default connection port is 993, and we must use
// an SSL connection for the initial hookup
super(session, urlName, "imaps", true, DEFAULT_IMAP_SSL_PORT);
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java 0000664 0001750 0001750 00000005162 11025715072 031625 0 ustar brian brian /**
* 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;
/**
* A named access control list for IMAP resources.
*/
public class ACL implements Cloneable {
/**
* The name of the resource this ACL applies to.
*/
private String name;
/**
* The rights associated with this resource.
*/
private Rights rights;
/**
* Create an ACL for a resource. The ACL will have an empty Rights set.
*
* @param name The name of the resource.
*/
public ACL(String name) {
this.name = name;
this.rights = new Rights();
}
/**
* Create a named ACL instance with an initial Rights set.
*
* @param name The name of the resouce this ACL applies to.
* @param rights The Rights associated with this resource.
*/
public ACL(String name, Rights rights) {
this.name = name;
this.rights = rights;
}
/**
* Get the ACL name.
*
* @return The string name of the ACL.
*/
public String getName() {
return name;
}
/**
* Get the Rights associated with this ACL.
*
* @return The Rights set supported for this resource.
*/
public Rights getRights() {
return rights;
}
/**
* Set a new set of Rights for this ACL instance.
*
* @param rights The new Rights set.
*/
public void setRights(Rights rights) {
this.rights = rights;
}
/**
* Creates and returns a copy of this object.
*
* @return A cloned copy of this object. This is a deep
* copy, given that a new Rights set is also created.
* @exception CloneNotSupportedException
*/
protected Object clone() throws CloneNotSupportedException {
return new ACL(name, new Rights(rights));
}
}
././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.ja0000664 0001750 0001750 00000022014 11025715072 032132 0 ustar brian brian /**
* 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;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Represents a set of rights associated with a user to manipulate the
* IMAP Store.
*/
public class Rights implements Cloneable {
/**
* An individual right for IMAP Store manipulation.
*/
public static final class Right {
// The set of created stores. The getInstance() method ensures
// that each right is a singleton object.
static private Map rights = new HashMap();
/**
* lookup (mailbox is visible to LIST/LSUB commands)
*/
public static final Right LOOKUP = getInstance('l');
/**
* read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL,
* SEARCH, COPY from mailbox)
*/
public static final Right READ = getInstance('r');
/**
* keep seen/unseen information across sessions (STORE SEEN flag)
*/
public static final Right KEEP_SEEN = getInstance('s');
/**
* write (STORE flags other than SEEN and DELETED)
*/
public static final Right WRITE = getInstance('w');
/**
* insert (perform APPEND, COPY into mailbox)
*/
public static final Right INSERT = getInstance('i');
/**
* post (send mail to submission address for mailbox,
* not enforced by IMAP4 itself)
*/
public static final Right POST = getInstance('p');
/**
* create (CREATE new sub-mailboxes in any implementation-defined
* hierarchy)
*/
public static final Right CREATE = getInstance('c');
/**
* delete (STORE DELETED flag, perform EXPUNGE)
*/
public static final Right DELETE = getInstance('d');
/**
* administer (perform SETACL)
*/
public static final Right ADMINISTER = getInstance('a');
// the actual right definition
String right;
/**
* Private constructor for an individual Right. Used by getInstance().
*
* @param right The String name of the right (a single character).
*/
private Right(String right) {
this.right = right;
}
/**
* Get an instance for a right from the single character right value. The
* returned instance will be a singleton for that character value.
*
* @param right The right character value.
*
* @return A Right instance that's the mapping for the character value.
*/
public static synchronized Right getInstance(char right) {
String name = String.valueOf(right);
Right instance = (Right)rights.get(name);
if (instance == null) {
instance = new Right(name);
rights.put(name, instance);
}
return instance;
}
/**
* Return the string value of the Right. The string value is the character
* used to create the Right with newInstance().
*
* @return The string representation of the Right.
*/
public String toString() {
return right;
}
}
/**
* The set of Rights contained in this instance. This is a TreeSet so that
* we can create the string value more consistently.
*/
private SortedSet rights = new TreeSet(new RightComparator());
/**
* Construct an empty set of Rights.
*/
public Rights() {
}
/**
* Construct a Rights set from a single Right instance.
*
* @param right The source Right.
*/
public Rights(Right right) {
rights.add(right);
}
/**
* Construct a set of rights from an existing Rights set. This will copy
* the rights values.
*
* @param list The source Rights instance.
*/
public Rights(Rights list) {
add(list);
Rights[] otherRights = list.getRights();
for (int i = 0; i < otherRights.length; i++) {
rights.add(otherRights[i]);
}
}
/**
* Construct a Rights et from a character string. Each character in the
* string represents an individual Right.
*
* @param list The source set of rights.
*/
public Rights(String list) {
for (int i = 0; i < list.length(); i++) {
rights.add(Right.getInstance(list.charAt(i)));
}
}
/**
* Add a single Right to the set.
*
* @param right The new Right. If the Rigtht is already part of the Set, this is a nop.
*/
public void add(Right right) {
rights.add(right);
}
/**
* Merge a Rights set with this set. Duplicates are eliminated.
*
* @param list The source for the added Rights.
*/
public void add(Rights list) {
Rights[] otherRights = list.getRights();
for (int i = 0; i < otherRights.length; i++) {
rights.add(otherRights[i]);
}
}
/**
* Clone a set of Rights.
*/
public Object clone() {
return new Rights(this);
}
/**
* Test if a Rights set contains a given Right.
*
* @param right The Right instance to test.
*
* @return true if the Right exists in the Set, false otherwise.
*/
public boolean contains(Right right) {
return rights.contains(right);
}
/**
* Test if this Rights set contains all of the Rights contained in another
* set.
*
* @param list The source Rights set for the test.
*
* @return true if all of the Rights in the source set exist in the target set.
*/
public boolean contains(Rights list) {
return rights.containsAll(list.rights);
}
/**
* Test if two Rights sets are equivalent.
*
* @param list The source rights set.
*
* @return true if both Rigths sets contain the same Rights values.
*/
public boolean equals(Rights list) {
return rights.equals(list.rights);
}
/**
* Get an array of Rights contained in the set.
*
* @return An array of Rights[] values.
*/
public Rights[] getRights() {
Rights[] list = new Rights[rights.size()];
return (Rights[])rights.toArray(list);
}
/**
* Compute a hashCode for the Rights set.
*
* @return The computed hashCode.
*/
public int hashCode() {
return rights.hashCode();
}
/**
* Remove a Right from the set.
*
* @param right The single Right to remove.
*/
public void remove(Right right) {
rights.remove(right);
}
/**
* Remove a set of rights from the set.
*
* @param list The list of rights to be removed.
*/
public void remove(Rights list) {
rights.removeAll(list.rights);
}
/**
* Return a string value for the Rights set. The string value is the
* concatenation of the single-character Rights names.
*
* @return The string representation of this Rights set.
*/
public String toString() {
StringBuffer buff = new StringBuffer();
Iterator i = rights.iterator();
while (i.hasNext()) {
buff.append(i.next().toString());
}
return buff.toString();
}
class RightComparator implements Comparator {
/**
* Perform a sort comparison to order two Right objects.
* The sort is performed using the string value.
*
* @param o1 The left comparator
* @param o2 The right comparator.
*
* @return 0 if the two items have equal ordering, -1 if the
* left item is lower, 1 if the left item is greater.
*/
public int compare(Object o1, Object o2) {
// compare on the string value
String left = o1.toString();
return left.compareTo(o2.toString());
}
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolde0000664 0001750 0001750 00000266031 11165376024 032017 0 ustar brian brian /**
* 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;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Vector;
import javax.mail.*;
import javax.mail.event.ConnectionEvent;
import javax.mail.event.FolderEvent;
import javax.mail.event.MessageChangedEvent;
import javax.mail.search.FlagTerm;
import javax.mail.search.SearchTerm;
import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPFlags;
import org.apache.geronimo.javamail.store.imap.connection.IMAPListResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPMailboxStatus;
import org.apache.geronimo.javamail.store.imap.connection.IMAPSizeResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse;
import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler;
/**
* The base IMAP implementation of the javax.mail.Folder
* This is a base class for both the Root IMAP server and each IMAP group folder.
* @see javax.mail.Folder
*
* @version $Rev: 761642 $
*/
public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler {
/**
* Special profile item used for fetching SIZE and HEADER information.
* These items are extensions that Sun has added to their IMAPFolder immplementation.
* We're supporting the same set.
*/
public static class FetchProfileItem extends FetchProfile.Item {
public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE");
protected FetchProfileItem(String name) {
super(name);
}
}
// marker that we don't know the separator yet for this folder.
// This occurs when we obtain a folder reference from the
// default folder. At that point, we've not queried the
// server for specifics yet.
static final protected char UNDETERMINED = 0;
// our attached session
protected Session session;
// retrieved messages, mapped by sequence number.
protected Map messageCache;
// mappings of UIDs to retrieved messages.
protected Map uidCache;
// the separator the server indicates is used as the hierarchy separator
protected char separator;
// the "full" name of the folder. This is the fully qualified path name for the folder returned by
// the IMAP server. Elements of the hierarchy are delimited by "separator" characters.
protected String fullname;
// the name of this folder. The is the last element of the fully qualified name.
protected String name;
// the folder open state
protected boolean folderOpen = false;
// the type information on what the folder can hold
protected int folderType;
// the subscription status
protected boolean subscribed = false;
// the message identifier ticker, used to assign message numbers.
protected int nextMessageID = 1;
// the current count of messages in our cache.
protected int maxSequenceNumber = 0;
// the reported count of new messages (updated as a result of untagged message resposes)
protected int recentMessages = -1;
// the reported count of unseen messages
protected int unseenMessages = 0;
// the uidValidity value reported back from the server
protected long uidValidity = 0;
// the uidNext value reported back from the server
protected long uidNext = 0;
// the persistent flags we save in the store
protected Flags permanentFlags;
// the settable flags the server reports back to us
protected Flags availableFlags;
// Our cached status information. We will only hold this for the timeout interval.
protected IMAPMailboxStatus cachedStatus;
// Folder information retrieved from the server. Good info here indicates the
// folder exists.
protected IMAPListResponse listInfo;
// the configured status cache timeout value.
protected long statusCacheTimeout;
// the last time we took a status snap shot.
protected long lastStatusTimeStamp;
// Our current connection. We get one of these when opened, and release it when closed.
// We do this because for any folder (and message) operations, the folder must be selected on
// the connection.
// Note, however, that there are operations which will require us to borrow a connection
// temporarily because we need to touch the server when the folder is not open. In those
// cases, we grab a connection, then immediately return it to the pool.
protected IMAPConnection currentConnection;
/**
* Super class constructor the base IMAPFolder class.
*
* @param store The javamail store this folder is attached to.
* @param fullname The fully qualified name of this folder.
* @param separator The separtor character used to delimit the different
* levels of the folder hierarchy. This is used to
* decompose the full name into smaller parts and
* create the names of subfolders.
*/
protected IMAPFolder(IMAPStore store, String fullname, char separator) {
super(store);
this.session = store.getSession();
this.fullname = fullname;
this.separator = separator;
// get the status timeout value from the folder.
statusCacheTimeout = store.statusCacheTimeout;
}
/**
* Retrieve the folder name. This is the simple folder
* name at the its hiearchy level. This can be invoked when the folder is closed.
*
* @return The folder's name.
*/
public String getName() {
// At the time we create the folder, we might not know the separator character yet.
// Because of this we need to delay creating the name element until
// it's required.
if (name == null) {
// extract the name from the full name
int lastLevel = -1;
try {
lastLevel = fullname.lastIndexOf(getSeparator());
} catch (MessagingException e) {
// not likely to occur, but the link could go down before we
// get this. Just assume a failure to locate the character
// occurred.
}
if (lastLevel == -1) {
name = fullname;
}
else {
name = fullname.substring(lastLevel + 1);
}
}
return name;
}
/**
* Retrieve the folder's full name (including hierarchy information).
* This can be invoked when the folder is closed.
*
* @return The full name value.
*/
public String getFullName() {
return fullname;
}
/**
* Return the parent for this folder; if the folder is at the root of a heirarchy
* this returns null.
* This can be invoked when the folder is closed.
*
* @return this folder's parent
* @throws MessagingException
*/
public Folder getParent() throws MessagingException {
// NB: We need to use the method form because the separator
// might not have been retrieved from the server yet.
char separator = getSeparator();
// we don't hold a reference to the parent folder, as that would pin the instance in memory
// as long as any any leaf item in the hierarchy is still open.
int lastLevel = fullname.lastIndexOf(separator);
// no parent folder? Get the root one from the Store.
if (lastLevel == -1) {
return ((IMAPStore)store).getDefaultFolder();
}
else {
// create a folder for the parent.
return new IMAPFolder((IMAPStore)store, fullname.substring(0, lastLevel), separator);
}
}
/**
* Check to see if this folder physically exists in the store.
* This can be invoked when the folder is closed.
*
* @return true if the folder really exists
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized boolean exists() throws MessagingException {
IMAPConnection connection = getConnection();
try {
return checkExistance(connection);
} finally {
releaseConnection(connection);
}
}
/**
* Internal routine for checking existance using an
* already obtained connection. Used for situations
* where the list information needs updating but
* we'd end up acquiring a new connection because
* the folder isn't open yet.
*
* @param connection The connection to use.
*
* @return true if the folder exists, false for non-existence.
* @exception MessagingException
*/
private boolean checkExistance(IMAPConnection connection) throws MessagingException {
// get the list response for this folder.
List responses = connection.list("", fullname);
// NB, this grabs the latest information and updates
// the type information also. Note also that we need to
// use the mailbox name, not the full name. This is so
// the namespace folders will return the correct response.
listInfo = findListResponse(responses, getMailBoxName());
if (listInfo == null) {
return false;
}
// update the type information from the status.
folderType = 0;
if (!listInfo.noinferiors) {
folderType |= HOLDS_FOLDERS;
}
if (!listInfo.noselect) {
folderType |= HOLDS_MESSAGES;
}
// also update the separator information. This will allow
// use to skip a call later
separator = listInfo.separator;
// this can be omitted in the response, so assume a default
if (separator == '\0') {
separator = '/';
}
// updated ok, so it must be there.
return true;
}
/**
* Return a list of folders from this Folder's namespace that match the supplied pattern.
* Patterns may contain the following wildcards:
* - '%' which matches any characater except hierarchy delimiters
* - '*' which matches any character including hierarchy delimiters
*
* This can be invoked when the folder is closed.
*
* @param pattern the pattern to search for
*
* @return a possibly empty array containing Folders that matched the pattern
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized Folder[] list(String pattern) throws MessagingException {
// go filter the folders based on the pattern. The server does most of the
// heavy lifting on the pattern matching.
return filterFolders(pattern, false);
}
/**
* Return a list of folders to which the user is subscribed and which match the supplied pattern.
* If the store does not support the concept of subscription then this should match against
* all folders; the default implementation of this method achieves this by defaulting to the
* {@link #list(String)} method.
*
* @param pattern the pattern to search for
*
* @return a possibly empty array containing subscribed Folders that matched the pattern
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized Folder[] listSubscribed(String pattern) throws MessagingException {
// go filter the folders based on the pattern. The server does most of the
// heavy lifting on the pattern matching.
return filterFolders(pattern, true);
}
/**
* Return the character used by this folder's Store to separate path components.
*
* @return the name separater character
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized char getSeparator() throws MessagingException {
// not determined yet, we need to ask the server for the information
if (separator == UNDETERMINED) {
IMAPConnection connection = getConnection();
try {
List responses = connection.list("", fullname);
IMAPListResponse info = findListResponse(responses, fullname);
// if we didn't get any hits, then we just assume a reasonable default.
if (info == null) {
separator = '/';
}
else {
separator = info.separator;
// this can be omitted in the response, so assume a default
if (separator == '\0') {
separator = '/';
}
}
} finally {
releaseConnection(connection);
}
}
return separator;
}
/**
* Return whether this folder can hold just messages or also
* subfolders.
*
* @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending
* on the folder capabilities.
* @exception MessagingException
*/
public int getType() throws MessagingException {
// checking the validity will update the type information
// if it succeeds.
checkFolderValidity();
return folderType;
}
/**
* Create a new folder capable of containing subfolder and/or messages as
* determined by the type parameter. Any hierarchy defined by the folder
* name will be recursively created.
* If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
* is sent to all FolderListeners registered with this Folder or with the Store.
*
* @param newType the type, indicating if this folder should contain subfolders, messages or both
*
* @return true if the folder was sucessfully created
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized boolean create(int newType) throws MessagingException {
IMAPConnection connection = getConnection();
try {
// by default, just create using the fullname.
String newPath = fullname;
// if this folder is expected to only hold additional folders, we need to
// add a separator on to the end when we create this.
if ((newType & HOLDS_MESSAGES) == 0) {
newPath = fullname + separator;
}
try {
// go create this
connection.createMailbox(newPath);
// verify this exists...also updates some of the status
boolean reallyCreated = checkExistance(connection);
// broadcast a creation event.
notifyFolderListeners(FolderEvent.CREATED);
return reallyCreated;
} catch (MessagingException e) {
//TODO add folder level debug logging.
}
// we have a failure
return false;
} finally {
releaseConnection(connection);
}
}
/**
* Return the subscription status of this folder.
*
* @return true if the folder is marked as subscribed, false for
* unsubscribed.
*/
public synchronized boolean isSubscribed() {
try {
IMAPConnection connection = getConnection();
try {
// get the lsub response for this folder.
List responses = connection.listSubscribed("", fullname);
IMAPListResponse response = findListResponse(responses, fullname);
if (response == null) {
return false;
}
else {
// a NOSELECT flag response indicates the mailbox is no longer
// selectable, so it's also no longer subscribed to.
return !response.noselect;
}
} finally {
releaseConnection(connection);
}
} catch (MessagingException e) {
// Can't override to throw a MessagingException on this method, so
// just swallow any exceptions and assume false is the answer.
}
return false;
}
/**
* Set or clear the subscription status of a file.
*
* @param flag
* The new subscription state.
*/
public synchronized void setSubscribed(boolean flag) throws MessagingException {
IMAPConnection connection = getConnection();
try {
if (flag) {
connection.subscribe(fullname);
}
else {
connection.unsubscribe(fullname);
}
} finally {
releaseConnection(connection);
}
}
/**
* Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set.
* This can be used when the folder is closed to perform a light-weight check for new mail;
* to perform an incremental check for new mail the folder must be opened.
*
* @return true if the Store has recent messages
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized boolean hasNewMessages() throws MessagingException {
// the folder must exist for this to work.
checkFolderValidity();
// get the freshest status information.
refreshStatus(true);
// return the indicator from the message state.
return recentMessages > 0;
}
/**
* Get the Folder determined by the supplied name; if the name is relative
* then it is interpreted relative to this folder. This does not check that
* the named folder actually exists.
*
* @param name the name of the folder to return
* @return the named folder
* @throws MessagingException if there was a problem accessing the store
*/
public Folder getFolder(String name) throws MessagingException {
// this must be a real, valid folder to hold a subfolder
checkFolderValidity();
if (!holdsFolders()) {
throw new MessagingException("Folder " + fullname + " cannot hold subfolders");
}
// our separator does not get determined until we ping the server for it. We
// might need to do that now, so we need to use the getSeparator() method to retrieve this.
char separator = getSeparator();
return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
}
/**
* Delete this folder and possibly any subfolders. This operation can only be
* performed on a closed folder.
* If recurse is true, then all subfolders are deleted first, then any messages in
* this folder are removed and it is finally deleted; {@link FolderEvent#DELETED}
* events are sent as appropriate.
* If recurse is false, then the behaviour depends on the folder type and store
* implementation as followd:
*
* - If the folder can only conrain messages, then all messages are removed and
* then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.
* - If the folder can onlu contain subfolders, then if it is empty it will be
* deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not
* empty then the delete fails and this method returns false.
* - If the folder can contain both subfolders and messages, then if the folder
* does not contain any subfolders, any messages are deleted, the folder itself
* is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does
* contain subfolders then the implementation may choose from the following three
* behaviors:
*
* - it may return false indicting the operation failed
* - it may remove all messages within the folder, send a {@link FolderEvent#DELETED}
* event, and then return true to indicate the delete was performed. Note this does
* not delete the folder itself and the {@link #exists()} operation for this folder
* will return true
* - it may remove all messages within the folder as per the previous option; in
* addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing
* that messages may no longer be added
*
*
* FolderEvents are sent to all listeners registered with this folder or
* with the Store.
*
* @param recurse whether subfolders should be recursively deleted as well
* @return true if the delete operation succeeds
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized boolean delete(boolean recurse) throws MessagingException {
// we must be in the closed state.
checkClosed();
// if recursive, get the list of subfolders and delete them first.
if (recurse) {
Folder[] subfolders = list();
for (int i = 0; i < subfolders.length; i++) {
// this is a recursive delete also
subfolders[i].delete(true);
}
}
IMAPConnection connection = getConnection();
try {
// delete this one now.
connection.deleteMailbox(fullname);
// this folder no longer exists on the server.
listInfo = null;
// notify interested parties about the deletion.
notifyFolderListeners(FolderEvent.DELETED);
return true;
} catch (MessagingException e) {
// ignored
} finally {
releaseConnection(connection);
}
return false;
}
/**
* Rename this folder; the folder must be closed.
* If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to
* all listeners registered with this folder or with the store.
*
* @param newName the new name for this folder
* @return true if the rename succeeded
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized boolean renameTo(Folder f) throws MessagingException {
// we must be in the closed state.
checkClosed();
// but we must also exist
checkFolderValidity();
IMAPConnection connection = getConnection();
try {
// delete this one now.
connection.renameMailbox(fullname, f.getFullName());
// we renamed, so get a fresh set of status
refreshStatus(false);
// notify interested parties about the deletion.
notifyFolderRenamedListeners(f);
return true;
} catch (MessagingException e) {
// ignored
} finally {
releaseConnection(connection);
}
return false;
}
/**
* Open this folder; the folder must be able to contain messages and
* must currently be closed. If the folder is opened successfully then
* a {@link ConnectionEvent#OPENED} event is sent to listeners registered
* with this Folder.
*
* Whether the Store allows multiple connections or if it allows multiple
* writers is implementation defined.
*
* @param mode READ_ONLY or READ_WRITE
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized void open(int mode) throws MessagingException {
// we use a synchronized block rather than use a synchronized method so that we
// can notify the event listeners while not holding the lock.
synchronized(this) {
// can only be performed on a closed folder
checkClosed();
// ask the store to kindly hook us up with a connection.
// We're going to hang on to this until we're closed, so store it in
// the Folder field. We need to make sure our mailbox is selected while
// we're working things.
currentConnection = ((IMAPStore)store).getFolderConnection(this);
// we need to make ourselves a handler of unsolicited responses
currentConnection.addResponseHandler(this);
// record our open mode
this.mode = mode;
try {
// try to open, which gives us a lot of initial mailbox state.
IMAPMailboxStatus status = currentConnection.openMailbox(fullname, mode == Folder.READ_ONLY);
// not available in the requested mode?
if (status.mode != mode) {
// trying to open READ_WRITE and this isn't available?
if (mode == READ_WRITE) {
throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
}
}
// save this status and when we got it for later updating.
cachedStatus = status;
// mark when we got this
lastStatusTimeStamp = System.currentTimeMillis();
// now copy the status information over and flip over the open sign.
this.mode = status.mode;
maxSequenceNumber = status.messages;
recentMessages = status.recentMessages;
uidValidity = status.uidValidity;
uidNext = status.uidNext;
availableFlags = status.availableFlags;
permanentFlags = status.permanentFlags;
// create a our caches. These are empty initially
messageCache = new HashMap();
uidCache = new HashMap();
// we're open for business folks!
folderOpen = true;
notifyConnectionListeners(ConnectionEvent.OPENED);
} finally {
// NB: this doesn't really release this, but it does drive
// the processing of any unsolicited responses.
releaseConnection(currentConnection);
}
}
}
/**
* Close this folder; it must already be open.
* A @link ConnectionEvent#CLOSED} event is sent to all listeners registered
{*
* with this folder.
*
* @param expunge whether to expunge all deleted messages
* @throws MessagingException if there was a problem accessing the store; the folder is still closed
*/
public synchronized void close(boolean expunge) throws MessagingException {
// Can only be performed on an open folder
checkOpen();
cleanupFolder(expunge, false);
}
/**
* Do folder cleanup. This is used both for normal
* close operations, and adnormal closes where the
* server has sent us a BYE message.
*
* @param expunge Indicates whether open messages should be expunged.
* @param disconnected
* The disconnected flag. If true, the server has cut
* us off, which means our connection can not be returned
* to the connection pool.
*
* @exception MessagingException
*/
protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
folderOpen = false;
uidCache = null;
messageCache = null;
// if we have a connection active at the moment
if (currentConnection != null) {
// was this a forced disconnect by the server?
if (disconnected) {
currentConnection.setClosed();
}
else {
// The CLOSE operation depends on what mode was used to select the mailbox.
// If we're open in READ-WRITE mode, we used a SELECT operation. When CLOSE
// is issued, any deleted messages will be expunged. If we've been asked not
// to expunge the messages, we have a problem. The solution is to reselect the
// mailbox using EXAMINE, which will not expunge messages when closed.
if (mode == READ_WRITE && !expunge) {
// we can ignore the result...we're just switching modes.
currentConnection.openMailbox(fullname, true);
}
// have this close the selected mailbox
currentConnection.closeMailbox();
}
currentConnection.removeResponseHandler(this);
// we need to release the connection to the Store once we're closed
((IMAPStore)store).releaseFolderConnection(this, currentConnection);
currentConnection = null;
}
notifyConnectionListeners(ConnectionEvent.CLOSED);
}
/**
* Tests the open status of the folder.
*
* @return true if the folder is open, false otherwise.
*/
public boolean isOpen() {
return folderOpen;
}
/**
* Get the permanentFlags
*
* @return The set of permanent flags we support (only SEEN).
*/
public synchronized Flags getPermanentFlags() {
if (permanentFlags != null) {
// we need a copy of our master set.
return new Flags(permanentFlags);
}
else {
// a null return is expected if not there.
return null;
}
}
/**
* Return the number of messages this folder contains.
* If this operation is invoked on a closed folder, the implementation
* may choose to return -1 to avoid the expense of opening the folder.
*
* @return the number of messages, or -1 if unknown
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized int getMessageCount() throws MessagingException {
checkFolderValidity();
// if we haven't opened the folder yet, we might not have good status information.
// go request some, which updates the folder fields also.
refreshStatus(false);
return maxSequenceNumber;
}
/**
* Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set.
* If this operation is invoked on a closed folder, the implementation
* may choose to return -1 to avoid the expense of opening the folder.
* The default implmentation of this method iterates over all messages
* in the folder; subclasses should override if possible to provide a more
* efficient implementation.
*
* NB: This is an override of the default Folder implementation, which
* examines each of the messages in the folder. IMAP has more efficient
* mechanisms for grabbing the information.
*
* @return the number of new messages, or -1 if unknown
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized int getNewMessageCount() throws MessagingException {
// the folder must be a real one for this to work.
checkFolderValidity();
// now get current status from the folder
refreshStatus(false);
// this should be current now.
return recentMessages;
}
/**
* Return the number of messages in this folder that do not have the {@link Flag.SEEN} flag set.
* If this operation is invoked on a closed folder, the implementation
* may choose to return -1 to avoid the expense of opening the folder.
* The default implmentation of this method iterates over all messages
* in the folder; subclasses should override if possible to provide a more
* efficient implementation.
*
* NB: This is an override of the default Folder implementation, which
* examines each of the messages in the folder. IMAP has more efficient
* mechanisms for grabbing the information.
*
* @return the number of new messages, or -1 if unknown
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized int getUnreadMessageCount() throws MessagingException {
checkFolderValidity();
// if we haven't opened the folder yet, we might not have good status information.
// go request some, which updates the folder fields also.
if (!folderOpen) {
refreshStatus(false);
}
else {
// if we have an open connection, then search the folder for any messages
// marked UNSEEN.
// UNSEEN is a false test on SEEN using the search criteria.
SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// search using the connection directly rather than calling our search() method so we don't
// need to instantiate each of the matched messages. We're really only interested in the count
// right now.
int[] matches = connection.searchMailbox(criteria);
// update the unseen count.
unseenMessages = matches == null ? 0 : matches.length;
} finally {
releaseConnection(connection);
}
}
// return our current message count.
return unseenMessages;
}
/**
* Return the number of messages in this folder that have the {@link Flag.DELETED} flag set.
* If this operation is invoked on a closed folder, the implementation
* may choose to return -1 to avoid the expense of opening the folder.
* The default implmentation of this method iterates over all messages
* in the folder; subclasses should override if possible to provide a more
* efficient implementation.
*
* @return the number of new messages, or -1 if unknown
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized int getDeletedMessageCount() throws MessagingException {
checkFolderValidity();
// if we haven't opened the folder yet, we might not have good status information.
// go request some, which updates the folder fields also.
if (!folderOpen) {
// the status update doesn't return deleted messages. These can only be obtained by
// searching an open folder. Just return a bail-out response
return -1;
}
else {
// if we have an open connection, then search the folder for any messages
// marked DELETED.
// UNSEEN is a false test on SEEN using the search criteria.
SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// search using the connection directly rather than calling our search() method so we don't
// need to instantiate each of the matched messages. We're really only interested in the count
// right now.
int[] matches = connection.searchMailbox(criteria);
return matches == null ? 0 : matches.length;
} finally {
releaseConnection(connection);
}
}
}
/**
* Retrieve the message with the specified index in this Folder;
* messages indices start at 1 not zero.
* Clients should note that the index for a specific message may change
* if the folder is expunged; {@link Message} objects should be used as
* references instead.
*
* @param msgNum The message sequence number of the target message.
*
* @return the message
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized Message getMessage(int msgNum) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
// Check the validity of the message number. This may require pinging the server to
// see if there are new messages in the folder.
checkMessageValidity(msgNum);
// create the mapping key for this
Integer messageKey = new Integer(msgNum);
// ok, if the message number is within range, we should have this in the
// messages list. Just return the element.
Message message = (Message)messageCache.get(messageKey);
// if not in the cache, create a dummy add it in. The message body will be
// retrieved on demand
if (message == null) {
message = new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, msgNum);
messageCache.put(messageKey, message);
}
return message;
}
/**
* Retrieve a range of messages for this folder.
* messages indices start at 1 not zero.
*
* @param start Index of the first message to fetch, inclusive.
* @param end Index of the last message to fetch, inclusive.
*
* @return An array of the fetched messages.
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized Message[] getMessages(int start, int end) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
Message[] messageRange = new Message[end - start + 1];
for (int i = 0; i < messageRange.length; i++) {
// NB: getMessage() requires values that are origin 1, so there's
// no need to adjust the value by other than the start position.
messageRange[i] = getMessage(start + i);
}
return messageRange;
}
/**
* Append the supplied messages to this folder. A {@link MessageCountEvent} is sent
* to all listeners registered with this folder when all messages have been appended.
* If the array contains a previously expunged message, it must be re-appended to the Store
* and implementations must not abort this operation.
*
* @param msgs The array of messages to append to the folder.
*
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized void appendMessages(Message[] msgs) throws MessagingException {
checkFolderValidity();
for (int i = 0; i < msgs.length; i++) {
Message msg = msgs[i];
appendMessage(msg);
}
}
/**
* Hint to the store to prefetch information on the supplied messages.
* Subclasses should override this method to provide an efficient implementation;
* the default implementation in this class simply returns.
*
* @param messages messages for which information should be fetched
* @param profile the information to fetch
* @throws MessagingException if there was a problem accessing the store
* @see FetchProfile
*/
public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {
// we might already have the information being requested, so ask each of the
// messages in the list to evaluate itself against the profile. We'll only ask
// the server to send information that's required.
List fetchSet = new ArrayList();
for (int i = 0; i < messages.length; i++) {
Message msg = messages[i];
// the message is missing some of the information still. Keep this in the list.
// even if the message is only missing one piece of information, we still fetch everything.
if (((IMAPMessage)msg).evaluateFetch(profile)) {
fetchSet.add(msg);
}
}
// we've got everything already, no sense bothering the server
if (fetchSet.isEmpty()) {
return;
}
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// ok, from this point onward, we don't want any threads messing with the
// message cache. A single processed EXPUNGE could make for a very bad day
synchronized(this) {
// get the message set for this
String messageSet = generateMessageSet(fetchSet);
// fetch all of the responses
List responses = connection.fetch(messageSet, profile);
// IMPORTANT: We must do our updates while synchronized to keep the
// cache from getting updated underneath us. This includes
// not releasing the connection until we're done to delay processing any
// pending expunge responses.
for (int i = 0; i < responses.size(); i++) {
IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
Message msg = getMessage(response.getSequenceNumber());
// Belt and Braces. This should never be false.
if (msg != null) {
// have the message apply this to itself.
((IMAPMessage)msg).updateMessageInformation(response);
}
}
}
} finally {
releaseConnection(connection);
}
return;
}
/**
* Set flags on the messages to the supplied value; all messages must belong to this folder.
* This method may be overridden by subclasses that can optimize the setting
* of flags on multiple messages at once; the default implementation simply calls
* {@link Message#setFlags(Flags, boolean)} for each supplied messages.
*
* @param messages whose flags should be set
* @param flags the set of flags to modify
* @param set Indicates whether the flags should be set or cleared.
*
* @throws MessagingException
* if there was a problem accessing the store
*/
public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
// this is a list of messages for the change broadcast after the update
List updatedMessages = new ArrayList();
synchronized(this) {
// the folder must be open and writeable.
checkOpenReadWrite();
// now make sure these are settable flags.
if (!availableFlags.contains(flags))
{
throw new MessagingException("The IMAP server does not support changing of this flag set");
}
// turn this into a set of message numbers
String messageSet = generateMessageSet(messages);
// if all of the messages have been expunged, nothing to do.
if (messageSet == null) {
return;
}
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// and have the connection set this
List responses = connection.setFlags(messageSet, flags, set);
// retrieve each of the messages from our cache, and do the flag update.
// we need to keep the list so we can broadcast a change update event
// when we're finished.
for (int i = 0; i < responses.size(); i++) {
IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
// get the updated message and update the internal state.
Message message = getMessage(response.sequenceNumber);
// this shouldn't happen, but it might have been expunged too.
if (message != null) {
((IMAPMessage)message).updateMessageInformation(response);
updatedMessages.add(message);
}
}
} finally {
releaseConnection(connection);
}
}
// ok, we're no longer holding the lock. Now go broadcast the update for each
// of the affected messages.
for (int i = 0; i < updatedMessages.size(); i++) {
Message message = (Message)updatedMessages.get(i);
notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
}
}
/**
* Set flags on a range of messages to the supplied value.
* This method may be overridden by subclasses that can optimize the setting
* of flags on multiple messages at once; the default implementation simply
* gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
*
* @param start first message end set
* @param end last message end set
* @param flags the set of flags end modify
* @param value Indicates whether the flags should be set or cleared.
*
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
Message[] msgs = new Message[end - start + 1];
for (int i = start; i <= end; i++) {
msgs[i] = getMessage(i);
}
// go do a bulk set operation on these messages
setFlags(msgs, flags, value);
}
/**
* Set flags on a set of messages to the supplied value.
* This method may be overridden by subclasses that can optimize the setting
* of flags on multiple messages at once; the default implementation simply
* gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
*
* @param ids the indexes of the messages to set
* @param flags the set of flags end modify
* @param value Indicates whether the flags should be set or cleared.
*
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
Message[] msgs = new Message[ids.length];
for (int i = 0; i 0) {
notifyMessageRemovedListeners(true, messages);
}
// note, we're expected to return an array in all cases, even if the expunged count was zero.
return messages;
}
/**
* Search the supplied messages for those that match the supplied criteria;
* messages must belong to this folder.
* The default implementation iterates through the messages, returning those
* whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
* subclasses may provide a more efficient implementation.
*
* @param term the search criteria
* @param messages the messages to search
* @return an array containing messages that match the criteria
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized Message[] search(SearchTerm term) throws MessagingException {
// only allowed on open folders
checkOpen();
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// just search everything
int[] messageNumbers = connection.searchMailbox(term);
return resolveMessages(messageNumbers);
} finally {
releaseConnection(connection);
}
}
/**
* Search the supplied messages for those that match the supplied criteria;
* messages must belong to this folder.
* The default implementation iterates through the messages, returning those
* whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
* subclasses may provide a more efficient implementation.
*
* @param term the search criteria
* @param messages the messages to search
* @return an array containing messages that match the criteria
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
// only allowed on open folders
checkOpen();
// turn this into a string specifier for these messages. We'll weed out the expunged messages first.
String messageSet = generateMessageSet(messages);
// If we have no "live" messages to search, just return now. We're required to return a non-null
// value, so give an empy array back.
if (messageSet == null) {
return new Message[0];
}
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// now go do the search.
int[] messageNumbers = connection.searchMailbox(messageSet, term);
return resolveMessages(messageNumbers);
} finally {
releaseConnection(connection);
}
}
/**
* Get the UID validity value for this Folder.
*
* @return The current UID validity value, as a long.
* @exception MessagingException
*/
public synchronized long getUIDValidity() throws MessagingException
{
// get the latest status to make sure we have the
// most current.
refreshStatus(true);
return uidValidity;
}
/**
* Retrieve a message using the UID rather than the
* message sequence number. Returns null if the message
* doesn't exist.
*
* @param uid The target UID.
*
* @return the Message object. Returns null if the message does
* not exist.
* @exception MessagingException
*/
public synchronized Message getMessageByUID(long uid) throws MessagingException
{
// only allowed on open folders
checkOpen();
Long key = new Long(uid);
// first check to see if we have a cached value for this
synchronized(messageCache) {
Message msg = (Message)uidCache.get(key);
if (msg != null) {
return msg;
}
}
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// locate the message identifier
IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
// if nothing is returned, the message doesn't exist
if (imapuid == null) {
return null;
}
// retrieve the actual message object and place this in the UID cache
return retrieveMessageByUid(key, imapuid.messageNumber);
} finally {
releaseConnection(connection);
}
}
/**
* Get a series of messages using a UID range. The
* special value LASTUID can be used to mark the
* last available message.
*
* @param start The start of the UID range.
* @param end The end of the UID range. The special value
* LASTUID can be used to request all messages up
* to the last UID.
*
* @return An array containing all of the messages in the
* range.
* @exception MessagingException
*/
public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException
{
// only allowed on open folders
checkOpen();
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// locate the message identifier
List uids = connection.getSequenceNumbersForUids(start, end);
Message[] msgs = new Message[uids.size()];
// fill in each of the messages based on the returned value
for (int i = 0; i < msgs.length; i++) {
IMAPUid uid = (IMAPUid)uids.get(i);
msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
}
return msgs;
} finally {
releaseConnection(connection);
}
}
/**
* Retrieve a set of messages by explicit UIDs. If
* any message in the list does not exist, null
* will be returned for the corresponding item.
*
* @param ids An array of UID values to be retrieved.
*
* @return An array of Message items the same size as the ids
* argument array. This array will contain null
* entries for any UIDs that do not exist.
* @exception MessagingException
*/
public synchronized Message[] getMessagesByUID(long[] ids) throws MessagingException
{
// only allowed on open folders
checkOpen();
Message[] msgs = new Message[ids.length];
for (int i = 0; i < msgs.length; i++) {
msgs[i] = getMessageByUID(ids[i]);
}
return msgs;
}
/**
* Retrieve the UID for a message from this Folder.
* The argument Message MUST belong to this Folder
* instance, otherwise a NoSuchElementException will
* be thrown.
*
* @param message The target message.
*
* @return The UID associated with this message.
* @exception MessagingException
*/
public synchronized long getUID(Message message) throws MessagingException
{
// verify this actually is in this folder.
checkMessageFolder(message);
IMAPMessage msg = (IMAPMessage)message;
// we might already know this bit of information
if (msg.getUID() != -1) {
return msg.getUID();
}
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// locate the message identifier
IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
// if nothing is returned, the message doesn't exist
if (imapuid == null) {
return -1;
}
// cache this information now that we've gotten it.
addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
// return the UID information.
return imapuid.uid;
} finally {
releaseConnection(connection);
}
}
/**
* Retrieve a message from a UID/message mapping.
*
* @param key The UID key used for the mapping.
* @param msgNumber The message sequence number.
*
* @return The Message object corresponding to the message.
* @exception MessagingException
*/
protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
{
synchronized (messageCache) {
// first check the cache...this might have already been added.
Message msg = (Message)uidCache.get(key);
if (msg != null) {
return msg;
}
// retrieve the message by sequence number
msg = getMessage(msgNumber);
// add this to our UID mapping cache.
addToUidCache(key, msg);
return msg;
}
}
/**
* Add a message to the UID mapping cache, ensuring that
* the UID value is updated.
*
* @param key The UID key.
* @param msg The message to add.
*/
protected void addToUidCache(Long key, Message msg) {
synchronized (messageCache) {
((IMAPMessage)msg).setUID(key.longValue());
uidCache.put(key, msg);
}
}
/**
* Append a single message to the IMAP Folder.
*
* @param msg The message to append.
*
* @exception MessagingException
*/
protected synchronized void appendMessage(Message msg) throws MessagingException
{
// sort out the dates. If no received date, use the sent date.
Date date = msg.getReceivedDate();
if (date == null) {
date = msg.getSentDate();
}
Flags flags = msg.getFlags();
// convert the message into an array of bytes we can attach as a literal.
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
msg.writeTo(out);
} catch (IOException e) {
}
// now issue the append command
IMAPConnection connection = getConnection();
try {
connection.appendMessage(getFullName(), date, flags, out.toByteArray());
} finally {
releaseConnection(connection);
}
}
/**
* Retrieve the list of matching groups from the IMAP server using the LIST
* or LSUB command. The server does the wildcard matching for us.
*
* @param pattern
* The pattern string (in wildmat format) used to match.
*
* @return An array of folders for the matching groups.
*/
protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
IMAPConnection connection = getConnection();
// this is used to filter out our own folder from the search
String root = fullname + getSeparator();
List responses = null;
try {
if (subscribed) {
// get the lsub response for this folder.
responses = connection.listSubscribed(root, pattern);
}
else {
// grab using the LIST command.
responses = connection.list(root, pattern);
}
} finally {
releaseConnection(connection);
}
List folders = new ArrayList();
for (int i = 0; i < responses.size(); i++) {
IMAPListResponse response = (IMAPListResponse)responses.get(i);
// if a full wildcard is specified, the root folder can be returned too. Make sure we
// filter that one out.
if (!response.mailboxName.equals(root)) {
IMAPFolder folder = new IMAPFolder((IMAPStore)store, response.mailboxName, response.separator);
folders.add(folder);
}
}
// convert into an array and return
return (Folder[])folders.toArray(new Folder[folders.size()]);
}
/**
* Test if a folder can hold sub folders.
*
* @return True if the folder is allowed to have subfolders.
*/
protected synchronized boolean holdsFolders() throws MessagingException {
checkFolderValidity();
return (folderType & HOLDS_FOLDERS) != 0;
}
/**
* Validate that a target message number is considered valid
* by the IMAP server. If outside of the range we currently
* are a ware of, we'll ping the IMAP server to see if there
* have been any updates.
*
* @param messageNumber
* The message number we're checking.
*
* @exception MessagingException
*/
protected void checkMessageValidity(int messageNumber) throws MessagingException {
// lower range for a message is 1.
if (messageNumber < 1) {
throw new MessagingException("Invalid message number for IMAP folder: " + messageNumber);
}
// if within our current known range, we'll accept this
if (messageNumber <= maxSequenceNumber) {
return;
}
IMAPConnection connection = getConnection();
synchronized (this) {
try {
// ping the server to see if there's any updates to process. The updates are handled
// by the response handlers.
connection.updateMailboxStatus();
} finally {
releaseConnection(connection);
}
}
// still out of range?
if (messageNumber > maxSequenceNumber) {
throw new MessagingException("Message " + messageNumber + " does not exist on server");
}
}
/**
* Below is a list of convenience methods that avoid repeated checking for a
* value and throwing an exception
*/
/**
* Ensure the folder is open. Throws a MessagingException
* if not in the correct state for the operation.
*
* @exception IllegalStateException
*/
protected void checkOpen() throws IllegalStateException {
if (!folderOpen){
throw new IllegalStateException("Folder is not Open");
}
}
/**
* Ensure the folder is not open for operations
* that require the folder to be closed.
*
* @exception IllegalStateException
*/
protected void checkClosed() throws IllegalStateException {
if (folderOpen){
throw new IllegalStateException("Folder is Open");
}
}
/**
* Ensure that the folder is open for read/write mode before doing
* an operation that would make a change.
*
* @exception IllegalStateException
*/
protected void checkReadWrite() throws IllegalStateException {
if (mode != READ_WRITE) {
throw new IllegalStateException("Folder is opened READY_ONLY");
}
}
/**
* Check that the folder is open and in read/write mode.
*
* @exception IllegalStateException
*/
protected void checkOpenReadWrite() throws IllegalStateException {
checkOpen();
checkReadWrite();
}
/**
* Notify the message changed listeners that a
* message contained in the folder has been updated.
*
* @param type The type of update made to the message.
* @param m The message that was updated.
*
* @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message)
*/
public void notifyMessageChangedListeners(int type, Message m) {
super.notifyMessageChangedListeners(type, m);
}
/**
* Retrieve the connection attached to this folder. Throws an
* exception if we don't have an active connection.
*
* @return The current connection object.
* @exception MessagingException
*/
protected synchronized IMAPConnection getConnection() throws MessagingException {
// don't have an open connection yet? Just request a pool connection.
if (currentConnection == null) {
// request a connection from the central store.
IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);
// we need to make ourselves a handler of unsolicited responses
connection.addResponseHandler(this);
return connection;
}
// we have a connection for our use. Just return it.
return currentConnection;
}
/**
* Release our connection back to the Store.
*
* @param connection The connection to release.
*
* @exception MessagingException
*/
protected void releaseConnection(IMAPConnection connection) throws MessagingException {
// This is a bit of a pain. We need to delay processing of the
// unsolicited responses until after each user of the connection has
// finished processing the expected responses. We need to do this because
// the unsolicited responses may include EXPUNGED messages. The EXPUNGED
// messages will alter the message sequence numbers for the messages in the
// cache. Processing the EXPUNGED messages too early will result in
// updates getting applied to the wrong message instances. So, as a result,
// we delay that stage of the processing until all expected responses have
// been handled.
// process any pending messages before returning.
connection.processPendingResponses();
// if no cached connection or this is somehow different from the cached one, just
// return it.
if (currentConnection == null || connection != currentConnection) {
connection.removeResponseHandler(this);
((IMAPStore)store).releaseFolderConnection(this, connection);
}
// if we're open, then we don't have to worry about returning this connection
// to the Store. This is set up perfectly for our use right now.
}
/**
* Obtain a connection object for a Message attached to this Folder. This
* will be the Folder's connection, which is only available if the Folder
* is currently open.
*
* @return The connection object for the Message instance to use.
* @exception MessagingException
*/
synchronized IMAPConnection getMessageConnection() throws MessagingException {
// if we're not open, the messages can't communicate either
if (currentConnection == null) {
throw new FolderClosedException(this, "No Folder connections available");
}
// return the current Folder connection. At this point, we'll be sharing the
// connection between the Folder and the Message (and potentially, other messages). The
// command operations on the connection are synchronized so only a single command can be
// issued at one time.
return currentConnection;
}
/**
* Release the connection object back to the Folder instance.
*
* @param connection The connection being released.
*
* @exception MessagingException
*/
void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
// release it back to ourselves...this will drive unsolicited message processing.
releaseConnection(connection);
}
/**
* Refresh the status information on this folder.
*
* @param force Force a status refresh always.
*
* @exception MessagingException
*/
protected void refreshStatus(boolean force) throws MessagingException {
// first check that any cached status we've received has gotten a little moldy.
if (cachedStatus != null) {
// if not forcing, check the time out.
if (!force) {
if (statusCacheTimeout > 0) {
long age = System.currentTimeMillis() - lastStatusTimeStamp;
if (age < statusCacheTimeout) {
return;
}
}
}
// make sure the stale information is cleared out.
cachedStatus = null;
}
IMAPConnection connection = getConnection();
try {
// ping the server for the list information for this folder
cachedStatus = connection.getMailboxStatus(fullname);
// mark when we got this
lastStatusTimeStamp = System.currentTimeMillis();
} finally {
releaseConnection(connection);
}
// refresh the internal state from the message information
maxSequenceNumber = cachedStatus.messages;
recentMessages = cachedStatus.recentMessages;
unseenMessages = cachedStatus.unseenMessages;
uidValidity = cachedStatus.uidValidity;
}
/**
* Process an EXPUNGE response for a message, removing the
* message from the message cache.
*
* @param sequenceNumber
* The sequence number for the expunged message.
*
* @return The Message object corresponding to this expunged
* message.
* @exception MessagingException
*/
protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {
// first process the expunged message. We need to return a Message instance, so
// force this to be added to the cache
IMAPMessage expungedMessage = (IMAPMessage)getMessage(sequenceNumber);
// mark the message as expunged.
expungedMessage.setExpunged(true);
// have we retrieved a UID for this message? If we have, then it's in the UID cache and
// needs removal from there also
long uid = ((IMAPMessage)expungedMessage).getUID();
if (uid >= 0) {
uidCache.remove(new Long(uid));
}
// because we need to jigger the keys of some of these, we had better have a working
// copy.
Map newCache = new HashMap();
// now process each message in the cache, making adjustments as necessary
Iterator i = messageCache.keySet().iterator();
while (i.hasNext()) {
Integer key = (Integer)i.next();
int index = key.intValue();
// if before the expunged message, just copy over to the
// new cache
if (index < sequenceNumber) {
newCache.put(key, messageCache.get(key));
}
// after the expunged message...we need to adjust this
else if (index > sequenceNumber) {
// retrieve the message using the current position,
// adjust the message sequence number, and add to the new
// message cache under the new key value
IMAPMessage message = (IMAPMessage)messageCache.get(key);
message.setSequenceNumber(index - 1);
newCache.put(new Integer(index - 1), message);
}
else {
// the expunged message. We don't move this over to the new
// cache, and we've already done all processing of that message that's
// required
}
}
// replace the old cache now that everything has been adjusted
messageCache = newCache;
// adjust the message count downward
maxSequenceNumber--;
return expungedMessage;
}
/**
* Resolve an array of message numbers into an array of the
* referenced messages.
*
* @param messageNumbers
* The array of message numbers (can be null).
*
* @return An array of Message[] containing the resolved messages from
* the list. Returns a zero-length array if there are no
* messages to resolve.
* @exception MessagingException
*/
protected Message[] resolveMessages(int[] messageNumbers) throws MessagingException {
// the connection search returns a null pointer if nothing was found, just convert this into a
// null array.
if (messageNumbers == null) {
return new Message[0];
}
Message[] messages = new Message[messageNumbers.length];
// retrieve each of the message numbers in turn.
for (int i = 0; i < messageNumbers.length; i++) {
messages[i] = getMessage(messageNumbers[i]);
}
return messages;
}
/**
* Generate a message set string from a List of messages rather than an
* array.
*
* @param messages The List of messages.
*
* @return The evaluated message set string.
* @exception MessagingException
*/
protected String generateMessageSet(List messages) throws MessagingException {
Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]);
return generateMessageSet(msgs);
}
/**
* Take an array of messages and generate a String
* argument as specified by RFC 2060. The message set argument
* is a comma-separated list of message number ranges. A
* single element range is just one number. A longer range is
* a pair of numbers separated by a ":". The generated string
* should not have any blanks. This will attempt to locate
* consequetive ranges of message numbers, but will only do this
* for messages that are already ordered in the array (i.e., we
* don't try to sort). Expunged messages are excluded from the
* search, since they don't exist anymore. A valid search string
* will look something like this:
*
* "3,6:10,15,21:35"
*
* @param messages The array of messages we generate from.
*
* @return A string formatted version of these message identifiers that
* can be used on an IMAP command.
*/
protected String generateMessageSet(Message[] messages) throws MessagingException {
StringBuffer set = new StringBuffer();
for (int i = 0; i < messages.length; i++) {
// first scan the list looking for a "live" message.
IMAPMessage start = (IMAPMessage)messages[i];
if (!start.isExpunged()) {
// we can go ahead and add this to the list now. If we find this is the start of a
// range, we'll tack on the ":end" bit once we find the last message in the range.
if (set.length() != 0) {
// only append the comma if not the first element of the list
set.append(',');
}
// append the first number. NOTE: We append this directly rather than
// use appendInteger(), which appends it using atom rules.
set.append(Integer.toString(start.getSequenceNumber()));
// ok, we have a live one. Now scan the list from here looking for the end of
// a range of consequetive messages.
int endIndex = -1; ;
// get the number we're checking against.
int previousSequence = start.getSequenceNumber();
for (int j = i + 1; j < messages.length; j++) {
IMAPMessage message = (IMAPMessage)messages[j];
if (!message.isExpunged()) {
// still consequetive?
if (message.getSequenceNumber() == previousSequence + 1) {
// step this for the next check.
previousSequence++;
// record this as the current end of the range.
endIndex = j;
}
else {
// found a non-consequetive one, stop here
break;
}
}
}
// have a range end point? Add the range specifier and step the loop index point
// to skip over this
if (endIndex != -1) {
// pick up the scan at the next location
i = endIndex;
set.append(':');
set.append(Integer.toString(((IMAPMessage)messages[endIndex]).getSequenceNumber()));
}
}
}
// return null for an empty list. This is possible because either an empty array has been handed to
// us or all of the messages in the array have been expunged.
if (set.length() == 0) {
return null;
}
return set.toString();
}
/**
* Verify that this folder exists on the server before
* performning an operation that requires a valid
* Folder instance.
*
* @exception MessagingException
*/
protected void checkFolderValidity() throws MessagingException {
// if we are holding a current listinfo response, then
// we have chached existance information. In that case,
// all of our status is presumed up-to-date and we can go
// with that. If we don't have the information, then we
// ping the server for it.
if (listInfo == null && !exists()) {
throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server");
}
}
/**
* Check if a Message is properly within the target
* folder.
*
* @param msg The message we're checking.
*
* @exception MessagingException
*/
protected void checkMessageFolder(Message msg) throws MessagingException {
if (msg.getFolder() != this) {
throw new NoSuchElementException("Message is not within the target Folder");
}
}
/**
* Search a list of LIST responses for one containing information
* for a particular mailbox name.
*
* @param responses The list of responses.
* @param name The desired mailbox name.
*
* @return The IMAPListResponse information for the requested name.
*/
protected IMAPListResponse findListResponse(List responses, String name) {
for (int i = 0; i < responses.size(); i++) {
IMAPListResponse response = (IMAPListResponse)responses.get(i);
if (response.mailboxName.equals(name)) {
return response;
}
}
return null;
}
/**
* Protected class intended for subclass overrides. For normal folders,
* the mailbox name is fullname. For Namespace root folders, the mailbox
* name is the prefix + separator.
*
* @return The string name to use as the mailbox name for exists() and issubscribed()
* calls.
*/
protected String getMailBoxName() {
return fullname;
}
/**
* Handle an unsolicited response from the server. Most unsolicited responses
* are replies to specific commands sent to the server. The remainder must
* be handled by the Store or the Folder using the connection. These are
* critical to handle, as events such as expunged messages will alter the
* sequence numbers of the live messages. We need to keep things in sync.
*
* @param response The UntaggedResponse to process.
*
* @return true if we handled this response and no further handling is required. false
* means this one wasn't one of ours.
*/
public boolean handleResponse(IMAPUntaggedResponse response) {
// "you've got mail". The message count has been updated. There
// are two posibilities. Either there really are new messages, or
// this is an update following an expunge. If there are new messages,
// we need to update the message cache and broadcast the change to
// any listeners.
if (response.isKeyword("EXISTS")) {
// we need to update our cache, and also retrieve the new messages and
// send them out in a broadcast update.
int oldCount = maxSequenceNumber;
maxSequenceNumber = ((IMAPSizeResponse)response).getSize();
// has the size grown? We have to send the "you've got mail" announcement.
if (oldCount < maxSequenceNumber) {
try {
Message[] messages = getMessages(oldCount + 1, maxSequenceNumber);
notifyMessageAddedListeners(messages);
} catch (MessagingException e) {
// should never happen in this context
}
}
return true;
}
// "you had mail". A message was expunged from the server. This MUST
// be processed immediately, as any subsequent expunge messages will
// shift the message numbers as a result of previous messages getting
// removed. We need to keep our internal cache in sync with the server.
else if (response.isKeyword("EXPUNGE")) {
int messageNumber = ((IMAPSizeResponse)response).getSize();
try {
Message message = expungeMessage(messageNumber);
// broadcast the message update.
notifyMessageRemovedListeners(false, new Message[] {message});
} catch (MessagingException e) {
}
// we handled this one.
return true;
}
// just an update of recently arrived stuff? Just update the field.
else if (response.isKeyword("RECENT")) {
recentMessages = ((IMAPSizeResponse)response).getSize();
return true;
}
// The spec is not particularly clear what types of unsolicited
// FETCH response can be sent. The only one that is specifically
// spelled out is flag updates. If this is one of those, then
// handle it.
else if (response.isKeyword("FETCH")) {
IMAPFetchResponse fetch = (IMAPFetchResponse)response;
IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS);
// if this is a flags response, get the message and update
if (flags != null) {
try {
// get the updated message and update the internal state.
IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber);
// this shouldn't happen, but it might have been expunged too.
if (message != null) {
message.updateMessageInformation(fetch);
}
notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
} catch (MessagingException e) {
}
return true;
}
}
// this is a BYE response on our connection. This forces us to close, but
// when we return the connection, the pool needs to get rid of it.
else if (response.isKeyword("BYE")) {
// this is essentially a close event. We need to clean everything up
// and make sure our connection is not returned to the general pool.
try {
cleanupFolder(false, true);
} catch (MessagingException e) {
}
return true;
}
// not a response the folder knows how to deal with.
return false;
}
// The following set of methods are extensions that exist in the Sun implementation. They
// match the Sun version in intent, but are not 100% compatible because the Sun implementation
// uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.
/**
* Remove an entry from the access control list for this folder.
*
* @param acl The ACL element to remove.
*
* @exception MessagingException
*/
public synchronized void removeACL(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
connection.removeACLRights(fullname, acl);
} finally {
releaseConnection(connection);
}
}
/**
* Add an entry to the access control list for this folder.
*
* @param acl The new ACL to add.
*/
public synchronized void addACL(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
connection.setACLRights(fullname, acl);
} finally {
releaseConnection(connection);
}
}
/**
* Add Rights to a given ACL entry.
*
* @param acl The target ACL to update.
*
* @exception MessagingException
*/
public synchronized void addRights(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
connection.addACLRights(fullname, acl);
} finally {
releaseConnection(connection);
}
}
/**
* Remove ACL Rights from a folder.
*
* @param acl The ACL describing the Rights to remove.
*
* @exception MessagingException
*/
public synchronized void removeRights(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
connection.removeACLRights(fullname, acl);
} finally {
releaseConnection(connection);
}
}
/**
* List the rights associated with a given name.
*
* @param name The user name for the Rights.
*
* @return The set of Rights associated with the user name.
* @exception MessagingException
*/
public synchronized Rights[] listRights(String name) throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
return connection.listACLRights(fullname, name);
} finally {
releaseConnection(connection);
}
}
/**
* List the rights for the currently authenticated user.
*
* @return The set of Rights for the current user.
* @exception MessagingException
*/
public synchronized Rights myRights() throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
return connection.getMyRights(fullname);
} finally {
releaseConnection(connection);
}
}
/**
* Get the quota values assigned to the current folder.
*
* @return The Quota information for the folder.
* @exception MessagingException
*/
public synchronized Quota[] getQuota() throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
return connection.fetchQuotaRoot(fullname);
} finally {
releaseConnection(connection);
}
}
/**
* Set the quota value for a quota root
*
* @param quota The new quota information to set.
*
* @exception MessagingException
*/
public synchronized void setQuota(Quota quota) throws MessagingException {
// ask the store to kindly hook us up with a connection.
IMAPConnection connection = getConnection();
try {
// the connection does the heavy lifting
connection.setQuota(quota);
} finally {
releaseConnection(connection);
}
}
/**
* Get the set of attributes defined for the folder
* as the set of capabilities returned when the folder
* was opened.
*
* @return The set of attributes associated with the folder.
* @exception MessagingException
*/
public synchronized String[] getAttributes() throws MessagingException {
// if we don't have the LIST command information for this folder yet,
// call exists() to force this to be updated so we can return.
if (listInfo == null) {
// return a null reference if this is not valid.
if (!exists()) {
return null;
}
}
// return a copy of the attributes array.
return (String[])listInfo.attributes.clone();
}
}
././@LongLink 0000000 0000000 0000000 00000000164 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttachedMessage.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttac0000664 0001750 0001750 00000010367 10716317503 032017 0 ustar brian brian /**
* 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;
import javax.activation.DataHandler;
import javax.mail.Flags;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
/**
* A nested message attachement inside of another
* IMAP message. This is a less-functional version
* of the top-level message.
*/
public class IMAPAttachedMessage extends IMAPMessage {
// the parent enclosing message.
protected IMAPMessage parent;
/**
* Constructor for an attached message part.
*
* @param parent The parent message (outer-most message).
* @param section The section identifier for this embedded part
* in IMAP section format. This will identify
* the part hierarchy used to locate this part within
* the message.
* @param envelope The Envelope that describes this part.
* @param bodyStructure
* The Body structure element that describes this part.
*/
public IMAPAttachedMessage(IMAPMessage parent, String section, IMAPEnvelope envelope, IMAPBodyStructure bodyStructure) {
super((IMAPFolder)parent.getFolder(), parent.store, parent.getMessageNumber(), parent.sequenceNumber);
this.parent = parent;
// sets the subset we're looking for
this.section = section;
// the envelope and body structure are loaded from the server by the parent
this.envelope = envelope;
this.bodyStructure = bodyStructure;
}
/**
* Check if this message is still valid. This is
* delegated to the outer-most message.
*
* @exception MessagingException
*/
protected void checkValidity() throws MessagingException {
parent.checkValidity();
}
/**
* Check if the outer-most message has been expunged.
*
* @return true if the message has been expunged.
*/
public boolean isExpunged() {
return parent.isExpunged();
}
/**
* Get the size of this message part.
*
* @return The estimate size of this message part, in bytes.
*/
public int getSize() {
return bodyStructure.bodySize;
}
/**
* Return a copy the flags associated with this message.
*
* @return a copy of the flags for this message
* @throws MessagingException if there was a problem accessing the Store
*/
public Flags getFlags() throws MessagingException {
return parent.getFlags();
}
/**
* Check whether the supplied flag is set.
* The default implementation checks the flags returned by {@link #getFlags()}.
*
* @param flag the flags to check for
* @return true if the flags is set
* @throws MessagingException if there was a problem accessing the Store
*/
public boolean isSet(Flags.Flag flag) throws MessagingException {
// load the flags, if needed
return parent.isSet(flag);
}
/**
* Set or clear a flag value.
*
* @param flags The set of flags to effect.
* @param set The value to set the flag to (true or false).
*
* @exception MessagingException
*/
public void setFlags(Flags flag, boolean set) throws MessagingException {
throw new MethodNotSupportedException("Flags cannot be set on message attachements");
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/ 0000775 0001750 0001750 00000000000 11703373730 030316 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Const0000664 0001750 0001750 00000002436 10721056121 031765 0 ustar brian brian /*
* 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.pop3;
/**
* Defines a few constants that are used throught the implementation.
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public interface POP3Constants {
public final static String SPACE = " ";
public final static String CRLF = "\r\n";
public final static int DOT = '.';
public final static int OK = 0;
public final static int ERR = 1;
public final static int CHALLENGE = 2;
} ././@LongLink 0000000 0000000 0000000 00000000154 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Messa0000664 0001750 0001750 00000033334 10721056121 031750 0 ustar brian brian /*
* 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.pop3;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.IllegalWriteException;
import javax.mail.MessagingException;
import javax.mail.event.MessageChangedEvent;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
/**
* POP3 implementation of javax.mail.internet.MimeMessage
*
* Only the most basic information is given and Message objects created here is
* a light-weight reference to the actual Message As per the JavaMail spec items
* from the actual message will get filled up on demand
*
* If some other items are obtained from the server as a result of one call,
* then the other details are also processed and filled in. For ex if RETR is
* called then header information will also be processed in addition to the
* content
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class POP3Message extends MimeMessage {
// the size of the message, in bytes
protected int msgSize = -1;
// the size of the headers. We keep this around, as it's needed to
// properly calculate the size of the message
protected int headerSize = -1;
// the UID value retrieved from the server
protected String uid;
// the raw message data from loading the message
protected byte[] messageData;
/**
* Create a new POP3 message associated with a folder.
*
* @param folder The owning folder.
* @param msgnum The message sequence number in the folder.
*/
protected POP3Message(Folder folder, int msgnum) {
super(folder, msgnum);
this.session = session;
// force the headers to empty so we'll load them the first time they're referenced.
this.headers = null;
}
/**
* Get an InputStream for reading the message content.
*
* @return An InputStream instance initialized to read the message
* content.
* @exception MessagingException
*/
protected InputStream getContentStream() throws MessagingException {
// make sure the content is loaded first
loadContent();
// allow the super class to handle creating it from the loaded content.
return super.getContentStream();
}
/**
* Write out the byte data to the provided output stream.
*
* @param out The target stream.
*
* @exception IOException
* @exception MessagingException
*/
public void writeTo(OutputStream out) throws IOException, MessagingException {
// make sure we have everything loaded
loadContent();
// just write out the raw message data
out.write(messageData);
}
/**
* Set a flag value for this Message. The flags are
* only set locally, not the server. When the folder
* is closed, any messages with the Deleted flag set
* will be removed from the server.
*
* @param newFlags The new flag values.
* @param set Indicates whether this is a set or an unset operation.
*
* @exception MessagingException
*/
public void setFlags(Flags newFlags, boolean set) throws MessagingException {
Flags oldFlags = (Flags) flags.clone();
super.setFlags(newFlags, set);
if (!flags.equals(oldFlags)) {
((POP3Folder) folder).notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, this);
}
}
/**
* Unconditionally load the headers from an inputstream.
* When retrieving content, we get back the entire message,
* including the headers. This allows us to skip over
* them to reach the content, even if we already have
* headers loaded.
*
* @param in The InputStream with the header data.
*
* @exception MessagingException
*/
protected void loadHeaders(InputStream in) throws MessagingException {
try {
headerSize = in.available();
// just load and replace the haders
headers = new InternetHeaders(in);
headerSize -= in.available();
} catch (IOException e) {
// reading from a ByteArrayInputStream...this should never happen.
}
}
/**
* Lazy loading of the message content.
*
* @exception MessagingException
*/
protected void loadContent() throws MessagingException {
if (content == null) {
POP3Connection connection = getConnection();
try {
// retrieve (and save the raw message data
messageData = connection.retrieveMessageData(msgnum);
} finally {
// done with the connection
releaseConnection(connection);
}
// now create a input stream for splitting this into headers and
// content
ByteArrayInputStream in = new ByteArrayInputStream(messageData);
// the Sun implementation has an option that forces headers loaded using TOP
// should be forgotten when retrieving the message content. This is because
// some POP3 servers return different results for TOP and RETR. Since we need to
// retrieve the headers anyway, and this set should be the most complete, we'll
// just replace the headers unconditionally.
loadHeaders(in);
// load headers stops loading at the header terminator. Everything
// after that is content.
loadContent(in);
}
}
/**
* Load the message content from the server.
*
* @param stream A ByteArrayInputStream containing the message content.
* We explicitly use ByteArrayInputStream because
* there are some optimizations that can take advantage
* of the fact it is such a stream.
*
* @exception MessagingException
*/
protected void loadContent(ByteArrayInputStream stream) throws MessagingException {
// since this is a byte array input stream, available() returns reliable value.
content = new byte[stream.available()];
try {
// just read everything in to the array
stream.read(content);
} catch (IOException e) {
// should never happen
throw new MessagingException("Error loading content info", e);
}
}
/**
* Get the size of the message.
*
* @return The calculated message size, in bytes.
* @exception MessagingException
*/
public int getSize() throws MessagingException {
if (msgSize < 0) {
// we need to get the headers loaded, since we need that information to calculate the total
// content size without retrieving the content.
loadHeaders();
POP3Connection connection = getConnection();
try {
// get the total message size, and adjust by size of the headers to get the content size.
msgSize = connection.retrieveMessageSize(msgnum) - headerSize;
} finally {
// done with the connection
releaseConnection(connection);
}
}
return msgSize;
}
/**
* notice that we pass zero as the no of lines from the message,as it
* doesn't serv any purpose to get only a certain number of lines.
*
* However this maybe important if a mail client only shows 3 or 4 lines of
* the message in the list and then when the user clicks they would load the
* message on demand.
*
*/
protected void loadHeaders() throws MessagingException {
if (headers == null) {
POP3Connection connection = getConnection();
try {
loadHeaders(connection.retrieveMessageHeaders(msgnum));
} finally {
// done with the connection
releaseConnection(connection);
}
}
}
/**
* Retrieve the message UID from the server.
*
* @return The string UID value.
* @exception MessagingException
*/
protected String getUID() throws MessagingException {
if (uid == null) {
POP3Connection connection = getConnection();
try {
uid = connection.retrieveMessageUid(msgnum);
} finally {
// done with the connection
releaseConnection(connection);
}
}
return uid;
}
// The following are methods that deal with all header accesses. Most of the
// methods that retrieve information from the headers funnel through these, so we
// can lazy-retrieve the header information.
public String[] getHeader(String name) throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getHeader(name);
}
public String getHeader(String name, String delimiter) throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getHeader(name, delimiter);
}
public Enumeration getAllHeaders() throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getAllHeaders();
}
public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getMatchingHeaders(names);
}
public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getNonMatchingHeaders(names);
}
public Enumeration getAllHeaderLines() throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getAllHeaderLines();
}
public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getMatchingHeaderLines(names);
}
public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
// make sure the headers are loaded
loadHeaders();
// allow the super class to handle everything from here
return super.getNonMatchingHeaderLines(names);
}
// the following are overrides for header modification methods. These
// messages are read only,
// so the headers cannot be modified.
public void addHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("POP3 messages are read-only");
}
public void setHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("POP3 messages are read-only");
}
public void removeHeader(String name) throws MessagingException {
throw new IllegalWriteException("POP3 messages are read-only");
}
public void addHeaderLine(String line) throws MessagingException {
throw new IllegalWriteException("POP3 messages are read-only");
}
/**
* We cannot modify these messages
*/
public void saveChanges() throws MessagingException {
throw new IllegalWriteException("POP3 messages are read-only");
}
/**
* get the current connection pool attached to the folder. We need
* to do this dynamically, to A) ensure we're only accessing an
* currently open folder, and B) to make sure we're using the
* correct connection attached to the folder.
*
* @return A connection attached to the hosting folder.
*/
protected POP3Connection getConnection() throws MessagingException {
// the folder owns everything.
return ((POP3Folder)folder).getMessageConnection();
}
/**
* Release the connection back to the Folder after performing an operation
* that requires a connection.
*
* @param connection The previously acquired connection.
*/
protected void releaseConnection(POP3Connection connection) throws MessagingException {
// the folder owns everything.
((POP3Folder)folder).releaseMessageConnection(connection);
}
}
././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/ geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000775 0001750 0001750 00000000000 11703373730 032220 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664 0001750 0001750 00000004433 10721056121 032215 0 ustar brian brian /*
* 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.pop3.connection;
import java.io.ByteArrayInputStream;
import org.apache.geronimo.javamail.store.pop3.POP3Constants;
import org.apache.geronimo.mail.util.Base64;
/**
* This class provides the basic implementation for the POP3Response.
*
* @see org.apache.geronimo.javamail.store.pop3.POP3Response
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class POP3Response implements POP3Constants {
private int status = ERR;
private String firstLine;
private byte[] data;
POP3Response(int status, String firstLine, byte []data) {
this.status = status;
this.firstLine = firstLine;
this.data = data;
}
public int getStatus() {
return status;
}
public byte[] getData() {
return data;
}
public ByteArrayInputStream getContentStream() {
return new ByteArrayInputStream(data);
}
public String getFirstLine() {
return firstLine;
}
public boolean isError() {
return status == ERR;
}
public boolean isChallenge() {
return status == CHALLENGE;
}
/**
* Decode the message portion of a continuation challenge response.
*
* @return The byte array containing the decoded data.
*/
public byte[] decodeChallengeResponse()
{
// the challenge response is a base64 encoded string...
return Base64.decode(firstLine.trim());
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664 0001750 0001750 00000021234 10721056121 032213 0 ustar brian brian /**
* 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.pop3.connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.StoreClosedException;
import org.apache.geronimo.javamail.store.pop3.POP3Store;
import org.apache.geronimo.javamail.util.ProtocolProperties;
public class POP3ConnectionPool {
protected static final String MAIL_PORT = "port";
protected static final String MAIL_SASL_REALM = "sasl.realm";
protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid";
protected static final String DEFAULT_MAIL_HOST = "localhost";
// Our hosting Store instance
protected POP3Store store;
// our Protocol abstraction
protected ProtocolProperties props;
// POP3 is not nearly as multi-threaded as IMAP. We really just have a single folder,
// plus the Store, but the Store doesn't really talk to the server very much. We only
// hold one connection available, and on the off chance there is a situation where
// we need to create a new one, we'll authenticate on demand. The one case where
// I know this might be an issue is a folder checking back with the Store to see it if
// it is still connected.
protected POP3Connection availableConnection;
// our debug flag
protected boolean debug;
// the target host
protected String host;
// the target server port.
protected int port;
// the username we connect with
protected String username;
// the authentication password.
protected String password;
// the SASL realm name
protected String realm;
// the authorization id.
protected String authid;
// Turned on when the store is closed for business.
protected boolean closed = false;
/**
* Create a connection pool associated with a give POP3Store instance. The
* connection pool manages handing out connections for both the Store and
* Folder and Message usage.
*
* @param store The Store we're creating the pool for.
* @param props The protocol properties abstraction we use.
*/
public POP3ConnectionPool(POP3Store store, ProtocolProperties props) {
this.store = store;
this.props = props;
}
/**
* Manage the initial connection to the POP3 server. This is the first
* point where we obtain the information needed to make an actual server
* connection. Like the Store protocolConnect method, we return false
* if there's any sort of authentication difficulties.
*
* @param host The host of the mail server.
* @param port The mail server connection port.
* @param user The connection user name.
* @param password The connection password.
*
* @return True if we were able to connect and authenticate correctly.
* @exception MessagingException
*/
public synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
// NOTE: We don't check for the username/password being null at this point. It's possible that
// the server will send back a PREAUTH response, which means we don't need to go through login
// processing. We'll need to check the capabilities response after we make the connection to decide
// if logging in is necesssary.
// save this for subsequent connections. All pool connections will use this info.
// if the port is defaulted, then see if we have something configured in the session.
// if not configured, we just use the default default.
if (port == -1) {
// check for a property and fall back on the default if it's not set.
port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
// it's possible that -1 might have been explicitly set, so one last check.
if (port == -1) {
port = props.getDefaultPort();
}
}
// Before we do anything, let's make sure that we succesfully received a host
if ( host == null ) {
host = DEFAULT_MAIL_HOST;
}
this.host = host;
this.port = port;
this.username = username;
this.password = password;
// make sure we have the realm information
realm = props.getProperty(MAIL_SASL_REALM);
// get an authzid value, if we have one. The default is to use the username.
authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
// go create a connection and just add it to the pool. If there is an authenticaton error,
// return the connect failure, and we may end up trying again.
availableConnection = createPoolConnection();
if (availableConnection == null) {
return false;
}
// we're connected, authenticated, and ready to go.
return true;
}
/**
* Creates an authenticated pool connection and adds it to
* the connection pool. If there is an existing connection
* already in the pool, this returns without creating a new
* connection.
*
* @exception MessagingException
*/
protected POP3Connection createPoolConnection() throws MessagingException {
POP3Connection connection = new POP3Connection(props);
if (!connection.protocolConnect(host, port, authid, realm, username, password)) {
// we only add live connections to the pool. Sever the connections and
// allow it to go free.
connection.closeServerConnection();
return null;
}
// just return this connection
return connection;
}
/**
* Get a connection from the pool. We try to retrieve a live
* connection, but we test the connection's liveness before
* returning one. If we don't have a viable connection in
* the pool, we'll create a new one. The returned connection
* will be in the authenticated state already.
*
* @return A POP3Connection object that is connected to the server.
*/
public synchronized POP3Connection getConnection() throws MessagingException {
// if we have an available one (common when opening the INBOX), just return it
POP3Connection connection = availableConnection;
if (connection != null) {
availableConnection = null;
return connection;
}
// we need an additional connection...rare, but it can happen if we've closed the INBOX folder.
return createPoolConnection();
}
/**
* Return a connection to the connection pool.
*
* @param connection The connection getting returned.
*
* @exception MessagingException
*/
public synchronized void releaseConnection(POP3Connection connection) throws MessagingException
{
// we're generally only called if the store needed to talk to the server and
// then returned the connection to the pool. So it's pretty likely that we'll just cache this
if (availableConnection == null) {
availableConnection = connection;
}
else {
// got too many connections created...not sure how, but get rid of this one.
connection.close();
}
}
/**
* Close the entire connection pool.
*
* @exception MessagingException
*/
public synchronized void close() throws MessagingException {
// we'll on have the single connection in reserver
if (availableConnection != null) {
availableConnection.close();
availableConnection = null;
}
// turn out the lights, hang the closed sign on the wall.
closed = true;
}
}
././@LongLink 0000000 0000000 0000000 00000000174 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664 0001750 0001750 00000006430 10721056121 032214 0 ustar brian brian /*
* 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.pop3.connection;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import javax.mail.MessagingException;
/**
* This class adds functionality to the basic response by parsing the reply for
* LIST command and obtaining specific information about the msgnum and the
* size. It could be for one or more msgs depending on wether a msg number was
* passed or not into the LIST command
*
* @see org.apache.geronimo.javamail.store.pop3.POP3Response
* @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class POP3ListResponse extends POP3Response {
private int msgnum = 0;
private int size = 0;
private List multipleMsgs = null;
POP3ListResponse(POP3Response baseRes) throws MessagingException {
super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
// if ERR not worth proceeding any further
if (OK == getStatus()) {
// if data == null, then it mean it's a single line response
if (baseRes.getData() == null) {
String[] args = getFirstLine().split(SPACE);
try {
msgnum = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
throw new MessagingException("Invalid response for LIST command", e);
}
try {
size = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
throw new MessagingException("Invalid response for LIST command", e);
}
} else {
int totalMsgs = 0;
String[] args = getFirstLine().split(SPACE);
try {
totalMsgs = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
throw new MessagingException("Invalid response for LIST command", e);
}
multipleMsgs = new ArrayList(totalMsgs);
// Todo : multi-line response parsing
}
}
}
public int getMessageNumber() {
return msgnum;
}
public int getSize() {
return size;
}
/**
* Messages can be accessed by multipleMsgs.getElementAt(msgnum)
*
*/
public List getMultipleMessageDetails() {
return multipleMsgs;
}
}
././@LongLink 0000000 0000000 0000000 00000000172 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664 0001750 0001750 00000057056 11515320247 032233 0 ustar brian brian /**
* 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.pop3.connection;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
import org.apache.geronimo.javamail.store.pop3.POP3Constants;
import org.apache.geronimo.javamail.util.CommandFailedException;
import org.apache.geronimo.javamail.util.InvalidCommandException;
import org.apache.geronimo.javamail.util.MIMEInputReader;
import org.apache.geronimo.javamail.util.MailConnection;
import org.apache.geronimo.javamail.util.ProtocolProperties;
import org.apache.geronimo.mail.util.Base64;
import org.apache.geronimo.mail.util.Hex;
/**
* Simple implementation of POP3 transport.
*
* @version $Rev: 1060388 $ $Date: 2011-01-18 09:16:07 -0500 (Tue, 18 Jan 2011) $
*/
public class POP3Connection extends MailConnection implements POP3Constants {
static final protected String MAIL_APOP_ENABLED = "apop.enable";
static final protected String MAIL_AUTH_ENABLED = "auth.enable";
static final protected String MAIL_RESET_QUIT = "rsetbeforequit";
static final protected String MAIL_DISABLE_TOP = "disabletop";
static final protected String MAIL_FORGET_TOP = "forgettopheaders";
// the initial greeting string, which might be required for APOP authentication.
protected String greeting;
// is use of the AUTH command enabled
protected boolean authEnabled;
// is use of APOP command enabled
protected boolean apopEnabled;
// input reader wrapped around the socket input stream
protected BufferedReader reader;
// output writer wrapped around the socket output stream.
protected PrintWriter writer;
// this connection was closed unexpectedly
protected boolean closed;
// indicates whether this conneciton is currently logged in. Once
// we send a QUIT, we're finished.
protected boolean loggedIn;
// indicates whether we need to avoid using the TOP command
// when retrieving headers
protected boolean topDisabled = false;
/**
* Normal constructor for an POP3Connection() object.
*
* @param store The store we're associated with (source of parameter values).
* @param host The target host name of the IMAP server.
* @param port The target listening port of the server. Defaults to 119 if
* the port is specified as -1.
* @param username The login user name (can be null unless authentication is
* required).
* @param password Password associated with the userid account. Can be null if
* authentication is not required.
* @param sslConnection
* True if this is targetted as an SSLConnection.
* @param debug The session debug flag.
*/
public POP3Connection(ProtocolProperties props) {
super(props);
// get our login properties flags
authEnabled = props.getBooleanProperty(MAIL_AUTH_ENABLED, false);
apopEnabled = props.getBooleanProperty(MAIL_APOP_ENABLED, false);
topDisabled = props.getBooleanProperty(MAIL_DISABLE_TOP, false);
}
/**
* Connect to the server and do the initial handshaking.
*
* @exception MessagingException
*/
public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
this.serverHost = host;
this.serverPort = port;
this.realm = realm;
this.authid = authid;
this.username = username;
this.password = password;
try {
// create socket and connect to server.
getConnection();
// consume the welcome line
getWelcome();
// go login with the server
if (login())
{
loggedIn = true;
return true;
}
return false;
} catch (IOException e) {
if (debug) {
debugOut("I/O exception establishing connection", e);
}
throw new MessagingException("Connection error", e);
}
}
/**
* Create a transport connection object and connect it to the
* target server.
*
* @exception MessagingException
*/
protected void getConnection() throws MessagingException
{
try {
// do all of the non-protocol specific set up. This will get our socket established
// and ready use.
super.getConnection();
} catch (IOException e) {
throw new MessagingException("Unable to obtain a connection to the POP3 server", e);
}
// The POp3 protocol is inherently a string-based protocol, so we get
// string readers/writers for the connection streams. Note that we explicitly
// set the encoding to ensure that an inappropriate native encoding is not picked up.
try {
reader = new BufferedReader(new InputStreamReader(inputStream, "ISO8859-1"));
writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream), "ISO8859-1"));
} catch (UnsupportedEncodingException e) {
}
}
protected void getWelcome() throws IOException {
// just read the line and consume it. If debug is
// enabled, there I/O stream will be traced
greeting = reader.readLine();
}
public String toString() {
return "POP3Connection host: " + serverHost + " port: " + serverPort;
}
/**
* Close the connection. On completion, we'll be disconnected from
* the server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// if we're already closed, get outta here.
if (socket == null) {
return;
}
try {
// say goodbye
logout();
} finally {
// and close up the connection. We do this in a finally block to make sure the connection
// is shut down even if quit gets an error.
closeServerConnection();
// get rid of our response processor too.
reader = null;
writer = null;
}
}
/**
* Tag this connection as having been closed by the
* server. This will not be returned to the
* connection pool.
*/
public void setClosed() {
closed = true;
}
/**
* Test if the connnection has been forcibly closed.
*
* @return True if the server disconnected the connection.
*/
public boolean isClosed() {
return closed;
}
protected POP3Response sendCommand(String cmd) throws MessagingException {
return sendCommand(cmd, false);
}
protected POP3Response sendMultiLineCommand(String cmd) throws MessagingException {
return sendCommand(cmd, true);
}
protected synchronized POP3Response sendCommand(String cmd, boolean multiLine) throws MessagingException {
if (socket.isConnected()) {
{
// NOTE: We don't use println() because it uses the platform concept of a newline rather
// than using CRLF, which is required by the POP3 protocol.
writer.write(cmd);
writer.write("\r\n");
writer.flush();
POP3Response response = buildResponse(multiLine);
if (response.isError()) {
throw new CommandFailedException("Error issuing POP3 command: " + cmd);
}
return response;
}
}
throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
}
/**
* Build a POP3Response item from the response stream.
*
* @param isMultiLineResponse
* If true, this command is expecting multiple lines back from the server.
*
* @return A POP3Response item with all of the command response data.
* @exception MessagingException
*/
protected POP3Response buildResponse(boolean isMultiLineResponse) throws MessagingException {
int status = ERR;
byte[] data = null;
String line;
MIMEInputReader source = new MIMEInputReader(reader);
try {
line = reader.readLine();
} catch (IOException e) {
throw new MessagingException("Error in receving response");
}
if (line == null || line.trim().equals("")) {
throw new MessagingException("Empty Response");
}
if (line.startsWith("+OK")) {
status = OK;
line = removeStatusField(line);
if (isMultiLineResponse) {
data = getMultiLineResponse();
}
} else if (line.startsWith("-ERR")) {
status = ERR;
line = removeStatusField(line);
}else if (line.startsWith("+")) {
status = CHALLENGE;
line = removeStatusField(line);
if (isMultiLineResponse) {
data = getMultiLineResponse();
}
} else {
throw new MessagingException("Unexpected response: " + line);
}
return new POP3Response(status, line, data);
}
private static String removeStatusField(String line) {
return line.substring(line.indexOf(SPACE) + 1);
}
/**
* This could be a multiline response
*/
private byte[] getMultiLineResponse() throws MessagingException {
MIMEInputReader source = new MIMEInputReader(reader);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// it's more efficient to do this a buffer at a time.
// the MIMEInputReader takes care of the byte-stuffing and
// ".\r\n" input terminator for us.
try {
OutputStreamWriter outWriter = new OutputStreamWriter(out, "ISO8859-1");
char buffer[] = new char[500];
try {
int charsRead = -1;
while ((charsRead = source.read(buffer)) >= 0) {
outWriter.write(buffer, 0, charsRead);
}
outWriter.flush();
} catch (IOException e) {
throw new MessagingException("Error processing a multi-line response", e);
}
} catch (UnsupportedEncodingException e) {
}
return out.toByteArray();
}
/**
* Retrieve the raw message content from the POP3
* server. This is all of the message data, including
* the header.
*
* @param sequenceNumber
* The message sequence number.
*
* @return A byte array containing all of the message data.
* @exception MessagingException
*/
public byte[] retrieveMessageData(int sequenceNumber) throws MessagingException {
POP3Response msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
// we want the data directly in this case.
return msgResponse.getData();
}
/**
* Retrieve the message header information for a given
* message, returned as an input stream suitable
* for loading the message data.
*
* @param sequenceNumber
* The server sequence number for the message.
*
* @return An inputstream that can be used to read the message
* data.
* @exception MessagingException
*/
public ByteArrayInputStream retrieveMessageHeaders(int sequenceNumber) throws MessagingException {
POP3Response msgResponse;
// some POP3 servers don't correctly implement TOP, so this can be disabled. If
// we can't use TOP, then use RETR and retrieve everything. We can just hand back
// the stream, as the header loading routine will stop at the first
// null line.
if (topDisabled) {
msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
}
else {
msgResponse = sendMultiLineCommand("TOP " + sequenceNumber + " 0");
}
// just load the returned message data as a set of headers
return msgResponse.getContentStream();
}
/**
* Retrieve the total message size from the mail
* server. This is the size of the headers plus
* the size of the message content.
*
* @param sequenceNumber
* The message sequence number.
*
* @return The full size of the message.
* @exception MessagingException
*/
public int retrieveMessageSize(int sequenceNumber) throws MessagingException {
POP3Response msgResponse = sendCommand("LIST " + sequenceNumber);
// Convert this into the parsed response type we need.
POP3ListResponse list = new POP3ListResponse(msgResponse);
// this returns the total message size
return list.getSize();
}
/**
* Retrieve the mail drop status information.
*
* @return An object representing the returned mail drop status.
* @exception MessagingException
*/
public POP3StatusResponse retrieveMailboxStatus() throws MessagingException {
// issue the STAT command and return this into a status response
return new POP3StatusResponse(sendCommand("STAT"));
}
/**
* Retrieve the UID for an individual message.
*
* @param sequenceNumber
* The target message sequence number.
*
* @return The string UID maintained by the server.
* @exception MessagingException
*/
public String retrieveMessageUid(int sequenceNumber) throws MessagingException {
POP3Response msgResponse = sendCommand("UIDL " + sequenceNumber);
String message = msgResponse.getFirstLine();
// the UID is everything after the blank separating the message number and the UID.
// there's not supposed to be anything else on the message, but trim it of whitespace
// just to be on the safe side.
return message.substring(message.indexOf(' ') + 1).trim();
}
/**
* Delete a single message from the mail server.
*
* @param sequenceNumber
* The sequence number of the message to delete.
*
* @exception MessagingException
*/
public void deleteMessage(int sequenceNumber) throws MessagingException {
// just issue the command...we ignore the command response
sendCommand("DELE " + sequenceNumber);
}
/**
* Logout from the mail server. This sends a QUIT
* command, which will likely sever the mail connection.
*
* @exception MessagingException
*/
public void logout() throws MessagingException {
// we may have already sent the QUIT command
if (!loggedIn) {
return;
}
// just issue the command...we ignore the command response
sendCommand("QUIT");
loggedIn = false;
}
/**
* Perform a reset on the mail server.
*
* @exception MessagingException
*/
public void reset() throws MessagingException {
// some mail servers mark retrieved messages for deletion
// automatically. This will reset the read flags before
// we go through normal cleanup.
if (props.getBooleanProperty(MAIL_RESET_QUIT, false)) {
// just send an RSET command first
sendCommand("RSET");
}
}
/**
* Ping the mail server to see if we still have an active connection.
*
* @exception MessagingException thrown if we do not have an active connection.
*/
public void pingServer() throws MessagingException {
// just issue the command...we ignore the command response
sendCommand("NOOP");
}
/**
* Login to the mail server, using whichever method is
* configured. This will try multiple methods, if allowed,
* in decreasing levels of security.
*
* @return true if the login was successful.
* @exception MessagingException
*/
public synchronized boolean login() throws MessagingException {
// permitted to use the AUTH command?
if (authEnabled) {
try {
// go do the SASL thing
return processSaslAuthentication();
} catch (MessagingException e) {
// Any error here means fall back to the next mechanism
}
}
if (apopEnabled) {
try {
// go do the SASL thing
return processAPOPAuthentication();
} catch (MessagingException e) {
// Any error here means fall back to the next mechanism
}
}
try {
// do the tried and true login processing.
return processLogin();
} catch (MessagingException e) {
}
// everything failed...can't get in
return false;
}
/**
* Process a basic LOGIN operation, using the
* plain test USER/PASS command combo.
*
* @return true if we logged successfully.
* @exception MessagingException
*/
public boolean processLogin() throws MessagingException {
// start by sending the USER command, followed by
// the PASS command
sendCommand("USER " + username);
sendCommand("PASS " + password);
return true; // we're in
}
/**
* Process logging in using the APOP command. Only
* works on servers that give a timestamp value
* in the welcome response.
*
* @return true if the login was accepted.
* @exception MessagingException
*/
public boolean processAPOPAuthentication() throws MessagingException {
int timeStart = greeting.indexOf('<');
// if we didn't get an APOP challenge on the greeting, throw an exception
// the main login processor will swallow that and fall back to the next
// mechanism
if (timeStart == -1) {
throw new MessagingException("POP3 Server does not support APOP");
}
int timeEnd = greeting.indexOf('>');
String timeStamp = greeting.substring(timeStart, timeEnd + 1);
// we create the digest password using the timestamp value sent to use
// concatenated with the password.
String digestPassword = timeStamp + password;
byte[] digest;
try {
// create a digest value from the password.
MessageDigest md = MessageDigest.getInstance("MD5");
digest = md.digest(digestPassword.getBytes("iso-8859-1"));
} catch (NoSuchAlgorithmException e) {
// this shouldn't happen, but if it does, we'll just try a plain
// login.
throw new MessagingException("Unable to create MD5 digest", e);
} catch (UnsupportedEncodingException e) {
// this shouldn't happen, but if it does, we'll just try a plain
// login.
throw new MessagingException("Unable to create MD5 digest", e);
}
// this will throw an exception if it gives an error failure
sendCommand("APOP " + username + " " + Hex.encode(digest));
// no exception, we must have passed
return true;
}
/**
* Process SASL-type authentication.
*
* @return Returns true if the server support a SASL authentication mechanism and
* accepted reponse challenges.
* @exception MessagingException
*/
protected boolean processSaslAuthentication() throws MessagingException {
// if unable to get an appropriate authenticator, just fail it.
ClientAuthenticator authenticator = getSaslAuthenticator();
if (authenticator == null) {
throw new MessagingException("Unable to obtain SASL authenticator");
}
// go process the login.
return processLogin(authenticator);
}
/**
* Attempt to retrieve a SASL authenticator for this
* protocol.
*
* @return A SASL authenticator, or null if a suitable one
* was not located.
*/
protected ClientAuthenticator getSaslAuthenticator() {
return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
}
/**
* Process a login using the provided authenticator object.
*
* NB: This method is synchronized because we have a multi-step process going on
* here. No other commands should be sent to the server until we complete.
*
* @return Returns true if the server support a SASL authentication mechanism and
* accepted reponse challenges.
* @exception MessagingException
*/
protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
if (debug) {
debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
}
POP3Response response = sendCommand("AUTH " + authenticator.getMechanismName());
// now process the challenge sequence. We get a continuation response back for each stage of the
// authentication, and finally an OK when everything passes muster.
while (true) {
// this should be a continuation reply, if things are still good.
if (response.isChallenge()) {
// we're passed back a challenge value, Base64 encoded.
byte[] challenge = response.decodeChallengeResponse();
try {
String responseString = new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII");
// have the authenticator evaluate and send back the encoded response.
response = sendCommand(responseString);
} catch (UnsupportedEncodingException ex) {
}
}
else {
// there are only two choices here, OK or a continuation. OK means
// we've passed muster and are in.
return true;
}
}
}
/**
* Merge the configured SASL mechanisms with the capabilities that the
* server has indicated it supports, returning a merged list that can
* be used for selecting a mechanism.
*
* @return A List representing the intersection of the configured list and the
* capabilities list.
*/
protected List selectSaslMechanisms() {
// just return the set that have been explicity permitted
return getSaslMechanisms();
}
}
././@LongLink 0000000 0000000 0000000 00000000176 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664 0001750 0001750 00000004360 10721056121 032214 0 ustar brian brian /*
* 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.pop3.connection;
import javax.mail.MessagingException;
/**
* This class adds functionality to the basic response by parsing the status
* line and obtaining specific information about num of msgs and the size
*
* @see org.apache.geronimo.javamail.store.pop3.POP3Response
* @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class POP3StatusResponse extends POP3Response {
private int numMessages = 0;
private int size = 0;
POP3StatusResponse(POP3Response baseRes) throws MessagingException {
super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
// if ERR not worth proceeding any further
if (OK == getStatus()) {
String[] args = getFirstLine().split(SPACE);
try {
numMessages = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
throw new MessagingException("Invalid response for STAT command", e);
}
try {
size = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
throw new MessagingException("Invalid response for STAT command", e);
}
}
}
public int getNumMessages() {
return numMessages;
}
public int getSize() {
return size;
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootF0000664 0001750 0001750 00000010747 10721056121 031734 0 ustar brian brian /**
* 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.pop3;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Store;
import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
/**
* An POP3 folder instance for the root of POP3 folder tree. This has
* some of the folder operations disabled.
*/
public class POP3RootFolder extends POP3Folder {
// the inbox folder is the only one that exists
protected Folder inbox;
/**
* Create a default POP3RootFolder attached to a specific Store instance.
*
* @param store The Store instance this is the root for.
*/
public POP3RootFolder(POP3Store store) {
// create a folder with a null string name and the default separator.
super(store, "");
// this only holds folders
folderType = HOLDS_FOLDERS;
// this folder does exist
exists = true;
// no messages in this folder
msgCount = 0;
}
/**
* Get the parent. This is the root folder, which
* never has a parent.
*
* @return Always returns null.
*/
public Folder getParent() {
// we never have a parent folder
return null;
}
/**
* We have a separator because the root folder is "special".
*/
public char getSeparator() throws MessagingException {
return '/';
}
/**
* Retrieve a list of folders that match a pattern.
*
* @param pattern The match pattern.
*
* @return An array of matching folders.
* @exception MessagingException
*/
public Folder[] list(String pattern) throws MessagingException {
// I'm not sure this is correct, but the Sun implementation appears to
// return a array containing the inbox regardless of what pattern was specified.
return new Folder[] { getInbox() };
}
/**
* Get a folder of a given name from the root folder.
* The Sun implementation seems somewhat inconsistent
* here. The docs for Store claim that only INBOX is
* supported, but it will return a Folder instance for any
* name. On the other hand, the root folder raises
* an exception for anything but the INBOX.
*
* @param name The folder name (which must be "INBOX".
*
* @return The inbox folder instance.
* @exception MessagingException
*/
public Folder getFolder(String name) throws MessagingException {
if (!name.equalsIgnoreCase("INBOX")) {
throw new MessagingException("Only the INBOX folder is supported");
}
// return the inbox folder
return getInbox();
}
/**
* Override for the isOpen method. The root folder can
* never be opened.
*
* @return always returns false.
*/
public boolean isOpen() {
return false;
}
public void open(int mode) throws MessagingException {
throw new MessagingException("POP3 root folder cannot be opened");
}
public void open(boolean expunge) throws MessagingException {
throw new MessagingException("POP3 root folder cannot be close");
}
/**
* Retrieve the INBOX folder from the root.
*
* @return The Folder instance for the inbox.
* @exception MessagingException
*/
protected Folder getInbox() throws MessagingException {
// we're the only place that creates folders, and
// we only create the single instance.
if (inbox == null) {
inbox = new POP3Folder((POP3Store)store, "INBOX");
}
return inbox;
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folde0000664 0001750 0001750 00000042073 11055035501 031731 0 ustar brian brian /*
* 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.pop3;
import java.util.List;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.FolderClosedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.event.ConnectionEvent;
import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse;
/**
* The POP3 implementation of the javax.mail.Folder Note that only INBOX is
* supported in POP3
*
* http://www.faqs.org/rfcs/rfc1939.html
*
*
* @see javax.mail.Folder
*
* @version $Rev: 689140 $ $Date: 2008-08-26 13:20:01 -0400 (Tue, 26 Aug 2008) $
*/
public class POP3Folder extends Folder {
protected boolean isFolderOpen = false;
protected int mode;
protected int msgCount;
private POP3Message[] messageCache;
// The fully qualified name of the folder. For a POP3 folder, this is either "" for the root or
// "INPUT" for the in-basket. It is possible to create other folders, but they will report that
// they don't exist.
protected String fullName;
// indicates whether this folder exists or not
protected boolean exists = false;
// indicates the type of folder this is.
protected int folderType;
/**
* Create a new folder associate with a POP3 store instance.
*
* @param store The owning Store.
* @param name The name of the folder. Note that POP3 stores only
* have 2 real folders, the root ("") and the in-basket
* ("INBOX"). It is possible to create other instances
* of Folder associated with the Store, but they will
* be non-functional.
*/
public POP3Folder(POP3Store store, String name) {
super(store);
this.fullName = name;
// if this is the input folder, this exists
if (name.equalsIgnoreCase("INPUT")) {
exists = true;
}
// by default, we're holding messages.
folderType = Folder.HOLDS_MESSAGES;
}
/**
* Retrieve the folder name. This is the simple folder
* name at the its hiearchy level. This can be invoked when the folder is closed.
*
* @return The folder's name.
*/
public String getName() {
// the name and the full name are always the same
return fullName;
}
/**
* Retrieve the folder's full name (including hierarchy information).
* This can be invoked when the folder is closed.
*
* @return The full name value.
*/
public String getFullName() {
return fullName;
}
/**
* Never return "this" as the parent folder. Somebody not familliar with
* POP3 may do something like while(getParent() != null) or something
* simmilar which will result in an infinte loop
*/
public Folder getParent() throws MessagingException {
// the default folder returns null. We return the default
// folder
return store.getDefaultFolder();
}
/**
* Indicate whether a folder exists. Only the root
* folder and "INBOX" will ever return true.
*
* @return true for real POP3 folders, false for any other
* instances that have been created.
* @exception MessagingException
*/
public boolean exists() throws MessagingException {
// only one folder truely exists...this might be it.
return exists;
}
public Folder[] list(String pattern) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
}
/**
* No sub folders, hence there is no notion of a seperator. This is always a null character.
*/
public char getSeparator() throws MessagingException {
return '\0';
}
/**
* There's no hierarchy in POP3, so the only type
* is HOLDS_MESSAGES (and only one of those exists).
*
* @return Always returns HOLDS_MESSAGES.
* @exception MessagingException
*/
public int getType() throws MessagingException {
return folderType;
}
/**
* Always returns false as any creation operation must
* fail.
*
* @param type The type of folder to create. This is ignored.
*
* @return Always returns false.
* @exception MessagingException
*/
public boolean create(int type) throws MessagingException {
return false;
}
/**
* No way to detect new messages, so always return false.
*
* @return Always returns false.
* @exception MessagingException
*/
public boolean hasNewMessages() throws MessagingException {
return false;
}
public Folder getFolder(String name) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
}
public boolean delete(boolean recurse) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be deleted");
}
public boolean renameTo(Folder f) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be renamed");
}
/**
* @see javax.mail.Folder#open(int)
*/
public void open(int mode) throws MessagingException {
// Can only be performed on a closed folder
checkClosed();
// get a connection object
POP3Connection connection = getConnection();
try {
POP3StatusResponse res = connection.retrieveMailboxStatus();
this.mode = mode;
this.isFolderOpen = true;
this.msgCount = res.getNumMessages();
// JavaMail API has no method in Folder to expose the total
// size (no of bytes) of the mail drop;
// NB: We use the actual message number to access the messages from
// the cache, which is origin 1. Vectors are origin 0, so we have to subtract each time
// we access a messagge.
messageCache = new POP3Message[msgCount];
} catch (Exception e) {
throw new MessagingException("Unable to execute STAT command", e);
}
finally {
// return the connection when finished
releaseConnection(connection);
}
notifyConnectionListeners(ConnectionEvent.OPENED);
}
/**
* Close a POP3 folder.
*
* @param expunge The expunge flag (ignored for POP3).
*
* @exception MessagingException
*/
public void close(boolean expunge) throws MessagingException {
// Can only be performed on an open folder
checkOpen();
// get a connection object
POP3Connection connection = getConnection();
try {
// we might need to reset the connection before we
// process deleted messages and send the QUIT. The
// connection knows if we need to do this.
connection.reset();
// clean up any messages marked for deletion
expungeDeletedMessages(connection);
} finally {
// return the connection when finished
releaseConnection(connection);
// cleanup the the state even if exceptions occur when deleting the
// messages.
cleanupFolder(false);
}
}
/**
* Mark any messages we've flagged as deleted from the
* POP3 server before closing.
*
* @exception MessagingException
*/
protected void expungeDeletedMessages(POP3Connection connection) throws MessagingException {
if (mode == READ_WRITE) {
for (int i = 0; i < messageCache.length; i++) {
POP3Message msg = messageCache[i];
if (msg != null) {
// if the deleted flag is set, go delete this
// message. NB: We adjust the index back to an
// origin 1 value
if (msg.isSet(Flags.Flag.DELETED)) {
try {
connection.deleteMessage(i + 1);
} catch (MessagingException e) {
throw new MessagingException("Exception deleting message number " + (i + 1), e);
}
}
}
}
}
}
/**
* Do folder cleanup. This is used both for normal
* close operations, and adnormal closes where the
* server has sent us a BYE message.
*
* @param expunge Indicates whether open messages should be expunged.
* @param disconnected
* The disconnected flag. If true, the server has cut
* us off, which means our connection can not be returned
* to the connection pool.
*
* @exception MessagingException
*/
protected void cleanupFolder(boolean disconnected) throws MessagingException {
messageCache = null;
isFolderOpen = false;
notifyConnectionListeners(ConnectionEvent.CLOSED);
}
/**
* Obtain a connection object for a Message attached to this Folder. This
* will be the Folder's connection, which is only available if the Folder
* is currently open.
*
* @return The connection object for the Message instance to use.
* @exception MessagingException
*/
synchronized POP3Connection getMessageConnection() throws MessagingException {
// we always get one from the store. If we're fully single threaded, then
// we can get away with just a single one.
return getConnection();
}
/**
* Release the connection object back to the Folder instance.
*
* @param connection The connection being released.
*
* @exception MessagingException
*/
void releaseMessageConnection(POP3Connection connection) throws MessagingException {
// give this back to the store
releaseConnection(connection);
}
public boolean isOpen() {
// if we're not open, we're not open
if (!isFolderOpen) {
return false;
}
try {
// we might be open, but the Store has been closed. In which case, we're not any more
// closing also changes the isFolderOpen flag.
if (!((POP3Store)store).isConnected()) {
close(false);
}
} catch (MessagingException e) {
}
return isFolderOpen;
}
public Flags getPermanentFlags() {
// unfortunately doesn't have a throws clause for this method
// throw new MethodNotSupportedException("POP3 doesn't support permanent
// flags");
// Better than returning null, save the extra condition from a user to
// check for null
// and avoids a NullPointerException for the careless.
return new Flags();
}
/**
* Get the folder message count.
*
* @return The number of messages in the folder.
* @exception MessagingException
*/
public int getMessageCount() throws MessagingException {
// NB: returns -1 if the folder isn't open.
return msgCount;
}
/**
* Checks wether the message is in cache, if not will create a new message
* object and return it.
*
* @see javax.mail.Folder#getMessage(int)
*/
public Message getMessage(int msgNum) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
if (msgNum < 1 || msgNum > getMessageCount()) {
throw new MessagingException("Invalid Message number");
}
Message msg = messageCache[msgNum - 1];
if (msg == null) {
msg = new POP3Message(this, msgNum);
messageCache[msgNum - 1] = (POP3Message)msg;
}
return msg;
}
public void appendMessages(Message[] msgs) throws MessagingException {
throw new MethodNotSupportedException("Message appending is not supported in POP3");
}
public Message[] expunge() throws MessagingException {
throw new MethodNotSupportedException("Expunge is not supported in POP3");
}
public int getMode() throws IllegalStateException {
// Can only be performed on an Open folder
checkOpen();
return mode;
}
/**
* @see javax.mail.Folder#fetch(javax.mail.Message[],
* javax.mail.FetchProfile)
*
* The JavaMail API recommends that this method be overrident to provide a
* meaningfull implementation.
*/
public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
for (int i = 0; i < msgs.length; i++) {
Message msg = msgs[i];
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
// fetching the size and the subject will force all of the
// envelope information to load
msg.getHeader("Subject");
msg.getSize();
}
if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
// force the content to load...this also fetches the header information.
// C'est la vie.
((POP3Message)msg).loadContent();
msg.getSize();
}
// force flag loading for this message
if (fp.contains(FetchProfile.Item.FLAGS)) {
msg.getFlags();
}
if (fp.getHeaderNames().length > 0) {
// loading any header loads all headers, so just grab the header set.
msg.getHeader("Subject");
}
}
}
/**
* Retrieve the UID for a given message.
*
* @param msg The message of interest.
*
* @return The String UID value for this message.
* @exception MessagingException
*/
public synchronized String getUID(Message msg) throws MessagingException {
checkOpen();
// the Message knows how to do this
return ((POP3Message)msg).getUID();
}
/**
* Below is a list of covinience methods that avoid repeated checking for a
* value and throwing an exception
*/
/** Ensure the folder is open */
private void checkOpen() throws IllegalStateException {
if (!isFolderOpen) {
throw new IllegalStateException("Folder is not Open");
}
}
/** Ensure the folder is not open */
private void checkClosed() throws IllegalStateException {
if (isFolderOpen) {
throw new IllegalStateException("Folder is Open");
}
}
/**
* @see javax.mail.Folder#notifyMessageChangedListeners(int,
* javax.mail.Message)
*
* this method is protected and cannot be used outside of Folder, therefore
* had to explicitly expose it via a method in POP3Folder, so that
* POP3Message has access to it
*
* Bad design on the part of the Java Mail API.
*/
public void notifyMessageChangedListeners(int type, Message m) {
super.notifyMessageChangedListeners(type, m);
}
/**
* Retrieve the connection attached to this folder. Throws an
* exception if we don't have an active connection.
*
* @return The current connection object.
* @exception MessagingException
*/
protected synchronized POP3Connection getConnection() throws MessagingException {
// request a connection from the central store.
return ((POP3Store)store).getFolderConnection(this);
}
/**
* Release our connection back to the Store.
*
* @param connection The connection to release.
*
* @exception MessagingException
*/
protected void releaseConnection(POP3Connection connection) throws MessagingException {
// we need to release the connection to the Store once we're finished with it
((POP3Store)store).releaseFolderConnection(this, connection);
}
}
././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store0000664 0001750 0001750 00000027732 11061534403 032003 0 ustar brian brian /*
* 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.pop3;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;
import javax.mail.AuthenticationFailedException;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
import org.apache.geronimo.javamail.store.pop3.connection.POP3ConnectionPool;
import org.apache.geronimo.javamail.util.ProtocolProperties;
/**
* POP3 implementation of javax.mail.Store POP protocol spec is implemented in
* org.apache.geronimo.javamail.store.pop3.POP3Connection
*
* @version $Rev: 693530 $ $Date: 2008-09-09 13:57:23 -0400 (Tue, 09 Sep 2008) $
*/
public class POP3Store extends Store {
protected static final int DEFAULT_POP3_PORT = 110;
protected static final int DEFAULT_POP3_SSL_PORT = 995;
// our accessor for protocol properties and the holder of
// protocol-specific information
protected ProtocolProperties props;
// our connection object
protected POP3ConnectionPool connectionPool;
// our session provided debug output stream.
protected PrintStream debugStream;
// the debug flag
protected boolean debug;
// the root folder
protected POP3RootFolder root;
// until we're connected, we're closed
boolean closedForBusiness = true;
protected LinkedList openFolders = new LinkedList();
public POP3Store(Session session, URLName name) {
this(session, name, "pop3", DEFAULT_POP3_PORT, false);
}
/**
* Common constructor used by the POP3Store and POP3SSLStore classes
* to do common initialization of defaults.
*
* @param session
* The host session instance.
* @param name
* The URLName of the target.
* @param protocol
* The protocol type ("pop3"). This helps us in
* retrieving protocol-specific session properties.
* @param defaultPort
* The default port used by this protocol. For pop3, this will
* be 110. The default for pop3 with ssl is 995.
* @param sslConnection
* Indicates whether an SSL connection should be used to initial
* contact the server. This is different from the STARTTLS
* support, which switches the connection to SSL after the
* initial startup.
*/
protected POP3Store(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
// create the protocol property holder. This gives an abstraction over the different
// flavors of the protocol.
props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
// get our debug settings
debugStream = session.getDebugOut();
debug = session.getDebug();
// the connection pool manages connections for the stores, folder, and message usage.
connectionPool = new POP3ConnectionPool(this, props);
}
/**
* Return a Folder object that represents the root of the namespace for the current user.
*
* Note that in some store configurations (such as IMAP4) the root folder might
* not be the INBOX folder.
*
* @return the root Folder
* @throws MessagingException if there was a problem accessing the store
*/
public Folder getDefaultFolder() throws MessagingException {
checkConnectionStatus();
// if no root yet, create a root folder instance.
if (root == null) {
return new POP3RootFolder(this);
}
return root;
}
/**
* Return the Folder corresponding to the given name.
* The folder might not physically exist; the {@link Folder#exists()} method can be used
* to determine if it is real.
*
* @param name the name of the Folder to return
*
* @return the corresponding folder
* @throws MessagingException
* if there was a problem accessing the store
*/
public Folder getFolder(String name) throws MessagingException {
return getDefaultFolder().getFolder(name);
}
/**
* Return the folder identified by the URLName; the URLName must refer to this Store.
* Implementations may use the {@link URLName#getFile()} method to determined the folder name.
*
* @param url
*
* @return the corresponding folder
* @throws MessagingException
* if there was a problem accessing the store
*/
public Folder getFolder(URLName url) throws MessagingException {
return getDefaultFolder().getFolder(url.getFile());
}
/**
* @see javax.mail.Service#protocolConnect(java.lang.String, int,
* java.lang.String, java.lang.String)
*/
protected synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
if (debug) {
debugOut("Connecting to server " + host + ":" + port + " for user " + username);
}
// the connection pool handles all of the details here.
if (connectionPool.protocolConnect(host, port, username, password))
{
// the store is now open
closedForBusiness = false;
return true;
}
return false;
}
/**
* Get a connection for the store.
*
* @return The request connection object.
* @exception MessagingException
*/
protected POP3Connection getConnection() throws MessagingException {
return connectionPool.getConnection();
}
/**
* Return a connection back to the connection pool after
* it has been used for a request.
*
* @param connection The return connection.
*
* @exception MessagingException
*/
protected void releaseConnection(POP3Connection connection) throws MessagingException {
connectionPool.releaseConnection(connection);
}
/**
* Get a connection object for a folder to use.
*
* @param folder The requesting folder (always the inbox for POP3).
*
* @return An active POP3Connection.
* @exception MessagingException
*/
synchronized POP3Connection getFolderConnection(POP3Folder folder) throws MessagingException {
POP3Connection connection = connectionPool.getConnection();
openFolders.add(folder);
return connection;
}
/**
* Release a connection object after a folder is
* finished with a request.
*
* @param folder The requesting folder.
* @param connection
*
* @exception MessagingException
*/
synchronized void releaseFolderConnection(POP3Folder folder, POP3Connection connection) throws MessagingException {
openFolders.remove(folder);
// return this back to the pool
connectionPool.releaseConnection(connection);
}
/**
* Close all open folders. We have a small problem here with a race condition. There's no safe, single
* synchronization point for us to block creation of new folders while we're closing. So we make a copy of
* the folders list, close all of those folders, and keep repeating until we're done.
*/
protected void closeOpenFolders() {
// we're no longer accepting additional opens. Any folders that open after this point will get an
// exception trying to get a connection.
closedForBusiness = true;
while (true) {
List folders = null;
// grab our lock, copy the open folders reference, and null this out. Once we see a null
// open folders ref, we're done closing.
synchronized(connectionPool) {
folders = openFolders;
openFolders = new LinkedList();
}
// null folder, we're done
if (folders.isEmpty()) {
return;
}
// now close each of the open folders.
for (int i = 0; i < folders.size(); i++) {
POP3Folder folder = (POP3Folder)folders.get(i);
try {
folder.close(false);
} catch (MessagingException e) {
}
}
}
}
/**
* @see javax.mail.Service#isConnected()
*/
public boolean isConnected() {
// the connect() method of the super class checks here first. If the connected flag
// is off, then it's too early for use to try to get a connection and verify we still
// have a live one.
if (!super.isConnected()) {
return false;
}
try {
POP3Connection connection = getConnection();
// a null connection likely means we had a failure establishing a
// new connection to the POP3 server.
if (connection == null) {
return false;
}
try {
// make sure the server is really there
connection.pingServer();
return true;
}
finally {
// return the connection to the pool when finished
if (connection != null) {
releaseConnection(connection);
}
}
} catch (MessagingException e) {
}
return false;
}
/**
* Close the store, and any open folders associated with the
* store.
*
* @exception MessagingException
*/
public synchronized void close() throws MessagingException{
// if already closed, nothing to do.
if (closedForBusiness) {
return;
}
// close the folders first, then shut down the Store.
closeOpenFolders();
connectionPool.close();
connectionPool = null;
// make sure we do the superclass close operation first so
// notification events get broadcast properly.
super.close();
}
/**
* Check the status of our connection.
*
* @exception MessagingException
*/
private void checkConnectionStatus() throws MessagingException {
if (!this.isConnected()) {
throw new MessagingException("Not connected ");
}
}
/**
* Internal debug output routine.
*
* @param value The string value to output.
*/
void debugOut(String message) {
debugStream.println("POP3Store DEBUG: " + message);
}
/**
* Internal debugging routine for reporting exceptions.
*
* @param message A message associated with the exception context.
* @param e The received exception.
*/
void debugOut(String message, Throwable e) {
debugOut("Received exception -> " + message);
debugOut("Exception message -> " + e.getMessage());
e.printStackTrace(debugStream);
}
/**
* Finalizer to perform IMAPStore() cleanup when
* no longer in use.
*
* @exception Throwable
*/
protected void finalize() throws Throwable {
super.finalize();
close();
}
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLSt0000664 0001750 0001750 00000003061 10721056121 031642 0 ustar brian brian /**
* 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.pop3;
import javax.mail.Session;
import javax.mail.URLName;
/**
* POP3 implementation of javax.mail.Store over an SSL connection.
*
* @version $Rev: 597135 $ $Date: 2007-11-21 11:26:57 -0500 (Wed, 21 Nov 2007) $
*/
public class POP3SSLStore extends POP3Store {
/**
* Construct an POP3SSLStore item.
*
* @param session The owning javamail Session.
* @param urlName The Store urlName, which can contain server target information.
*/
public POP3SSLStore(Session session, URLName urlName) {
// we're the imaps protocol, our default connection port is 993, and we must use
// an SSL connection for the initial hookup
super(session, urlName, "pop3s", DEFAULT_POP3_SSL_PORT, true);
}
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/ 0000775 0001750 0001750 00000000000 11703373727 031326 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Plain0000664 0001750 0001750 00000007127 10474735322 032320 0 ustar brian brian /*
* 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.authentication;
import java.io.UnsupportedEncodingException;
import javax.mail.MessagingException;
public class PlainAuthenticator implements ClientAuthenticator {
// the user we're authenticating
protected String username;
// the user's password (the "shared secret")
protected String password;
// indicates whether we've gone through the entire challenge process.
protected boolean complete = false;
/**
* Main constructor.
*
* @param username
* The login user name.
* @param password
* The login password.
*/
public PlainAuthenticator(String username, String password) {
this.username = username;
this.password = password;
}
/**
* Respond to the hasInitialResponse query. This mechanism does have an
* initial response, which is the entire challenge sequence.
*
* @return Always returns true.
*/
public boolean hasInitialResponse() {
return true;
}
/**
* Indicate whether the challenge/response process is complete.
*
* @return True if the last challenge has been processed, false otherwise.
*/
public boolean isComplete() {
return complete;
}
/**
* Retrieve the authenticator mechanism name.
*
* @return Always returns the string "PLAIN"
*/
public String getMechanismName() {
return "PLAIN";
}
/**
* Evaluate a PLAIN login challenge, returning the a result string that
* should satisfy the clallenge.
*
* @param challenge
* The decoded challenge data, as byte array.
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
try {
// get the username and password in an UTF-8 encoding to create the
// token
byte[] userBytes = username.getBytes("UTF-8");
byte[] passBytes = password.getBytes("UTF-8");
// our token has two copies of the username, one copy of the
// password, and nulls
// between
byte[] tokenBytes = new byte[(userBytes.length * 2) + passBytes.length + 2];
System.arraycopy(userBytes, 0, tokenBytes, 0, userBytes.length);
System.arraycopy(userBytes, 0, tokenBytes, userBytes.length + 1, userBytes.length);
System.arraycopy(passBytes, 0, tokenBytes, (userBytes.length * 2) + 2, passBytes.length);
complete = true;
return tokenBytes;
} catch (UnsupportedEncodingException e) {
// got an error, fail this
throw new MessagingException("Invalid encoding");
}
}
}
././@LongLink 0000000 0000000 0000000 00000000171 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/CramM0000664 0001750 0001750 00000013163 11404405474 032245 0 ustar brian brian /*
* 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.authentication;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.mail.MessagingException;
import org.apache.geronimo.mail.util.Hex;
public class CramMD5Authenticator implements ClientAuthenticator {
// the user we're authenticating
protected String username;
// the user's password (the "shared secret")
protected String password;
// indicates whether we've gone through the entire challenge process.
protected boolean complete = false;
/**
* Main constructor.
*
* @param username
* The login user name.
* @param password
* The login password.
*/
public CramMD5Authenticator(String username, String password) {
this.username = username;
this.password = password;
}
/**
* Respond to the hasInitialResponse query. This mechanism does not have an
* initial response.
*
* @return Always returns false.
*/
public boolean hasInitialResponse() {
return false;
}
/**
* Indicate whether the challenge/response process is complete.
*
* @return True if the last challenge has been processed, false otherwise.
*/
public boolean isComplete() {
return complete;
}
/**
* Retrieve the authenticator mechanism name.
*
* @return Always returns the string "CRAM-MD5"
*/
public String getMechanismName() {
return "CRAM-MD5";
}
/**
* Evaluate a CRAM-MD5 login challenge, returning the a result string that
* should satisfy the clallenge.
*
* @param challenge
* The decoded challenge data, as a byte array.
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
// we create the challenge from the userid and password information (the
// "shared secret").
byte[] passBytes;
try {
// get the password in an UTF-8 encoding to create the token
passBytes = password.getBytes("UTF-8");
// compute the password digest using the key
byte[] digest = computeCramDigest(passBytes, challenge);
// create a unified string using the user name and the hex encoded
// digest
String responseString = username + " " + new String(Hex.encode(digest), "ISO8859-1");
complete = true;
return responseString.getBytes("ISO8859-1");
} catch (UnsupportedEncodingException e) {
// got an error, fail this
throw new MessagingException("Invalid character encodings");
}
}
/**
* Compute a CRAM digest using the hmac_md5 algorithm. See the description
* of RFC 2104 for algorithm details.
*
* @param key
* The key (K) for the calculation.
* @param input
* The encrypted text value.
*
* @return The computed digest, as a byte array value.
* @exception NoSuchAlgorithmException
*/
protected byte[] computeCramDigest(byte[] key, byte[] input) throws MessagingException {
// CRAM digests are computed using the MD5 algorithm.
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new MessagingException("Unable to access MD5 message digest", e);
}
// if the key is longer than 64 bytes, then we get a digest of the key
// and use that instead.
// this is required by RFC 2104.
if (key.length > 64) {
digest.update(key);
key = digest.digest();
}
// now we create two 64 bit padding keys, initialized with the key
// information.
byte[] ipad = new byte[64];
byte[] opad = new byte[64];
System.arraycopy(key, 0, ipad, 0, key.length);
System.arraycopy(key, 0, opad, 0, key.length);
// and these versions are munged by XORing with "magic" values.
for (int i = 0; i < 64; i++) {
ipad[i] ^= 0x36;
opad[i] ^= 0x5c;
}
// now there are a pair of MD5 operations performed, and inner and an
// outer. The spec defines this as
// H(K XOR opad, H(K XOR ipad, text)), where H is the MD5 operation.
// inner operation
digest.reset();
digest.update(ipad);
digest.update(input); // this appends the text to the pad
byte[] md5digest = digest.digest();
// outer operation
digest.reset();
digest.update(opad);
digest.update(md5digest);
return digest.digest(); // final result
}
}
././@LongLink 0000000 0000000 0000000 00000000171 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Authe0000664 0001750 0001750 00000007623 11276027406 032322 0 ustar brian brian /**
* 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.authentication;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Properties;
import org.apache.geronimo.javamail.util.ProtocolProperties;
public class AuthenticatorFactory {
// the list of authentication mechanisms we have direct support for. Others come from
// SASL, if it's available.
public static final String AUTHENTICATION_PLAIN = "PLAIN";
public static final String AUTHENTICATION_LOGIN = "LOGIN";
public static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
public static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
static public ClientAuthenticator getAuthenticator(ProtocolProperties props, List mechanisms, String host, String username, String password, String authId, String realm)
{
// if the authorization id isn't given, then this is the same as the logged in user name.
if (authId == null) {
authId = username;
}
// if SASL is enabled, try getting a SASL authenticator first
if (props.getBooleanProperty("sasl.enable", false)) {
// we need to convert the mechanisms map into an array of strings for SASL.
String [] mechs = (String [])mechanisms.toArray(new String[mechanisms.size()]);
try {
// need to try to load this using reflection since it has references to
// the SASL API. That's only available with 1.5 or later.
Class authenticatorClass = Class.forName("org.apache.geronimo.javamail.authentication.SASLAuthenticator");
Constructor c = authenticatorClass.getConstructor(new Class[] {
(new String[0]).getClass(),
Properties.class,
String.class,
String.class,
String.class,
String.class,
String.class,
String.class
});
Object[] args = { mechs, props.getProperties(), props.getProtocol(), host, realm, authId, username, password };
return (ClientAuthenticator)c.newInstance(args);
} catch (Throwable e) {
// Any exception is likely because we're running on 1.4 and can't use the Sasl API.
// just ignore and use our fallback implementations.
}
}
// now go through the progression of mechanisms we support, from the
// most secure to the least secure.
if (mechanisms.contains(AUTHENTICATION_DIGESTMD5)) {
return new DigestMD5Authenticator(host, username, password, realm);
} else if (mechanisms.contains(AUTHENTICATION_CRAMMD5)) {
return new CramMD5Authenticator(username, password);
} else if (mechanisms.contains(AUTHENTICATION_LOGIN)) {
return new LoginAuthenticator(username, password);
} else if (mechanisms.contains(AUTHENTICATION_PLAIN)) {
return new PlainAuthenticator(username, password);
} else {
// can't find a mechanism we support in common
return null;
}
}
}
././@LongLink 0000000 0000000 0000000 00000000166 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/SASLAuthenticator.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/SASLA0000664 0001750 0001750 00000014643 10716317503 032115 0 ustar brian brian /*
* 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.authentication;
import java.io.UnsupportedEncodingException ;
import java.util.Map;
import java.util.Properties;
import javax.mail.MessagingException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import javax.security.sasl.RealmCallback;
import javax.security.sasl.RealmChoiceCallback;
public class SASLAuthenticator implements ClientAuthenticator, CallbackHandler {
// The realm we're authenticating within
protected String realm;
// the user we're authenticating
protected String username;
// the user's password (the "shared secret")
protected String password;
// the authenticator we're proxying
protected SaslClient authenticator;
protected boolean complete = false;
/**
* Main constructor.
*
* @param username
* The login user name.
* @param password
* The login password.
*/
public SASLAuthenticator(String[] mechanisms, Properties properties, String protocol, String host, String realm,
String authorizationID, String username, String password) throws MessagingException {
this.realm = realm;
this.username = username;
this.password = password;
try {
authenticator = Sasl.createSaslClient(mechanisms, authorizationID, protocol, host, (Map)properties,
this);
} catch (SaslException e) {
}
}
/**
* Respond to the hasInitialResponse query. We defer this to the Sasl client.
*
* @return The SaslClient response to the same query.
*/
public boolean hasInitialResponse() {
return authenticator.hasInitialResponse();
}
/**
* Indicate whether the challenge/response process is complete.
*
* @return True if the last challenge has been processed, false otherwise.
*/
public boolean isComplete() {
return authenticator.hasInitialResponse();
}
/**
* Retrieve the authenticator mechanism name.
*
* @return Always returns the string "PLAIN"
*/
public String getMechanismName() {
// the authenticator selects this for us.
return authenticator.getMechanismName();
}
/**
* Evaluate a login challenge, returning the a result string that
* should satisfy the clallenge. This is forwarded to the
* SaslClient, which will use the CallBackHandler to retrieve the
* information it needs for the given protocol.
*
* @param challenge
* The decoded challenge data, as byte array.
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
// for an initial response challenge, there's no challenge date. The SASL
// client still expects a byte array argument.
if (challenge == null) {
challenge = new byte[0];
}
try {
return authenticator.evaluateChallenge(challenge);
} catch (SaslException e) {
// got an error, fail this
throw new MessagingException("Error performing SASL validation", e);
}
}
public void handle(Callback[] callBacks) {
for (int i = 0; i < callBacks.length; i++) {
Callback callBack = callBacks[i];
// requesting the user name
if (callBack instanceof NameCallback) {
((NameCallback)callBack).setName(username);
}
// need the password
else if (callBack instanceof PasswordCallback) {
((PasswordCallback)callBack).setPassword(password.toCharArray());
}
// direct request for the realm information
else if (callBack instanceof RealmCallback) {
RealmCallback realmCallback = (RealmCallback)callBack;
// we might not have a realm, so use the default from the
// callback item
if (realm == null) {
realmCallback.setText(realmCallback.getDefaultText());
}
else {
realmCallback.setText(realm);
}
}
// asked to select the realm information from a list
else if (callBack instanceof RealmChoiceCallback) {
RealmChoiceCallback realmCallback = (RealmChoiceCallback)callBack;
// if we don't have a realm, just tell it to use the default
if (realm == null) {
realmCallback.setSelectedIndex(realmCallback.getDefaultChoice());
}
else {
// locate our configured one in the list
String[] choices = realmCallback.getChoices();
for (int j = 0; j < choices.length; j++) {
// set the index to any match and get out of here.
if (choices[j].equals(realm)) {
realmCallback.setSelectedIndex(j);
break;
}
}
// NB: If there was no match, we don't set anything.
// this should cause an authentication failure.
}
}
}
}
}
././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011571 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Login0000664 0001750 0001750 00000010431 10474735322 032315 0 ustar brian brian /*
* 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.authentication;
import java.io.UnsupportedEncodingException;
import javax.mail.MessagingException;
public class LoginAuthenticator implements ClientAuthenticator {
// constants for the authentication stages
protected static final int USERNAME = 0;
protected static final int PASSWORD = 1;
protected static final int COMPLETE = 2;
// the user we're authenticating
protected String username;
// the user's password (the "shared secret")
protected String password;
// indicates whether we've gone through the entire challenge process.
protected int stage = USERNAME;
/**
* Main constructor.
*
* @param username
* The login user name.
* @param password
* The login password.
*/
public LoginAuthenticator(String username, String password) {
this.username = username;
this.password = password;
}
/**
* Respond to the hasInitialResponse query. This mechanism does not have an
* initial response.
*
* @return Always returns false;
*/
public boolean hasInitialResponse() {
return false;
}
/**
* Indicate whether the challenge/response process is complete.
*
* @return True if the last challenge has been processed, false otherwise.
*/
public boolean isComplete() {
return stage == COMPLETE;
}
/**
* Retrieve the authenticator mechanism name.
*
* @return Always returns the string "LOGIN"
*/
public String getMechanismName() {
return "LOGIN";
}
/**
* Evaluate a PLAIN login challenge, returning the a result string that
* should satisfy the clallenge.
*
* @param challenge
* The decoded challenge data, as a byte array
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
// process the correct stage for the challenge
switch (stage) {
// should never happen
case COMPLETE:
throw new MessagingException("Invalid LOGIN challenge");
case USERNAME: {
byte[] userBytes;
try {
// get the username and password in an UTF-8 encoding to create
// the token
userBytes = username.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// got an error, fail this (this should never happen).
throw new MessagingException("Invalid encoding");
}
// next time through we're looking for a password.
stage = PASSWORD;
// the user bytes are the entire challenge respose.
return userBytes;
}
case PASSWORD: {
byte[] passBytes;
try {
// get the username and password in an UTF-8 encoding to create
// the token
passBytes = password.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// got an error, fail this (this should never happen).
throw new MessagingException("Invalid encoding");
}
// we're finished
stage = COMPLETE;
return passBytes;
}
}
// should never get here.
throw new MessagingException("Invalid LOGIN challenge");
}
}
././@LongLink 0000000 0000000 0000000 00000000173 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Diges0000664 0001750 0001750 00000051464 11515320247 032304 0 ustar brian brian /*
* 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.authentication;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import javax.mail.AuthenticationFailedException;
import javax.mail.MessagingException;
import org.apache.geronimo.mail.util.Base64;
import org.apache.geronimo.mail.util.Hex;
/**
* Process a DIGEST-MD5 authentication, using the challenge/response mechanisms.
*/
public class DigestMD5Authenticator implements ClientAuthenticator {
protected static final int AUTHENTICATE_CLIENT = 0;
protected static final int AUTHENTICATE_SERVER = 1;
protected static final int AUTHENTICATION_COMPLETE = 2;
// the host server name
protected String host;
// the user we're authenticating
protected String username;
// the user's password (the "shared secret")
protected String password;
// the target login realm
protected String realm;
// our message digest for processing the challenges.
MessageDigest digest;
// the string we send to the server on the first challenge.
protected String clientResponse;
// the response back from an authentication challenge.
protected String authenticationResponse = null;
// our list of realms received from the server (normally just one).
protected ArrayList realms;
// the nonce value sent from the server
protected String nonce;
// indicates whether we've gone through the entire challenge process.
protected int stage = AUTHENTICATE_CLIENT;
/**
* Main constructor.
*
* @param host
* The server host name.
* @param username
* The login user name.
* @param password
* The login password.
* @param realm
* The target login realm (can be null).
*/
public DigestMD5Authenticator(String host, String username, String password, String realm) {
this.host = host;
this.username = username;
this.password = password;
this.realm = realm;
}
/**
* Respond to the hasInitialResponse query. This mechanism does not have an
* initial response.
*
* @return Always returns false.
*/
public boolean hasInitialResponse() {
return false;
}
/**
* Indicate whether the challenge/response process is complete.
*
* @return True if the last challenge has been processed, false otherwise.
*/
public boolean isComplete() {
return stage == AUTHENTICATION_COMPLETE;
}
/**
* Retrieve the authenticator mechanism name.
*
* @return Always returns the string "DIGEST-MD5"
*/
public String getMechanismName() {
return "DIGEST-MD5";
}
/**
* Evaluate a DIGEST-MD5 login challenge, returning the a result string that
* should satisfy the clallenge.
*
* @param challenge
* The decoded challenge data, as a string.
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
// DIGEST-MD5 authentication goes in two stages. First state involves us
// validating with the
// server, the second stage is the server validating with us, using the
// shared secret.
switch (stage) {
// stage one of the process.
case AUTHENTICATE_CLIENT: {
// get the response and advance the processing stage.
byte[] response = authenticateClient(challenge);
stage = AUTHENTICATE_SERVER;
return response;
}
// stage two of the process.
case AUTHENTICATE_SERVER: {
// get the response and advance the processing stage to completed.
byte[] response = authenticateServer(challenge);
stage = AUTHENTICATION_COMPLETE;
return response;
}
// should never happen.
default:
throw new MessagingException("Invalid LOGIN challenge");
}
}
/**
* Evaluate a DIGEST-MD5 login server authentication challenge, returning
* the a result string that should satisfy the clallenge.
*
* @param challenge
* The decoded challenge data, as a string.
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] authenticateServer(byte[] challenge) throws MessagingException {
// parse the challenge string and validate.
if (!parseChallenge(challenge)) {
return null;
}
try {
// like all of the client validation steps, the following is order
// critical.
// first add in the URI information.
digest.update((":smtp/" + host).getBytes("US-ASCII"));
// now mix in the response we sent originally
String responseString = clientResponse + new String(Hex.encode(digest.digest()), "US-ASCII");
digest.update(responseString.getBytes("US-ASCII"));
// now convert that into a hex encoded string.
String validationText = new String(Hex.encode(digest.digest()), "US-ASCII");
// if everything went well, this calculated value should match what
// we got back from the server.
// our response back is just a null string....
if (validationText.equals(authenticationResponse)) {
return new byte[0];
}
throw new AuthenticationFailedException("Invalid DIGEST-MD5 response from server");
} catch (UnsupportedEncodingException e) {
throw new MessagingException("Invalid character encodings");
}
}
/**
* Evaluate a DIGEST-MD5 login client authentication challenge, returning
* the a result string that should satisfy the clallenge.
*
* @param challenge
* The decoded challenge data, as a string.
*
* @return A formatted challege response, as an array of bytes.
* @exception MessagingException
*/
public byte[] authenticateClient(byte[] challenge) throws MessagingException {
// parse the challenge string and validate.
if (!parseChallenge(challenge)) {
return null;
}
SecureRandom randomGenerator;
// before doing anything, make sure we can get the required crypto
// support.
try {
randomGenerator = new SecureRandom();
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new MessagingException("Unable to access cryptography libraries");
}
// if not configured for a realm, take the first realm from the list, if
// any
if (realm == null) {
// if not handed any realms, just use the host name.
if (realms.isEmpty()) {
realm = host;
} else {
// pretty arbitrary at this point, so just use the first one.
realm = (String) realms.get(0);
}
}
// use secure random to generate a collection of bytes. that is our
// cnonce value.
byte[] cnonceBytes = new byte[32];
randomGenerator.nextBytes(cnonceBytes);
try {
// and get this as a base64 encoded string.
String cnonce = new String(Base64.encode(cnonceBytes), "US-ASCII");
// Now the digest computation part. This gets a bit tricky, and must be
// done in strict order.
// this identifies where we're logging into.
String idString = username + ":" + realm + ":" + password;
// we get a digest for this string, then use the digest for the
// first stage
// of the next digest operation.
digest.update(digest.digest(idString.getBytes("US-ASCII")));
// now we add the nonce strings to the digest.
String nonceString = ":" + nonce + ":" + cnonce;
digest.update(nonceString.getBytes("US-ASCII"));
// hex encode this digest, and add on the string values
// NB, we only support "auth" for the quality of protection value
// (qop). We save this in an
// instance variable because we'll need this to validate the
// response back from the server.
clientResponse = new String(Hex.encode(digest.digest()), "US-ASCII") + ":" + nonce + ":00000001:" + cnonce + ":auth:";
// now we add in identification values to the hash.
String authString = "AUTHENTICATE:smtp/" + host;
digest.update(authString.getBytes("US-ASCII"));
// this gets added on to the client response
String responseString = clientResponse + new String(Hex.encode(digest.digest()), "US-ASCII");
// and this gets fed back into the digest
digest.update(responseString.getBytes("US-ASCII"));
// and FINALLY, the challege digest is hex encoded for sending back
// to the server (whew).
String challengeResponse = new String(Hex.encode(digest.digest()), "US-ASCII");
// now finally build the keyword/value part of the challenge
// response. These can be
// in any order.
StringBuffer response = new StringBuffer();
response.append("username=\"");
response.append(username);
response.append("\"");
response.append(",realm=\"");
response.append(realm);
response.append("\"");
// we only support auth qop values, and the nonce-count (nc) is
// always 1.
response.append(",qop=auth");
response.append(",nc=00000001");
response.append(",nonce=\"");
response.append(nonce);
response.append("\"");
response.append(",cnonce=\"");
response.append(cnonce);
response.append("\"");
response.append(",digest-uri=\"smtp/");
response.append(host);
response.append("\"");
response.append(",response=");
response.append(challengeResponse);
return response.toString().getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new MessagingException("Invalid character encodings");
}
}
/**
* Parse the challege string, pulling out information required for our
* challenge response.
*
* @param challenge
* The challenge data.
*
* @return true if there were no errors parsing the string, false otherwise.
* @exception MessagingException
*/
protected boolean parseChallenge(byte[] challenge) throws MessagingException {
realms = new ArrayList();
DigestParser parser = null;
try {
parser = new DigestParser(new String(challenge, "US-ASCII"));
} catch (UnsupportedEncodingException ex) {
}
// parse the entire string...but we ignore everything but the options we
// support.
while (parser.hasMore()) {
NameValuePair pair = parser.parseNameValuePair();
String name = pair.name;
// realm to add to our list?
if (name.equalsIgnoreCase("realm")) {
realms.add(pair.value);
}
// we need the nonce to evaluate the client challenge.
else if (name.equalsIgnoreCase("nonce")) {
nonce = pair.value;
}
// rspauth is the challenge replay back, which allows us to validate
// that server is also legit.
else if (name.equalsIgnoreCase("rspauth")) {
authenticationResponse = pair.value;
}
}
return true;
}
/**
* Inner class for parsing a DIGEST-MD5 challenge string, which is composed
* of "name=value" pairs, separated by "," characters.
*/
class DigestParser {
// the challenge we're parsing
String challenge;
// length of the challenge
int length;
// current parsing position
int position;
/**
* Normal constructor.
*
* @param challenge
* The challenge string to be parsed.
*/
public DigestParser(String challenge) {
this.challenge = challenge;
this.length = challenge.length();
position = 0;
}
/**
* Test if there are more values to parse.
*
* @return true if we've not reached the end of the challenge string,
* false if the challenge has been completely consumed.
*/
private boolean hasMore() {
return position < length;
}
/**
* Return the character at the current parsing position.
*
* @return The string character for the current parse position.
*/
private char currentChar() {
return challenge.charAt(position);
}
/**
* step forward to the next character position.
*/
private void nextChar() {
position++;
}
/**
* Skip over any white space characters in the challenge string.
*/
private void skipSpaces() {
while (position < length && Character.isWhitespace(currentChar())) {
position++;
}
}
/**
* Parse a quoted string used with a name/value pair, accounting for
* escape characters embedded within the string.
*
* @return The string value of the character string.
*/
private String parseQuotedValue() {
// we're here because we found the starting double quote. Step over
// it and parse to the closing
// one.
nextChar();
StringBuffer value = new StringBuffer();
while (hasMore()) {
char ch = currentChar();
// is this an escape char?
if (ch == '\\') {
// step past this, and grab the following character
nextChar();
// we have an invalid quoted string....
if (!hasMore()) {
return null;
}
value.append(currentChar());
}
// end of the string?
else if (ch == '"') {
// step over this so the caller doesn't process it.
nextChar();
// return the constructed string.
return value.toString();
} else {
// step over the character and contine with the next
// characteer1
value.append(ch);
}
nextChar();
}
/* fell off the end without finding a closing quote! */
return null;
}
/**
* Parse a token value used with a name/value pair.
*
* @return The string value of the token. Returns null if nothing is
* found up to the separater.
*/
private String parseTokenValue() {
StringBuffer value = new StringBuffer();
while (hasMore()) {
char ch = currentChar();
switch (ch) {
// process the token separators.
case ' ':
case '\t':
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
// no token characters found? this is bad.
if (value.length() == 0) {
return null;
}
// return the accumulated characters.
return value.toString();
default:
// is this a control character? That's a delimiter (likely
// invalid for the next step,
// but it is a token terminator.
if (ch < 32 || ch > 127) {
// no token characters found? this is bad.
if (value.length() == 0) {
return null;
}
// return the accumulated characters.
return value.toString();
}
value.append(ch);
break;
}
// step to the next character.
nextChar();
}
// no token characters found? this is bad.
if (value.length() == 0) {
return null;
}
// return the accumulated characters.
return value.toString();
}
/**
* Parse out a name token of a name/value pair.
*
* @return The string value of the name.
*/
private String parseName() {
// skip to the value start
skipSpaces();
// the name is a token.
return parseTokenValue();
}
/**
* Parse out a a value of a name/value pair.
*
* @return The string value associated with the name.
*/
private String parseValue() {
// skip to the value start
skipSpaces();
// start of a quoted string?
if (currentChar() == '"') {
// parse it out as a string.
return parseQuotedValue();
}
// the value must be a token.
return parseTokenValue();
}
/**
* Parse a name/value pair in an DIGEST-MD5 string.
*
* @return A NameValuePair object containing the two parts of the value.
* @exception MessagingException
*/
public NameValuePair parseNameValuePair() throws MessagingException {
// get the name token
String name = parseName();
if (name == null) {
throw new MessagingException("Name syntax error");
}
// the name should be followed by an "=" sign
if (!hasMore() || currentChar() != '=') {
throw new MessagingException("Name/value pair syntax error");
}
// step over the equals
nextChar();
// now get the value part
String value = parseValue();
if (value == null) {
throw new MessagingException("Name/value pair syntax error");
}
// skip forward to the terminator, which should either be the end of
// the line or a ","
skipSpaces();
// all that work, only to have a syntax error at the end (sigh)
if (hasMore()) {
if (currentChar() != ',') {
throw new MessagingException("Name/value pair syntax error");
}
// step over, and make sure we position ourselves at either the
// end or the first
// real character for parsing the next name/value pair.
nextChar();
skipSpaces();
}
return new NameValuePair(name, value);
}
}
/**
* Simple inner class to represent a name/value pair.
*/
public class NameValuePair {
public String name;
public String value;
NameValuePair(String name, String value) {
this.name = name;
this.value = value;
}
}
}
././@LongLink 0000000 0000000 0000000 00000000170 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Clien0000664 0001750 0001750 00000006713 10474735322 032307 0 ustar brian brian /*
* 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.authentication;
import javax.mail.MessagingException;
/**
* Simplified version of the Java 5 SaslClient interface. This is used to
* implement a javamail authentication framework that mimics the Sasl framework
* on a 1.4.2 JVM. Only the methods required by the Javamail code are
* implemented here, but it should be a simple migration to the fuller SASL
* interface.
*/
public interface ClientAuthenticator {
/**
* Evaluate a challenge and return a response that can be sent back to the
* server. Bot the challenge information and the response information are
* "raw data", minus any special encodings used by the transport. For
* example, SMTP DIGEST-MD5 authentication protocol passes information as
* Base64 encoded strings. That encoding must be removed before calling
* evaluateChallenge() and the resulting respose must be Base64 encoced
* before transmission to the server.
*
* It is the authenticator's responsibility to keep track of the state of
* the evaluations. That is, if the authentication process requires multiple
* challenge/response cycles, then the authenticator needs to keep track of
* context of the challenges.
*
* @param challenge
* The challenge data.
*
* @return An appropriate response for the challenge data.
*/
public byte[] evaluateChallenge(byte[] challenge) throws MessagingException;
/**
* Indicates that the authenticator has data that should be sent when the
* authentication process is initiated. For example, the SMTP PLAIN
* authentication sends userid/password without waiting for a challenge
* response.
*
* If this method returns true, then the initial response is retrieved using
* evaluateChallenge() passing null for the challenge information.
*
* @return True if the challenge/response process starts with an initial
* response on the client side.
*/
public boolean hasInitialResponse();
/**
* Indicates whether the client believes the challenge/response sequence is
* now complete.
*
* @return true if the client has evaluated what it believes to be the last
* challenge, false if there are additional stages to evaluate.
*/
public boolean isComplete();
/**
* Return the mechanism name implemented by this authenticator.
*
* @return The string name of the authentication mechanism. This name should
* match the names commonly used by the mail servers (e.g., "PLAIN",
* "LOGIN", "DIGEST-MD5", etc.).
*/
public String getMechanismName();
}
geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ 0000775 0001750 0001750 00000000000 11703373727 030107 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/RFC822MessageHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/RFC822Messa0000664 0001750 0001750 00000010750 11265061707 031667 0 ustar brian brian /**
* 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.handlers;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataContentHandler;
import javax.activation.DataSource;
import javax.mail.Message;
import javax.mail.MessageAware;
import javax.mail.MessageContext;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
/**
* Content handler for RFC-822 compliant messages.
* @version $Rev: 824701 $ $Date: 2009-10-13 07:25:59 -0400 (Tue, 13 Oct 2009) $
*/
public class RFC822MessageHandler implements DataContentHandler {
// the data flavor defines what this looks like, and is fixed once the
// handler is instantiated
protected final DataFlavor flavour;
public RFC822MessageHandler() {
flavour = new ActivationDataFlavor(Message.class, "message/rfc822", "Message");
}
/**
* Return all of the flavors processed by this handler. This
* is just the singleton flavor.
*
* @return An array of the transfer flavors supported by this handler.
*/
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { flavour };
}
/**
* Retrieve the transfer data from the data source, but
* only if the requested flavor matches what we support.
*
* @param df The requested data flavor.
* @param ds The source DataSource.
*
* @return The extracted content object, or null if there is a
* mismatch of flavors.
* @exception UnsupportedFlavorException
* @exception IOException
*/
public Object getTransferData(DataFlavor df, DataSource ds) throws UnsupportedFlavorException, IOException {
return flavour.equals(df) ? getContent(ds) : null;
}
/**
* Extract the RFC822 Message content from a DataSource.
*
* @param ds The source data source.
*
* @return An extracted MimeMessage object.
* @exception IOException
*/
public Object getContent(DataSource ds) throws IOException {
try {
// creating a MimeMessage instance requires a session. If the DataSource
// is a MessageAware one, we can get the session information from the MessageContext.
// This is generally the case, but if it is not available, then just retrieve
// the default instance and use it.
if (ds instanceof MessageAware) {
MessageContext context = ((MessageAware)ds).getMessageContext();
return new MimeMessage(context.getSession(), ds.getInputStream());
}
else {
return new MimeMessage(Session.getDefaultInstance(new Properties(), null), ds.getInputStream());
}
} catch (MessagingException e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
}
/**
* Write an RFC 822 message object out to an output stream.
*
* @param obj The source message object.
* @param mimeType The target mimetype
* @param os The target output stream.
*
* @exception IOException
*/
public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
// we only handle message instances here
if (obj instanceof Message) {
Message message = (Message) obj;
try {
message.writeTo(os);
} catch (MessagingException e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
}
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHa0000664 0001750 0001750 00000002236 10676151012 032203 0 ustar brian brian /**
* 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.handlers;
import javax.activation.ActivationDataFlavor;
/**
* @version $Rev: 579145 $ $Date: 2007-09-25 05:16:58 -0400 (Tue, 25 Sep 2007) $
*/
public class TextPlainHandler extends AbstractTextHandler {
public TextPlainHandler() {
super(new ActivationDataFlavor(String.class, "text/plain", "Plain Text"));
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHa0000664 0001750 0001750 00000002264 10676151012 032124 0 ustar brian brian /**
* 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.handlers;
import java.awt.Image;
import javax.activation.ActivationDataFlavor;
/**
* @version $Rev: 579145 $ $Date: 2007-09-25 05:16:58 -0400 (Tue, 25 Sep 2007) $
*/
public class ImageJpegHandler extends AbstractImageHandler {
public ImageJpegHandler() {
super(new ActivationDataFlavor(Image.class, "image/jpeg", "JPEG Image"));
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTex0000664 0001750 0001750 00000011014 11026734371 032245 0 ustar brian brian /**
* 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.handlers;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import javax.activation.DataContentHandler;
import javax.activation.DataSource;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeUtility;
/**
* @version $Rev: 669902 $ $Date: 2008-06-20 10:04:41 -0400 (Fri, 20 Jun 2008) $
*/
public class AbstractTextHandler implements DataContentHandler {
private final DataFlavor flavour;
public AbstractTextHandler(DataFlavor flavour) {
this.flavour = flavour;
}
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] {flavour};
}
public Object getTransferData(DataFlavor dataFlavor, DataSource dataSource) throws UnsupportedFlavorException, IOException {
return flavour.equals(dataFlavor) ? getContent(dataSource) : null;
}
/**
* Read the content from the DataSource and transform
* it into a text object (String).
*
* @param ds The source DataSource.
*
* @return The content object.
* @exception IOException
*/
public Object getContent(DataSource ds) throws IOException {
InputStream is = ds.getInputStream();
InputStreamReader reader;
// process any encoding to make sure the chars get transformed into the
// correct byte types.
try {
String charset = getCharSet(ds.getContentType());
reader = new InputStreamReader(is, charset);
} catch (Exception ex) {
throw new UnsupportedEncodingException(ex.toString());
}
StringBuffer result = new StringBuffer(1024);
char[] buffer = new char[32768];
int count;
while ((count = reader.read(buffer)) > 0) {
result.append(buffer, 0, count);
}
return result.toString();
}
/**
* Write an object of "our" type out to the provided
* output stream. The content type might modify the
* result based on the content type parameters.
*
* @param object The object to write.
* @param contentType
* The content mime type, including parameters.
* @param outputstream
* The target output stream.
*
* @throws IOException
*/
public void writeTo(Object o, String contentType, OutputStream outputstream) throws IOException {
String s;
if (o instanceof String) {
s = (String) o;
} else if (o != null) {
s = o.toString();
} else {
return;
}
// process any encoding to make sure the chars get transformed into the
// correct byte types.
OutputStreamWriter writer;
try {
String charset = getCharSet(contentType);
writer = new OutputStreamWriter(outputstream, charset);
} catch (Exception ex) {
ex.printStackTrace();
throw new UnsupportedEncodingException(ex.toString());
}
writer.write(s);
writer.flush();
}
/**
* get the character set from content type
* @param contentType
* @return
* @throws ParseException
*/
protected String getCharSet(String contentType) throws Exception {
ContentType type = new ContentType(contentType);
String charset = type.getParameter("charset");
if (charset == null) {
charset = "us-ascii";
}
return MimeUtility.javaCharset(charset);
}
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011566 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHand0000664 0001750 0001750 00000002226 10676151012 032221 0 ustar brian brian /**
* 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.handlers;
import javax.activation.ActivationDataFlavor;
/**
* @version $Rev: 579145 $ $Date: 2007-09-25 05:16:58 -0400 (Tue, 25 Sep 2007) $
*/
public class TextXmlHandler extends AbstractTextHandler {
public TextXmlHandler() {
super(new ActivationDataFlavor(String.class, "text/xml", "XML Text"));
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011570 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHa0000664 0001750 0001750 00000004667 10676151012 032266 0 ustar brian brian /**
* 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.handlers;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.OutputStream;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataContentHandler;
import javax.activation.DataSource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMultipart;
/**
* @version $Rev: 579145 $ $Date: 2007-09-25 05:16:58 -0400 (Tue, 25 Sep 2007) $
*/
public class MultipartHandler implements DataContentHandler {
private final DataFlavor flavour;
public MultipartHandler() {
flavour = new ActivationDataFlavor(MimeMultipart.class, "multipart/mixed", "Multipart MIME");
}
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{flavour};
}
public Object getTransferData(DataFlavor df, DataSource ds) throws UnsupportedFlavorException, IOException {
return flavour.equals(df) ? getContent(ds) : null;
}
public Object getContent(DataSource ds) throws IOException {
try {
return new MimeMultipart(ds);
} catch (MessagingException e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
}
public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
if (obj instanceof MimeMultipart) {
MimeMultipart mp = (MimeMultipart) obj;
try {
mp.writeTo(os);
} catch (MessagingException e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
}
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractImageHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractIma0000664 0001750 0001750 00000007464 11567442365 032242 0 ustar brian brian /**
* 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.handlers;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataContentHandler;
import javax.activation.DataSource;
import javax.activation.UnsupportedDataTypeException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
/**
* @version $Rev: 1127882 $ $Date: 2011-05-26 08:17:57 -0400 (Thu, 26 May 2011) $
*/
public class AbstractImageHandler implements DataContentHandler {
private final ActivationDataFlavor flavour;
public AbstractImageHandler(ActivationDataFlavor flavour) {
this.flavour = flavour;
}
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{flavour};
}
public Object getTransferData(DataFlavor dataFlavor, DataSource dataSource) throws UnsupportedFlavorException, IOException {
return flavour.equals(dataFlavor) ? getContent(dataSource) : null;
}
public Object getContent(DataSource ds) throws IOException {
Iterator i = ImageIO.getImageReadersByMIMEType(flavour.getMimeType());
if (!i.hasNext()) {
throw new UnsupportedDataTypeException("Unknown image type " + flavour.getMimeType());
}
ImageReader reader = (ImageReader) i.next();
ImageInputStream iis = ImageIO.createImageInputStream(ds.getInputStream());
reader.setInput(iis);
return reader.read(0);
}
public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
Iterator i = ImageIO.getImageWritersByMIMEType(flavour.getMimeType());
if (!i.hasNext()) {
throw new UnsupportedDataTypeException("Unknown image type " + flavour.getMimeType());
}
ImageWriter writer = (ImageWriter) i.next();
writer.setOutput(ImageIO.createImageOutputStream(os));
if (obj instanceof RenderedImage) {
writer.write((RenderedImage) obj);
} else if (obj instanceof BufferedImage) {
BufferedImage buffered = (BufferedImage) obj;
writer.write(new IIOImage(buffered.getRaster(), null, null));
} else if (obj instanceof Image) {
Image image = (Image) obj;
BufferedImage buffered = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = buffered.createGraphics();
graphics.drawImage(image, 0, 0, null, null);
writer.write(new IIOImage(buffered.getRaster(), null, null));
} else {
throw new UnsupportedDataTypeException("Unknown image type " + obj.getClass().getName());
}
os.flush();
}
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHan0000664 0001750 0001750 00000002232 10676151012 032216 0 ustar brian brian /**
* 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.handlers;
import javax.activation.ActivationDataFlavor;
/**
* @version $Rev: 579145 $ $Date: 2007-09-25 05:16:58 -0400 (Tue, 25 Sep 2007) $
*/
public class TextHtmlHandler extends AbstractTextHandler {
public TextHtmlHandler() {
super(new ActivationDataFlavor(String.class, "text/html", "HTML Text"));
}
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHandler.java geronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHan0000664 0001750 0001750 00000002260 10676151012 032116 0 ustar brian brian /**
* 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.handlers;
import java.awt.Image;
import javax.activation.ActivationDataFlavor;
/**
* @version $Rev: 579145 $ $Date: 2007-09-25 05:16:58 -0400 (Tue, 25 Sep 2007) $
*/
public class ImageGifHandler extends AbstractImageHandler {
public ImageGifHandler() {
super(new ActivationDataFlavor(Image.class, "image/gif", "GIF Image"));
}
}
geronimo-javamail-1.4-provider-1.8.3/src/site/ 0000775 0001750 0001750 00000000000 11703373731 017766 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/site/site.xml 0000664 0001750 0001750 00000002140 10474735322 021453 0 ustar brian brian
${parentProject}
${modules}
${reports}
geronimo-javamail-1.4-provider-1.8.3/src/site/apt/ 0000775 0001750 0001750 00000000000 11703373731 020552 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/ 0000775 0001750 0001750 00000000000 11703373727 020006 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/resources/ 0000775 0001750 0001750 00000000000 11703373727 022020 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/resources/messages/ 0000775 0001750 0001750 00000000000 11703373727 023627 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/resources/messages/simple.msg 0000664 0001750 0001750 00000000332 11140646643 025621 0 ustar brian brian Date: Sat, 11 Oct 2008 00:48:01 +0200 (CEST)
From: test@localhost
To: test@localhost
Subject: Test Foo
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Foo Bar
geronimo-javamail-1.4-provider-1.8.3/src/test/resources/messages/multipart.msg 0000664 0001750 0001750 00000001053 11140646643 026352 0 ustar brian brian Date: Sat, 11 Oct 2008 00:48:01 +0200 (CEST)
From: test@localhost
To: test@localhost
Message-ID: urn:uuid:219365EB848AD9CACB1223678880948
Subject: Test
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----=_Part_0_6727097.1223678881682"
------=_Part_0_6727097.1223678881682
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
First part
------=_Part_0_6727097.1223678881682
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Second part
------=_Part_0_6727097.1223678881682--
geronimo-javamail-1.4-provider-1.8.3/src/test/resources/imap/ 0000775 0001750 0001750 00000000000 11703373727 022746 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/resources/imap/multipart.bodystructure 0000664 0001750 0001750 00000000333 11156270711 027615 0 ustar brian brian (("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 1281 28 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 1510 33 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "0016e6d976ed8989d30464e986d1") NIL NIL) geronimo-javamail-1.4-provider-1.8.3/src/test/java/ 0000775 0001750 0001750 00000000000 11703373727 020727 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/ 0000775 0001750 0001750 00000000000 11703373727 021516 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/ 0000775 0001750 0001750 00000000000 11703373727 022737 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/ 0000775 0001750 0001750 00000000000 11703373727 024556 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/ 0000775 0001750 0001750 00000000000 11703373727 026342 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/ 0000775 0001750 0001750 00000000000 11703373727 027476 5 ustar brian brian geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/ 0000775 0001750 0001750 00000000000 11703373727 030424 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/connection/ geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/connectio0000775 0001750 0001750 00000000000 11703373727 032326 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000201 00000000000 011556 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructureTest.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/connectio0000664 0001750 0001750 00000003301 11375023623 032315 0 ustar brian brian /**
* 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.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import javax.mail.internet.MimeMessage;
import junit.framework.TestCase;
import org.apache.geronimo.javamail.store.imap.IMAPStoreTest;
public class IMAPBodyStructureTest extends TestCase {
public void testMultipart() throws Exception {
InputStream in = IMAPStoreTest.class.getResourceAsStream("/imap/multipart.bodystructure");
BufferedReader r = new BufferedReader(new InputStreamReader(in));
try {
IMAPResponseTokenizer tokenizer = new IMAPResponseTokenizer(r.readLine().getBytes("ISO8859-1"));
IMAPBodyStructure s = new IMAPBodyStructure(tokenizer);
assertNull(s.disposition.getDisposition());
assertNull(s.md5Hash);
} finally {
in.close();
}
}
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStoreTest.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStore0000664 0001750 0001750 00000010623 11265117504 032104 0 ustar brian brian /**
* 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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import junit.framework.TestCase;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetupTest;
public class IMAPStoreTest extends TestCase {
private GreenMail greenMail;
private Message[] messages;
//@Override
protected void setUp() throws Exception {
// Setup GreenMail
greenMail = new GreenMail(ServerSetupTest.SMTP_IMAP);
greenMail.start();
greenMail.setUser("test@localhost", "test", "test");
// Setup JavaMail session
Properties props = new Properties();
props.setProperty("mail.smtp.port", String.valueOf(greenMail.getSmtp().getPort()));
props.setProperty("mail.imap.port", String.valueOf(greenMail.getImap().getPort()));
System.out.println("stmp.port: " + greenMail.getSmtp().getPort());
System.out.println("imap port: " + greenMail.getImap().getPort());
Session session = Session.getInstance(props);
// Send messages for the current test to GreenMail
sendMessage(session, "/messages/multipart.msg");
sendMessage(session, "/messages/simple.msg");
// Load the message from IMAP
Store store = session.getStore("imap");
store.connect("localhost", "test", "test");
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
this.messages = folder.getMessages();
assertEquals(2, messages.length);
}
//@Override
protected void tearDown() throws Exception {
greenMail.stop();
}
private void sendMessage(Session session, String msgFile) throws Exception {
MimeMessage message;
InputStream in = IMAPStoreTest.class.getResourceAsStream(msgFile);
try {
message = new MimeMessage(session, in);
} finally {
in.close();
}
Transport.send(message, new Address[] { new InternetAddress("test@localhost") });
}
public void testMessages() throws Exception {
MimeMessage msg1 = (MimeMessage)messages[0];
Object content = msg1.getContent();
assertTrue(content instanceof MimeMultipart);
MimeMultipart multipart = (MimeMultipart)content;
assertEquals("First part", multipart.getBodyPart(0).getContent());
assertEquals("Second part", multipart.getBodyPart(1).getContent());
checkMessage(msg1);
MimeMessage msg2 = (MimeMessage)messages[1];
assertEquals("Foo Bar", msg2.getContent().toString().trim());
checkMessage(msg2);
}
private void checkMessage(MimeMessage input) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
input.writeTo(out);
Properties props = new Properties();
Session s = Session.getInstance(props);
byte [] inputData = out.toByteArray();
System.out.println(new String(inputData, 0, inputData.length));
MimeMessage output = new MimeMessage(s, new ByteArrayInputStream(inputData));
assertEquals(input.getContentType().toLowerCase(), output.getContentType().toLowerCase());
}
}
geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/pop3/ 0000775 0001750 0001750 00000000000 11703373727 030357 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3StoreTest.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3Store0000664 0001750 0001750 00000010632 11265117504 032032 0 ustar brian brian /**
* 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.pop3;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import junit.framework.TestCase;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetupTest;
public class POP3StoreTest extends TestCase {
private GreenMail greenMail;
private Message[] messages;
//@Override
protected void setUp() throws Exception {
// Setup GreenMail
greenMail = new GreenMail(ServerSetupTest.SMTP_POP3);
greenMail.start();
greenMail.setUser("test@localhost", "test", "test");
// Setup JavaMail session
Properties props = new Properties();
props.setProperty("mail.smtp.port", String.valueOf(greenMail.getSmtp().getPort()));
props.setProperty("mail.pop3.port", String.valueOf(greenMail.getPop3().getPort()));
System.out.println("stmp.port: " + greenMail.getSmtp().getPort());
System.out.println("pop3 port: " + greenMail.getPop3().getPort());
Session session = Session.getInstance(props);
// Send messages for the current test to GreenMail
sendMessage(session, "/messages/multipart.msg");
sendMessage(session, "/messages/simple.msg");
// Load the message from POP3
Store store = session.getStore("pop3");
store.connect("localhost", "test", "test");
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
this.messages = folder.getMessages();
assertEquals(2, messages.length);
}
//@Override
protected void tearDown() throws Exception {
greenMail.stop();
}
private void sendMessage(Session session, String msgFile) throws Exception {
MimeMessage message;
InputStream in = POP3StoreTest.class.getResourceAsStream(msgFile);
try {
message = new MimeMessage(session, in);
} finally {
in.close();
}
Transport.send(message, new Address[] { new InternetAddress("test@localhost") });
}
public void testMessages() throws Exception {
MimeMessage msg1 = (MimeMessage)messages[0];
Object content = msg1.getContent();
assertTrue(content instanceof MimeMultipart);
MimeMultipart multipart = (MimeMultipart)content;
assertEquals("First part", multipart.getBodyPart(0).getContent());
assertEquals("Second part", multipart.getBodyPart(1).getContent());
checkMessage(msg1);
MimeMessage msg2 = (MimeMessage)messages[1];
assertEquals("Foo Bar", msg2.getContent().toString().trim());
checkMessage(msg2);
}
private void checkMessage(MimeMessage input) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
input.writeTo(out);
Properties props = new Properties();
Session s = Session.getInstance(props);
byte [] inputData = out.toByteArray();
System.out.println(new String(inputData, 0, inputData.length));
MimeMessage output = new MimeMessage(s, new ByteArrayInputStream(inputData));
assertEquals(input.getContentType().toLowerCase(), output.getContentType().toLowerCase());
}
}
geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/ 0000775 0001750 0001750 00000000000 11703373727 030142 5 ustar brian brian ././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011564 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTest.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTes0000664 0001750 0001750 00000003063 11026734371 032306 0 ustar brian brian /**
* 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.handlers;
import java.awt.datatransfer.DataFlavor;
/**
* @version $Rev: 669902 $ $Date: 2008-06-20 10:04:41 -0400 (Fri, 20 Jun 2008) $
*/
public class TextHtmlTest extends AbstractHandler {
public void testDataFlavor() {
DataFlavor[] flavours = dch.getTransferDataFlavors();
assertEquals(1, flavours.length);
DataFlavor flavor = flavours[0];
assertEquals(String.class, flavor.getRepresentationClass());
assertEquals("text/html", flavor.getMimeType());
assertEquals("HTML Text", flavor.getHumanPresentableName());
}
protected void setUp() throws Exception {
super.setUp();
dch = new TextHtmlHandler();
mimeType = "text/html";
}
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011567 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHandler.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHan0000664 0001750 0001750 00000004315 11026734371 032254 0 ustar brian brian /**
* 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.handlers;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import javax.activation.DataContentHandler;
import javax.activation.DataSource;
import junit.framework.TestCase;
/**
* @version $Rev: 669902 $ $Date: 2008-06-20 10:04:41 -0400 (Fri, 20 Jun 2008) $
*/
public abstract class AbstractHandler extends TestCase {
protected DataContentHandler dch;
protected String mimeType;
public void testGetContent() throws Exception {
final byte[] bytes = "Hello World".getBytes();
DataSource ds = new DataSource() {
public InputStream getInputStream() {
return new ByteArrayInputStream(bytes);
}
public OutputStream getOutputStream() {
throw new UnsupportedOperationException();
}
public String getContentType() {
return mimeType;
}
public String getName() {
throw new UnsupportedOperationException();
}
};
Object o = dch.getContent(ds);
assertEquals("Hello World", o);
}
public void testWriteTo() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
dch.writeTo("Hello World", mimeType, baos);
assertEquals("Hello World", baos.toString());
}
}
././@LongLink 0000000 0000000 0000000 00000000154 00000000000 011565 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTest.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTe0000664 0001750 0001750 00000003070 11026734371 032260 0 ustar brian brian /**
* 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.handlers;
import java.awt.datatransfer.DataFlavor;
/**
* @version $Rev: 669902 $ $Date: 2008-06-20 10:04:41 -0400 (Fri, 20 Jun 2008) $
*/
public class TextPlainTest extends AbstractHandler {
public void testDataFlavor() {
DataFlavor[] flavours = dch.getTransferDataFlavors();
assertEquals(1, flavours.length);
DataFlavor flavor = flavours[0];
assertEquals(String.class, flavor.getRepresentationClass());
assertEquals("text/plain", flavor.getMimeType());
assertEquals("Plain Text", flavor.getHumanPresentableName());
}
protected void setUp() throws Exception {
super.setUp();
dch = new TextPlainHandler();
mimeType = "text/plain";
}
}
././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011563 L ustar root root geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest.java geronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest0000664 0001750 0001750 00000003056 11026734371 032330 0 ustar brian brian /**
* 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.handlers;
import java.awt.datatransfer.DataFlavor;
/**
* @version $Rev: 669902 $ $Date: 2008-06-20 10:04:41 -0400 (Fri, 20 Jun 2008) $
*/
public class TextXmlTest extends AbstractHandler {
public void testDataFlavor() {
DataFlavor[] flavours = dch.getTransferDataFlavors();
assertEquals(1, flavours.length);
DataFlavor flavor = flavours[0];
assertEquals(String.class, flavor.getRepresentationClass());
assertEquals("text/xml", flavor.getMimeType());
assertEquals("XML Text", flavor.getHumanPresentableName());
}
protected void setUp() throws Exception {
super.setUp();
dch = new TextXmlHandler();
mimeType = "text/xml";
}
}