geronimo-javamail-1.4-provider-1.8.3/0000775000175000017500000000000011703373731016233 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/pom.xml0000664000175000017500000001101311611717425017544 0ustar brianbrian 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/0000775000175000017500000000000011703373731017022 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/0000775000175000017500000000000011703373731017746 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/resources/0000775000175000017500000000000011703373731021760 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/resources/META-INF/0000775000175000017500000000000011703373731023120 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/resources/META-INF/mailcap0000664000175000017500000000334511412700776024456 0ustar brianbrian## ## 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.map0000664000175000017500000000234410474735322030457 0ustar brianbrian## ## 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.providers0000664000175000017500000000510011124332750030253 0ustar brianbrian## ## 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/0000775000175000017500000000000011703373731023133 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/0000775000175000017500000000000011703373731025150 5ustar brianbrian././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPSSLStoregeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664000175000017500000000015611345242342032015 0ustar brianbrianorg.apache.geronimo.javamail.store.nntp.NNTPSSLStore # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3Storegeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664000175000017500000000015311345242342032012 0ustar brianbrianorg.apache.geronimo.javamail.store.pop3.POP3Store # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.MultipartHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000016011345242342031742 0ustar brianbrianorg.apache.geronimo.javamail.handlers.MultipartHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPStoregeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664000175000017500000000015311345242342032012 0ustar brianbrianorg.apache.geronimo.javamail.store.nntp.NNTPStore # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPSSSLStoregeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664000175000017500000000015611345242342032015 0ustar brianbrianorg.apache.geronimo.javamail.store.imap.IMAPSSLStore # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.RFC822MessageHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000016411345242342031746 0ustar brianbrianorg.apache.geronimo.javamail.handlers.RFC822MessageHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextHtmlHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000015711345242342031750 0ustar brianbrianorg.apache.geronimo.javamail.handlers.TextHtmlHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017700000000000011572 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPSSLTransportgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664000175000017500000000016411345242342032007 0ustar brianbrianorg.apache.geronimo.javamail.transport.smtp.SMTPSTransport # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPTransportgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664000175000017500000000016311345242342032006 0ustar brianbrianorg.apache.geronimo.javamail.transport.smtp.SMTPTransport # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPTransportgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664000175000017500000000016311345242342032006 0ustar brianbrianorg.apache.geronimo.javamail.transport.nntp.NNTPTransport # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3SSLStoregeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664000175000017500000000015611345242342032015 0ustar brianbrianorg.apache.geronimo.javamail.store.pop3.POP3SSLStore # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPStoregeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.store0000664000175000017500000000015311345242342032012 0ustar brianbrianorg.apache.geronimo.javamail.store.imap.IMAPStore # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextPlainHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000016011345242342031742 0ustar brianbrianorg.apache.geronimo.javamail.handlers.TextPlainHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageJpegHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000016011345242342031742 0ustar brianbrianorg.apache.geronimo.javamail.handlers.ImageJpegHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageGifHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000015711345242342031750 0ustar brianbrianorg.apache.geronimo.javamail.handlers.ImageGifHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextXmlHandlergeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.handl0000664000175000017500000000015611345242342031747 0ustar brianbrianorg.apache.geronimo.javamail.handlers.TextXmlHandler # This is directly mapped back to the same class name ././@LongLink0000000000000000000000000000017700000000000011572 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPSSLTransportgeronimo-javamail-1.4-provider-1.8.3/src/main/resources/OSGI-INF/providers/org.apache.javamail.trans0000664000175000017500000000016611345242342032011 0ustar brianbrianorg.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/0000775000175000017500000000000011703373727020674 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/0000775000175000017500000000000011703373727021463 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/0000775000175000017500000000000011703373727022704 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/0000775000175000017500000000000011703373727024523 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/0000775000175000017500000000000011703373731026302 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/0000775000175000017500000000000011703373731030336 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/0000775000175000017500000000000011703373731031315 5ustar brianbrian././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPT0000664000175000017500000002360311033126004032130 0ustar brianbrian/* * 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(); } } ././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/StringListInputStream.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/Strin0000664000175000017500000000642510474735322032350 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPC0000664000175000017500000006203211404405474032123 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000016500000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPSSLTransport.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPS0000664000175000017500000000222311032465542032136 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPR0000664000175000017500000001551211032465542032142 0ustar brianbrian/* * 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/0000775000175000017500000000000011703373731031321 5ustar brianbrian././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPR0000664000175000017500000001337311033126004032141 0ustar brianbrian/* * 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(); } } ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPS0000664000175000017500000000441210474735322032155 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPT0000664000175000017500000006051111441725756032165 0ustar brianbrian/* * 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 :

*

*

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); } } ././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPM0000664000175000017500000001403510474735322032151 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000020200000000000011557 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPA0000664000175000017500000000442210474735322032134 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPS0000664000175000017500000000216710474735322032162 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000020000000000000011555 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/Malfo0000664000175000017500000000241010474735322032301 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000017300000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPT0000664000175000017500000000245710474735322032165 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPC0000664000175000017500000013773211441725756032156 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000017700000000000011572 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPA0000664000175000017500000000440110474735322032131 0ustar brianbrian/* * 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/0000775000175000017500000000000011703373731027257 5ustar brianbrian././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStrea0000664000175000017500000001063611027776137032165 0ustar brianbrian/* * 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'); } } ././@LongLink0000000000000000000000000000015100000000000011562 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MailConnection.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MailConnection.0000664000175000017500000010044511265117504032163 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CommandFailedException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CommandFailedEx0000664000175000017500000000226710716317503032167 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceInputStrea0000664000175000017500000000651711025715072032263 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader0000664000175000017500000001130110721056121032056 0ustar brianbrian/* * 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 } } ././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandE0000664000175000017500000000227310716317503032176 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatE0000664000175000017500000000227210716317503032257 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CountingOutputStream.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/CountingOutputS0000664000175000017500000000335111030212510032312 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStre0000664000175000017500000000476011025715252032321 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ConnectionException.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ConnectionExcep0000664000175000017500000000225310716317503032266 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/util/ProtocolPropert0000664000175000017500000002201210716317503032352 0ustar brianbrian/** * 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/0000775000175000017500000000000011703373730027435 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/0000775000175000017500000000000011703373730030414 5ustar brianbrian././@LongLink0000000000000000000000000000016000000000000011562 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroup0000664000175000017500000003201211051310624032116 0ustar brianbrian/* * 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); } } } ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLStore.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLSt0000664000175000017500000000306211032465542032006 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolde0000664000175000017500000003215710474735322032103 0ustar brianbrian/* * 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"); } } ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessage.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessa0000664000175000017500000003110110474735322032106 0ustar brianbrian/* * 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/0000775000175000017500000000000011703373730031715 5ustar brianbrian././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Range.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Ra0000664000175000017500000002141610474735322032211 0ustar brianbrian/* * 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); } } } ././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcGroup.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NN0000664000175000017500000001214010474735322032154 0ustar brianbrian/* * 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"); } } ././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrc.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NN0000664000175000017500000001271710474735322032166 0ustar brianbrian/* * 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(); } } ././@LongLink0000000000000000000000000000016600000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NN0000664000175000017500000000421611404405474032155 0ustar brianbrian/* * 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"); } } ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/RangeList.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Ra0000664000175000017500000001677010474735322032220 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore0000664000175000017500000002142611032465542032136 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootF0000664000175000017500000003343310474735322032101 0ustar brianbrian/* * 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/0000775000175000017500000000000011703373730030363 5ustar brianbrian././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootgeronimo-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/connectio0000775000175000017500000000000011703373730032265 5ustar brianbrian././@LongLink0000000000000000000000000000020200000000000011557 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000641610716317503032275 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000020200000000000011557 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListRightsResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000341010716317503032264 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000016500000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlags.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000245310716317503032272 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000001426310716317503032274 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000020000000000000011555 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000413610716317503032272 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespace.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000332510716317503032271 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000020400000000000011561 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPServerStatusResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000333710716317503032274 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017300000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageText.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000314710716317503032273 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPOkResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000453610716317503032276 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUid.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000245510716317503032274 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPDateFormat.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000471410716317503032274 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxStatus.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000001356210716317503032275 0ustar brianbrian/** * 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; } } } ././@LongLink0000000000000000000000000000017700000000000011572 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000244410716317503032272 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000020700000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponseHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000306110716317503032266 0ustar brianbrian/** * 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); } ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000467711375537364032317 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSizeResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000316310716317503032271 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternalDate.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000270110716317503032266 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000501510716317503032267 0ustar brianbrian/** * 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()]); } } ././@LongLink0000000000000000000000000000020100000000000011556 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaRootResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000370410716317503032272 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchDataItem.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000361610716317503032274 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000001755411156270711032300 0ustar brianbrian/** * 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"); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000003661111404405474032275 0ustar brianbrian/** * 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); } } } ././@LongLink0000000000000000000000000000017300000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageSize.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000230610716317503032267 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000001113110716317503032263 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000020000000000000011555 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMyRightsResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000257610716317503032300 0ustar brianbrian/** * 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()); } } ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPEnvelope.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000476010716317503032275 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000020100000000000011556 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000013657511404405474032307 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017300000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000351510716317503032272 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000020400000000000011561 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPContinuationResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000277010716317503032274 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000017300000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000002407310716317503032274 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000445311140646643032276 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000356110716317503032273 0ustar brianbrian/** * 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(); } } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlagsResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000274610716317503032277 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000020000000000000011555 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchDateFormat.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000461110716317503032270 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000005627310721056121032273 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000021744211375023623032277 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternetHeader.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000422610716317503032272 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000020100000000000011556 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespaceResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000747510716317503032303 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017500000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchBodyPart.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000513710716317503032274 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000620710716317503032273 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000020600000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPPermanentFlagsResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000320310716317503032264 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000014152611375023623032276 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000001044711375023623032273 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000542511156270711032272 0ustar brianbrian/** * 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; } } } } ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMultipartDataSource.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMulti0000664000175000017500000000431210716317503032046 0ustar brianbrian/** * 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]; } } ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNamespaceFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNames0000664000175000017500000000343710716317503032026 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore0000664000175000017500000005570311077607735032074 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootF0000664000175000017500000001053010716317503032004 0ustar brianbrian/** * 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"); } } ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessa0000664000175000017500000013631011375023623032027 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000016100000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeB0000664000175000017500000002760110716317503031753 0ustar brianbrian/** * 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); } } } ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLSt0000664000175000017500000000305610721056121031720 0ustar brianbrian/** * 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.java0000664000175000017500000000516211025715072031625 0ustar brianbrian/** * 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)); } } ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.ja0000664000175000017500000002201411025715072032132 0ustar brianbrian/** * 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()); } } } ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolde0000664000175000017500000026603111165376024032017 0ustar brianbrian/** * 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: *
      *
    1. it may return false indicting the operation failed
    2. *
    3. 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
    4. *
    5. 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
    6. * *
* 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(); } } ././@LongLink0000000000000000000000000000016400000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttachedMessage.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttac0000664000175000017500000001036710716317503032017 0ustar brianbrian/** * 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/0000775000175000017500000000000011703373730030316 5ustar brianbrian././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Const0000664000175000017500000000243610721056121031765 0ustar brianbrian/* * 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; }././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Messa0000664000175000017500000003333410721056121031750 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootgeronimo-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/connectio0000775000175000017500000000000011703373730032220 5ustar brianbrian././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664000175000017500000000443310721056121032215 0ustar brianbrian/* * 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()); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664000175000017500000002123410721056121032213 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000017400000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664000175000017500000000643010721056121032214 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000017200000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664000175000017500000005705611515320247032233 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000017600000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/connectio0000664000175000017500000000436010721056121032214 0ustar brianbrian/* * 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; } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootF0000664000175000017500000001074710721056121031734 0ustar brianbrian/** * 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; } } ././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folde0000664000175000017500000004207311055035501031731 0ustar brianbrian/* * 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); } } ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store0000664000175000017500000002773211061534403032003 0ustar brianbrian/* * 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(); } } ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLSt0000664000175000017500000000306110721056121031642 0ustar brianbrian/** * 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/0000775000175000017500000000000011703373727031326 5ustar brianbrian././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Plain0000664000175000017500000000712710474735322032320 0ustar brianbrian/* * 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"); } } } ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/CramM0000664000175000017500000001316311404405474032245 0ustar brianbrian/* * 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 } } ././@LongLink0000000000000000000000000000017100000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Authe0000664000175000017500000000762311276027406032322 0ustar brianbrian/** * 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; } } } ././@LongLink0000000000000000000000000000016600000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/SASLAuthenticator.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/SASLA0000664000175000017500000001464310716317503032115 0ustar brianbrian/* * 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. } } } } } ././@LongLink0000000000000000000000000000016700000000000011571 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Login0000664000175000017500000001043110474735322032315 0ustar brianbrian/* * 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"); } } ././@LongLink0000000000000000000000000000017300000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Diges0000664000175000017500000005146411515320247032304 0ustar brianbrian/* * 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; } } } ././@LongLink0000000000000000000000000000017000000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/authentication/Clien0000664000175000017500000000671310474735322032307 0ustar brianbrian/* * 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/0000775000175000017500000000000011703373727030107 5ustar brianbrian././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/RFC822MessageHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/RFC822Messa0000664000175000017500000001075011265061707031667 0ustar brianbrian/** * 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); } } } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHa0000664000175000017500000000223610676151012032203 0ustar brianbrian/** * 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")); } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHa0000664000175000017500000000226410676151012032124 0ustar brianbrian/** * 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")); } } ././@LongLink0000000000000000000000000000016200000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTex0000664000175000017500000001101411026734371032245 0ustar brianbrian/** * 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); } } ././@LongLink0000000000000000000000000000015500000000000011566 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHand0000664000175000017500000000222610676151012032221 0ustar brianbrian/** * 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")); } } ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHa0000664000175000017500000000466710676151012032266 0ustar brianbrian/** * 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); } } } } ././@LongLink0000000000000000000000000000016300000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractImageHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/AbstractIma0000664000175000017500000000746411567442365032242 0ustar brianbrian/** * 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(); } } ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHan0000664000175000017500000000223210676151012032216 0ustar brianbrian/** * 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")); } } ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHandler.javageronimo-javamail-1.4-provider-1.8.3/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHan0000664000175000017500000000226010676151012032116 0ustar brianbrian/** * 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/0000775000175000017500000000000011703373731017766 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/site/site.xml0000664000175000017500000000214010474735322021453 0ustar brianbrian ${parentProject} ${modules} ${reports} geronimo-javamail-1.4-provider-1.8.3/src/site/apt/0000775000175000017500000000000011703373731020552 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/0000775000175000017500000000000011703373727020006 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/resources/0000775000175000017500000000000011703373727022020 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/resources/messages/0000775000175000017500000000000011703373727023627 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/resources/messages/simple.msg0000664000175000017500000000033211140646643025621 0ustar brianbrianDate: 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.msg0000664000175000017500000000105311140646643026352 0ustar brianbrianDate: 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/0000775000175000017500000000000011703373727022746 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/resources/imap/multipart.bodystructure0000664000175000017500000000033311156270711027615 0ustar brianbrian(("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/0000775000175000017500000000000011703373727020727 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/0000775000175000017500000000000011703373727021516 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/0000775000175000017500000000000011703373727022737 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/0000775000175000017500000000000011703373727024556 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/0000775000175000017500000000000011703373727026342 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/0000775000175000017500000000000011703373727027476 5ustar brianbriangeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/0000775000175000017500000000000011703373727030424 5ustar brianbrian././@LongLink0000000000000000000000000000014700000000000011567 Lustar rootrootgeronimo-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/connectio0000775000175000017500000000000011703373727032326 5ustar brianbrian././@LongLink0000000000000000000000000000020100000000000011556 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructureTest.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/connectio0000664000175000017500000000330111375023623032315 0ustar brianbrian/** * 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(); } } } ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStoreTest.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStore0000664000175000017500000001062311265117504032104 0ustar brianbrian/** * 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/0000775000175000017500000000000011703373727030357 5ustar brianbrian././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3StoreTest.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3Store0000664000175000017500000001063211265117504032032 0ustar brianbrian/** * 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/0000775000175000017500000000000011703373727030142 5ustar brianbrian././@LongLink0000000000000000000000000000015300000000000011564 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTest.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTes0000664000175000017500000000306311026734371032306 0ustar brianbrian/** * 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"; } } ././@LongLink0000000000000000000000000000015600000000000011567 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHandler.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHan0000664000175000017500000000431511026734371032254 0ustar brianbrian/** * 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()); } } ././@LongLink0000000000000000000000000000015400000000000011565 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTest.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTe0000664000175000017500000000307011026734371032260 0ustar brianbrian/** * 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"; } } ././@LongLink0000000000000000000000000000015200000000000011563 Lustar rootrootgeronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest.javageronimo-javamail-1.4-provider-1.8.3/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest0000664000175000017500000000305611026734371032330 0ustar brianbrian/** * 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"; } }