Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/mrbook/mrpostman/another/AnotherMailSession.java


1   /*
2    * -*- mode: java; c-basic-indent: 4; indent-tabs-mode: nil -*-
3    * :indentSize=4:noTabs=true:tabSize=4:indentOnTab=true:indentOnEnter=true:mode=java:
4    * ex: set tabstop=4 expandtab:
5    *
6    * MrPostman - webmail <-> email gateway
7    * Copyright (C) 2002-2003 MrPostman Development Group
8    * Projectpage: http://mrbook.org/mrpostman/
9    *
10   *
11   * This program is free software; you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as published by
13   * the Free Software Foundation; either version 2 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * In particular, this implies that users are responsible for
21   * using MrPostman after reading the terms and conditions given
22   * by their web-mail provider.
23   *
24   * You should have received a copy of the GNU General Public License
25   * Named LICENSE in the base directory of this distribution,
26   * if not, write to the Free Software
27   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28   */
29  
30  package org.mrbook.mrpostman.another;
31  
32  import com.sonalb.net.http.HTTPRedirectHandler;
33  import com.sonalb.net.http.cookie.CookieJar;
34  
35  import org.mrbook.mrpostman.MailSessionException;
36  import org.mrbook.mrpostman.ModuleInfo;
37  import org.mrbook.mrpostman.ModuleOption;
38  
39  /*
40      Another.com module (v0.6) for MrPostman.
41      Supports retrieval of all Inbox messages.
42  
43      Ensure your account username in your mail program is set abc@another.com
44      Note: MrPostman will not recognize the custom addresses you may have created.
45      Ensure you have created a folder on another.com called 'READ'.
46  
47      This module currently will NOT delete messages.
48      This module cannot determine if a message is read or unread (another.com doesn't
49      indicate this to us except by colour - which is user configurable!).
50      Rather than marking a message as read it will move it from the INBOX to a folder
51      called READ. You must have a folder with this name for this to work!
52      All message bodies are returned as HTML.
53  
54      Written by Chris Humphreys Nov 2002 <chris@sonicdreams.org>
55      Based the YahooMainSession by Hector Urtubia <urtubia@mrbook.org>
56      Uses jCookie by Sonal Bansal.
57  
58      This program is free software; you can redistribute it and/or modify
59      it under the terms of the GNU General Public License as published by
60      the Free Software Foundation; either version 2 of the License, or
61      (at your option) any later version.
62  
63      This program is distributed in the hope that it will be useful,
64      but WITHOUT ANY WARRANTY; without even the implied warranty of
65      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
66      GNU General Public License for more details.
67  
68      You should have received a copy of the GNU General Public License
69      along with this program; if not, write to the Free Software
70      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
71  */
72  import org.mrbook.mrpostman.WebMailSession;
73  import org.mrbook.mrpostman.another.summary.HeaderInfo;
74  
75  import java.io.BufferedReader;
76  import java.io.ByteArrayOutputStream;
77  import java.io.IOException;
78  import java.io.InputStream;
79  import java.io.InputStreamReader;
80  import java.io.PrintWriter;
81  
82  import java.net.HttpURLConnection;
83  import java.net.URL;
84  
85  import java.util.Iterator;
86  import java.util.LinkedHashSet;
87  import java.util.LinkedList;
88  import java.util.logging.Level;
89  import java.util.logging.Logger;
90  import java.util.regex.Matcher;
91  import java.util.regex.Pattern;
92  
93  
94  public class AnotherMailSession extends WebMailSession {
95      public static final String CVSID = "$Id: AnotherMailSession.java,v 1.14 2003/02/09 23:38:13 lbruand Exp $";
96      private static final String[] AUTHORS = {"Chris Humphreys <chris@sonicdreams.org>"};
97      private static final ModuleOption[] OPTIONS = {
98          new ModuleOption("another.markasread", "true"), new ModuleOption("another.debugpagehtml", "false"),
99          new ModuleOption("another.createsummary", "true")
100     };
101 
102     /**
103     * Our hardcoded ModuleInfo data.
104     */
105     private static final ModuleInfo MODULE_INFO = new ModuleInfo("another", AUTHORS, "0.5",
106             "http://mrpostman.sourceforge.net/updates/another", "/en/another/index.html", OPTIONS);
107     private static Logger logger = Logger.getLogger("org.mrbook.mrpostman.another.AnotherMailSession");
108     private static final String UserAgent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:0.9.4) Gecko/20010914";
109     private static final String baseUrl = "http://www.another.com/";
110     private static final String httpLoginPageURL = baseUrl;
111     private static String loginURL = baseUrl + "servlet/login";
112     private static String logoutURL = baseUrl + "logout.jsp";
113     private static String msgReadBaseUrl = baseUrl + "servlet/viewMessage";
114     private static String inboxUrl = baseUrl + "inbox.jsp?folder=INBOX&bob=yes";
115     private static String moveToReadUrl = baseUrl + "inbox.jsp?action=READ";
116     public static int INVALID_ID = -200;
117     public static int PAGE_STRUCTURE_CHANGED = -200;
118     public static int LOGIN_OK = 100;
119     public static int SYSTEM_ERROR = -200;
120 
121     /**
122     * Flag message as read after RETR
123     */
124     public static final boolean FLAG_MSG_AS_READ = true;
125 
126     /**
127     * Include full html for the retrieved pages. Useful if the page structure changes.
128     */
129     public static final boolean DEBUG_PAGE_HTML = false;
130 
131     /**
132     * Generate summary information for all actions. If this is enabled, a maildrop summary will be output to the log.
133     */
134     public static final boolean CREATE_SUMMARY_INFO = true;
135     private LinkedHashSet inboxPageUrls = null;
136     private LinkedList msgHashCodes = null;
137     private LinkedList readMessages = null;
138     private boolean authenticated = false;
139     int numMessages = 0;
140     private String cb = null;
141     private CookieJar cj = null;
142     private AnotherMailDropSummary summaryInfo = null;
143 
144     /**
145     * Perform the login and retrieve the authentication cookie needed for subsequent requests.
146     * We'll do this by:
147     *    1 Retrieving the www.another.com main page (to create a session cookie with this service)
148     *    2 Posting a login block
149     *    3 Verifying the page is not an error page
150     */
151     public int login(String username, String password, boolean secureLogin) {
152         System.getProperties().put("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
153         java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
154 
155         //strip the extension off the user name (if present)...
156         int extIndex = username.indexOf("@");
157 
158         if (extIndex > -1) {
159             username = username.substring(0, extIndex);
160         }
161 
162         // Retrieve the main page and retrieve the session cookie
163         //Matcher logInMatcher = null, ctMatcher = null;
164         cj = new CookieJar();
165         BufferedReader rd = null;
166 
167         try {
168             URL url = new URL(httpLoginPageURL);
169             logger.fine("url: " + url);
170             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
171             conn.setRequestMethod("GET");
172             conn.setRequestProperty("User-Agent", UserAgent);
173             conn.setRequestProperty("Conten-type", "application/x-www-form-urlencoded");
174             conn.setRequestProperty("Accept", "*/*");
175             conn.setRequestProperty("Allowed", "GET HEAD PUT");
176             conn.setInstanceFollowRedirects(false);
177             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
178             hrh.connect();
179             conn = hrh.getConnection();
180             cj.addAll(hrh.getCookieJar());
181 
182             rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
183             String line;
184 
185             while ((line = rd.readLine()) != null) {
186                 logger.finest("html: " + line);
187             }
188         } catch (Exception e) {
189             logger.log(Level.SEVERE, "should not happen", e);
190         } finally {
191             try {
192                 if (rd != null) {
193                     rd.close();
194                 }
195             } catch (IOException ie) {
196                 logger.log(Level.SEVERE, "should not happen", ie);
197             }
198         }
199 
200         //About to perform login..
201         try {
202             URL url = new URL(loginURL + createPostRequest(username, password));
203             logger.fine("url: " + url);
204             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
205             conn.setRequestProperty("User-Agent", UserAgent);
206             conn.setRequestProperty("Conten-type", "application/x-www-form-urlencoded");
207             conn.setRequestMethod("GET");
208             conn.setRequestProperty("Allowed", "GET HEAD PUT");
209             conn.setInstanceFollowRedirects(false);
210             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
211             hrh.setCookieJar(cj);
212             hrh.connect();
213             conn = hrh.getConnection();
214             cj.addAll(hrh.getCookieJar());
215 
216             rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
217             String line;
218             String failure1 = "We're so sorry, something went wrong.";
219             String failure2 = "now why didn't that work?";
220 
221             Matcher failureMatcher = null;
222 
223             //verify page does not contain password error text...
224             while ((line = rd.readLine()) != null) {
225                 logger.finest("html: " + line);
226 
227                 if ((line.indexOf(failure1) > -1) || (line.indexOf(failure2) > -1)) {
228                     logger.warning("ID/Password does not exist/wrong");
229                     return INVALID_ID;
230                 }
231             }
232         } catch (Exception e) {
233             logger.log(Level.SEVERE, "should not happen", e);
234             return SYSTEM_ERROR;
235         } finally {
236             try {
237                 if (rd != null) {
238                     rd.close();
239                 }
240             } catch (IOException ie) {
241                 logger.log(Level.SEVERE, "should not happen", ie);
242             }
243         }
244 
245         // Lets retrieve the inbox URL, read the number of messages and determine links to the inbox pages...
246         // Each inbox page is determined by a 'hi=XX' id which represents take the top page size from the 
247         // bottom XX set of messages. Therefor the hi id actually decreases as you advance pages.
248         // A 'hi' id uniquely identifies a page, but the link may be repeated in the pages (first, last, 1..2..etc),
249         // so we have to remove duplicates as we collect.
250         // We also need to check for the existance of the 'READ' folder
251         numMessages = 0;
252 
253         if (inboxPageUrls == null) {
254             inboxPageUrls = new LinkedHashSet();
255         }
256         BufferedReader br = null;
257 
258         try {
259             URL url = new URL(inboxUrl);
260             logger.fine("url: " + url);
261             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
262             conn.setRequestProperty("User-Agent", UserAgent);
263             conn.setRequestProperty("Conten-type", "application/x-www-form-urlencoded");
264             conn.setRequestMethod("GET");
265             conn.setRequestProperty("Allowed", "GET HEAD PUT");
266             conn.setInstanceFollowRedirects(false);
267             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
268             hrh.setCookieJar(cj);
269             hrh.connect();
270             cj.addAll(hrh.getCookieJar());
271             br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
272             String line;
273 
274             Pattern inboxPattern = Pattern.compile("<B>INBOX</B>:\\s*(\\d+)\\s*msg");
275             Matcher inboxMatcher = null;
276 
277             Pattern pagePattern = Pattern.compile(
278                     "<A HREF=\"/inbox.jsp\\?cb=(\\d+)\\&folder=INBOX\\&bob=no\\&hi=(\\d+)\">");
279 
280             Matcher pageMatcher = null;
281 
282             //<A HREF="/inbox.jsp?folder=READ
283             while ((line = br.readLine()) != null) {
284                 logger.finest("html: " + line);
285                 inboxMatcher = inboxPattern.matcher(line);
286 
287                 if (inboxMatcher.find()) {
288                     numMessages = Integer.parseInt(inboxMatcher.group(1));
289                 }
290                 pageMatcher = pagePattern.matcher(line);
291 
292                 if (pageMatcher.find()) {
293                     cb = pageMatcher.group(1);
294                     Integer hiId = new Integer(pageMatcher.group(2));
295 
296                     //add this 'hi' id to our set of pages...
297                     inboxPageUrls.add(hiId);   //our set will handle duplicates
298                 }
299             }
300         } catch (Exception e) {
301             logger.log(Level.SEVERE, "should not happen", e);
302             return SYSTEM_ERROR;
303         } finally {
304             try {
305                 if (rd != null) {
306                     rd.close();
307                 }
308             } catch (IOException ie) {
309                 logger.log(Level.SEVERE, "should not happen", ie);
310             }
311         }
312 
313         logger.fine("Number of messages: " + numMessages);
314 
315         if (logger.isLoggable(Level.FINEST)) {
316             logger.finest("cb: " + cb);
317             Iterator it = inboxPageUrls.iterator();
318 
319             while (it.hasNext()) {
320                 logger.finest("HiIDs: " + (Integer) it.next());
321             }
322         }
323 
324         //Construct the summary information (if enabled)...
325         if (CREATE_SUMMARY_INFO) {
326             summaryInfo = new AnotherMailDropSummary();
327             summaryInfo.setUsername(username);
328             summaryInfo.setNumberOfMessages(numMessages);
329         }
330 
331         //We must have at least one mailbox page (or else something has gone horribly wrong!)
332         if (inboxPageUrls.size() > 0) {
333             authenticated = true;
334 
335             return LOGIN_OK;
336         } else {
337             return PAGE_STRUCTURE_CHANGED;
338         }
339     }
340 
341     /**
342     *Build the another.com login service post parameters
343     */
344     private String createPostRequest(String username, String password) {
345         StringBuffer line = new StringBuffer();
346         line.append("?uname=");
347         line.append(username);
348         line.append("&pword=");
349         line.append(password);
350         line.append("&folder=INBOX");
351         line.append("&uclass=funmail&o=funmail\n");
352         return line.toString();
353     }
354 
355     /**
356     * Logout from Another.com's service
357     */
358     private void logout() throws MailSessionException {
359         boolean loggedOut = false;
360         BufferedReader rd = null;
361 
362         try {
363             URL url = new URL(logoutURL);
364             logger.fine("url: " + url);
365             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
366             conn.setRequestMethod("GET");
367             conn.setRequestProperty("User-Agent", UserAgent);
368             conn.setRequestProperty("Conten-type", "application/x-www-form-urlencoded");
369             conn.setRequestProperty("Accept", "*/*");
370             conn.setRequestProperty("Allowed", "GET HEAD PUT");
371             conn.setInstanceFollowRedirects(false);
372             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
373             hrh.connect();
374             conn = hrh.getConnection();
375             cj.addAll(hrh.getCookieJar());
376 
377             rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
378             String line;
379 
380             while ((line = rd.readLine()) != null) {
381                 logger.fine("url: " + url);
382 
383                 if (line.indexOf("another.com: goodbye - come back soon!") > -1) {
384                     loggedOut = true;
385                 }
386             }
387         } catch (Exception e) {
388             logger.log(Level.SEVERE, "should not happen", e);
389             throw new MailSessionException("Logout failed : " + e.getMessage());
390         } finally {
391             try {
392                 if (rd != null) {
393                     rd.close();
394                 }
395             } catch (IOException ie) {
396                 logger.log(Level.SEVERE, "should not happen", ie);
397             }
398         }
399 
400         if (loggedOut) {
401             logger.info("Logged out successfully");
402         } else {
403             logger.info("Log out failed");
404         }
405     }
406 
407     /**
408     * Return the number of messages in the inbox (read and unread - we can't tell the difference!)
409     * In order to facilitate simple message retrieval, we'll take this opportunity to read all the
410     * message urls and hashcodes (used to delete or move a message) into our list.
411     * As the inbox will be spread across multiple pages, we'll have to do this once for each page.
412     * We already collected the page urls in the login() method :)
413     */
414 
415     //public int numMessages() throws MailSessionException{
416     public int numMessages() {
417         //This may be called repeatedly - so we will do the processing only once...
418         if (msgHashCodes == null) {
419             try {
420                 msgHashCodes = new LinkedList();
421                 Iterator it = inboxPageUrls.iterator();
422 
423                 while (it.hasNext()) {
424                     readMessageInfoForPage(((Integer) it.next()).intValue());
425                 }
426 
427                 for (int i = 0; i < msgHashCodes.size(); i++) {
428                     MailIdentifier mi = (MailIdentifier) msgHashCodes.get(i);
429                     logger.fine(mi.mailID + " : " + mi.hashId + "=" + mi.hashCode);
430                 }
431             } catch (MailSessionException me) {
432                 //Update the summary information
433                 if (CREATE_SUMMARY_INFO) {
434                     summaryInfo.addGeneralError(me.getMessage());
435                 }
436 
437                 //Pass this on to the PopConnector to allow it to return error to client
438                 //throw me;
439             }
440         }
441 
442         //return the total message count, which we calculated in the login() method
443         return numMessages;
444     }
445 
446     /**
447     * Retrieve all the message urls and hashcodes for this particular mailbox page.
448     */
449     private void readMessageInfoForPage(int hiID) throws MailSessionException {
450         if (!authenticated) {
451             return;
452         }
453         BufferedReader br = null;
454 
455         try {
456             URL url = new URL(inboxUrl + "&hi=" + Integer.toString(hiID));
457             logger.fine("url: " + url);
458             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
459             conn.setRequestProperty("User-Agent", UserAgent);
460             conn.setRequestProperty("Conten-type", "application/x-www-form-urlencoded");
461             conn.setRequestMethod("GET");
462             conn.setRequestProperty("Allowed", "GET HEAD PUT");
463             conn.setInstanceFollowRedirects(false);
464             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
465             hrh.setCookieJar(cj);
466             hrh.connect();
467             cj.addAll(hrh.getCookieJar());
468             br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
469             String line;
470 
471             Pattern messagePattern = Pattern.compile(
472                     "<A HREF=\"/servlet/viewMessage\\?cb=\\d+\\&action=msg&id=(\\d+)\\&folder=INBOX\">");
473             Matcher messageMatcher = null;
474 
475             Pattern hashCodePattern = Pattern.compile(
476                     "<INPUT TYPE=\"HIDDEN\" NAME=\"MessageHash(\\d+)\" VALUE=\"(\\S+)\">");
477             Matcher hashCodeMatcher = null;
478 
479             MailIdentifier mi = null;
480 
481             while ((line = br.readLine()) != null) {
482                 logger.finest("html: " + line);
483                 messageMatcher = messagePattern.matcher(line);
484 
485                 if (messageMatcher.find()) {
486                     int msgId = Integer.parseInt(messageMatcher.group(1));
487                     logger.fine("Found Message ID: " + msgId);
488                     mi = new MailIdentifier();
489                     mi.mailID = msgId;
490                 }
491 
492                 //They must come in pairs - just not on the same line, so we are guaranteed to get this just after the mailId
493                 hashCodeMatcher = hashCodePattern.matcher(line);
494 
495                 if (hashCodeMatcher.find()) {
496                     mi.hashCode = hashCodeMatcher.group(2);
497                     mi.hashId = hashCodeMatcher.group(1);
498                     logger.fine("Found HashCode" + mi.hashId + "=" + mi.hashCode);
499                     //Now add this into our list, indexed by mid
500                     msgHashCodes.add(mi.mailID, mi);
501                 }
502             }
503         } catch (Exception e) {
504             logger.log(Level.SEVERE, "should not happen", e);
505             throw new MailSessionException("readMessageInfoForPage(" + hiID + ") failed : " + e.getMessage());
506         } finally {
507             try {
508                 if (br != null) {
509                     br.close();
510                 }
511             } catch (IOException ie) {
512                 logger.log(Level.SEVERE, "should not happen", ie);
513             }
514         }
515     }
516 
517     /**
518     * Retrieve and output the message.
519     * If the option is enabled, move the message into the READ folder.
520     * This is called by the RETR command.
521     */
522 
523     //public void outputMessage(int num, PrintWriter pw) throws MailSessionException{
524     public void outputMessage(int num, PrintWriter pw) {
525         try {
526             retrieveMessage(num, pw, -1);
527 
528             //Add the summary info....
529             if (CREATE_SUMMARY_INFO) {
530                 summaryInfo.retrSuccess(num);
531             }
532 
533             if (FLAG_MSG_AS_READ) {
534                 //Add this message to our list of read messages....
535                 MailIdentifier mi = (MailIdentifier) msgHashCodes.get(num - 1);
536 
537                 if (readMessages == null) {
538                     readMessages = new LinkedList();
539                 }
540                 readMessages.add(mi);   //this adds it to the move-to-read list 
541             }
542         } catch (MailSessionException me) {
543             //Update the summary information
544             if (CREATE_SUMMARY_INFO) {
545                 summaryInfo.retrFailure(num, me.getMessage());
546             }
547 
548             //Pass this on to the PopConnector to allow it to return error to client
549             //throw me;
550         }
551     }
552 
553     /**
554     * Retrieve the email header and numLines of the email body.
555     * The message will not be marked as read.
556     * This is called by the TOP command.
557     */
558 
559     //public void outputLinesMessage(int num, PrintWriter pw, int numLines) throws MailSessionException
560     public void outputLinesMessage(int num, PrintWriter pw, int numLines) {
561         try {
562             retrieveMessage(num, pw, numLines);
563 
564             if (CREATE_SUMMARY_INFO) {
565                 summaryInfo.topSuccess(num);
566             }
567         } catch (MailSessionException me) {
568             //Update the summary information
569             if (CREATE_SUMMARY_INFO) {
570                 summaryInfo.topFailure(num, me.getMessage());
571             }
572 
573             //Pass this on to the PopConnector to allow it to return error to client
574             //throw me;
575         }
576     }
577 
578     /**
579     * Retrieve the msg specified by the supplied msg number (indexed from 1!)
580     * This will use other methods to verify if the mail has attachements, retrieve the body text (always HTML)
581     * and attachements.
582     */
583     private void retrieveMessage(int num, PrintWriter pw, int numLines)
584         throws MailSessionException {
585         logger.fine("Retrieve Email #:" + num + ((numLines != -1) ? (" numlines=" + numLines) : " full"));
586 
587         if (msgHashCodes == null) {
588             numMessages();
589         }
590 
591         if (num > numMessages) {
592             logger.severe("outputMessage() called with invalid message number " + num + " number of messages = "
593                 + numMessages);
594             return;
595         }
596 
597         //First parse the header and message body...
598         MailMessage message = parseMessageHeaderAndBody(num, numLines);
599 
600         //Add the summary info....
601         if (CREATE_SUMMARY_INFO) {
602             summaryInfo.setMsgHeader(num, message.header);
603         }
604 
605         //now check for any attachments...if this is a TOP command we won't bother with the attachments...
606         if (numLines == -1) {
607             message.attachments = checkForAttachments(num);
608         }
609 
610         //Output the message...
611         outputMessageHeader(message, pw);
612         outputMessageBody(message, pw);
613 
614         if ((message.attachments != null) && (message.attachments.size() > 0)) {
615             Iterator it = message.attachments.iterator();
616 
617             while (it.hasNext()) {
618                 AttachmentIdentifier ai = (AttachmentIdentifier) it.next();
619                 logger.fine("Handling attachment (" + ai.partId + "," + ai.filename + ")");
620                 outputAttachment(message, ai, pw);
621             }
622 
623             //Now we must output the closing boundary...
624             pw.print("--" + message.boundary + "--");
625             pw.print("\r\n");
626         }
627         pw.flush();
628     }
629 
630     /**
631     * Determine the attachment info for any attached files
632     * Does this by reading the message and searching for the download href
633     */
634     private LinkedList checkForAttachments(int num) throws MailSessionException {
635         logger.fine("checkForAttachments: " + num);
636         BufferedReader br = null;
637 
638         try {
639             // note no printInBaby option
640             URL url = new URL(msgReadBaseUrl + "?cb=" + cb + "&action=msg&folder=INBOX&id=" + (num - 1));
641             logger.fine("Read URL:\n" + url);
642             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
643             conn.setRequestProperty("User-Agent", UserAgent);
644             conn.setRequestMethod("GET");
645             conn.setRequestProperty("Allowed", "GET HEAD PUT");
646             conn.setInstanceFollowRedirects(false);
647             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
648             hrh.setCookieJar(cj);
649             hrh.connect();
650             cj.addAll(hrh.getCookieJar());
651             br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
652             Pattern attachPattern = Pattern.compile(
653                     "<A HREF=\"/servlet/downloadPart/(\\S+)\\?partID=(\\S+)\" TARGET=\"another_download\" TITLE=\"download file to your computer\">");
654             Matcher attachMatcher = null;
655             String line = null;
656             LinkedList attachmentList = new LinkedList();
657 
658             while ((line = br.readLine()) != null) {
659                 logger.finest("html: " + line);
660                 attachMatcher = attachPattern.matcher(line);
661 
662                 if (attachMatcher.find()) {
663                     AttachmentIdentifier at = new AttachmentIdentifier();
664                     at.partId = attachMatcher.group(2);
665                     at.filename = attachMatcher.group(1);
666                     logger.fine("Found Attachment: partId=" + at.partId + ", filename=" + at.filename);
667                     attachmentList.add(at);
668                 }
669             }
670             return attachmentList;
671         } catch (Exception e) {
672             logger.log(Level.SEVERE, "should not happen", e);
673             throw new MailSessionException("checkForAttachements(" + num + ") failed: " + e.getMessage());
674         } finally {
675             try {
676                 if (br != null) {
677                     br.close();
678                 }
679             } catch (IOException ie) {
680                 logger.log(Level.SEVERE, "should not happen", ie);
681             }
682         }
683     }
684 
685     /**
686     * Retrieve the msg specified by the supplied msg number (indexed from 1!)
687     * Strip the HTML tags and parse the message header. Output the message body to
688     * the supplied PrintWriter
689     * if numLines != -1 then limit the number of body lines to be this value (used for the TOP command)
690     * Assumptions: the msg will be surrounded by 'pre' and '/pre' tags.
691     */
692     private MailMessage parseMessageHeaderAndBody(int num, int numLines)
693         throws MailSessionException {
694         logger.fine("Retrieve Email Body #:" + num + ((numLines != -1) ? (" numlines=" + numLines) : " full"));
695         BufferedReader br = null;
696 
697         try {   //msgReadBaseUrl
698             URL url = new URL(msgReadBaseUrl + "?cb=" + cb + "&action=msg&folder=INBOX&id=" + (num - 1)
699                     + "&printable=printInBaby");
700             logger.fine("url: " + url);
701             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
702             conn.setRequestProperty("User-Agent", UserAgent);
703             conn.setRequestMethod("GET");
704             conn.setRequestProperty("Allowed", "GET HEAD PUT");
705             conn.setInstanceFollowRedirects(false);
706             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
707             hrh.setCookieJar(cj);
708             hrh.connect();
709             cj.addAll(hrh.getCookieJar());
710             br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
711             String line = null;
712 
713             //Strip all html tags from the text
714             boolean insideTag = false;
715             boolean waitingForStartOfBody = false;
716             boolean insideBody = false;
717 
718             //Create our structure to store the parsed message content...
719             MailMessage message = new MailMessage();
720             message.header = new HeaderInfo();
721             LinkedList bodyLines = new LinkedList();
722 
723             //The subject is the first non-whitespace line of the header...
724             String nextLine = "subject";
725             int bodyLine = 0;
726 
727             while ((line = br.readLine()) != null) {
728                 logger.finest("html: " + line);
729                 //Strip all html tags...
730                 StringBuffer lineBuff = new StringBuffer(line.length());
731 
732                 if ((line.indexOf("<title>") > -1) || (line.indexOf("<TITLE>") > -1)) {
733                     continue;   //throw away the title!
734                 }
735 
736                 //If we have already processed the header and are waiting for the start of the body...
737                 if (waitingForStartOfBody) {
738                     //The body starts at the firsr <p> tag!
739                     if ((line.indexOf("<p>") > -1) || (line.indexOf("<P>") > -1)) {
740                         insideBody = true;
741                         waitingForStartOfBody = false;
742                         //Output a HTML header info so the body can be interpretted as HTML...
743                         bodyLines.add("<html><body><p>");
744                         bodyLines.add("\r\n");
745                     }
746                 } else {
747                     //If we are looking for the header fields, we wil strip the HTML tags and convert to text...
748                     if (!insideBody) {
749                         //Inside header...
750                         for (int c = 0; c < line.length(); c++) {
751                             char chr = line.charAt(c);
752 
753                             if (chr == '<') {
754                                 insideTag = true;
755                             }
756 
757                             if (!insideTag) {
758                                 lineBuff.append(chr);
759                             }
760 
761                             if (chr == '>') {
762                                 insideTag = false;
763                             }
764                         }
765 
766                         //convert any html encodings that might be present in the line...
767                         String line2 = lineBuff.toString();
768 
769                         if (line2.length() > 0) {
770                             line2 = line2.replaceAll("\\&amp;", "\\&");
771                             line2 = line2.replaceAll("\\&lt;", "<");
772                             line2 = line2.replaceAll("\\&gt;", ">");
773                             line2 = line2.replaceAll("\\&quot;", "\"");
774                         }
775 
776                         //Process the header...
777                         //Throw away blank lines (or this with only whitespace characters)....
778                         Pattern whiteSpaceLine = Pattern.compile("^\\s*\\z");
779 
780                         if (whiteSpaceLine.matcher(line2).find()) {
781                             continue;   //line only contains white space!
782                         }
783 
784                         //We are guaranteed here a non-whitespace line, which is in the header...
785                         //We must replace any '[' with '<' and ']' with '>' but not if we are in the subject field....
786                         if ((nextLine != null) && !nextLine.equals("subject")) {
787                             line2 = line2.replace('[', '<');
788                             line2 = line2.replace(']', '>');
789                         }
790 
791                         if (nextLine != null) {
792                             //We are expecting some input...
793                             if (nextLine.equals("subject")) {
794                                 message.header.subject = line2;
795                                 nextLine = null;
796                             } else if (nextLine.equals("to")) {
797                                 message.header.to = line2;
798                                 nextLine = null;
799                             } else if (nextLine.equals("from")) {
800                                 message.header.from = line2;
801                                 nextLine = null;
802                             } else if (nextLine.equals("date")) {
803                                 message.header.sentDate = line2;
804                                 nextLine = null;
805 
806                                 //The Date field is the last item in the header
807                                 //So we are waiting for the start of the body, this will come at the next <p> tag
808                                 waitingForStartOfBody = true;
809                             }
810                         } else {
811                             //We do not know what the  next line represents yet....
812                             if (line2.indexOf("To:") > -1) {
813                                 //The next line is guaranteed to be the To: fields....
814                                 nextLine = "to";
815                             } else if (line2.indexOf("From:") > -1) {
816                                 //The next line is guaranteed to be the From: fields....
817                                 nextLine = "from";
818                             } else if (line2.indexOf("Date:") > -1) {
819                                 //The next line is guaranteed to be the Date: fields....
820                                 nextLine = "date";
821                             }
822                         }
823                     } else {  //end header processing
824                         //inside the body (we have already processed the header) then just echo the line
825                         bodyLines.add(line);
826                         bodyLines.add("\r\n");   //just incase the system we are running on has a different line terminator
827 
828                         if (numLines > -1) {   //Are we executing a TOP command?
829 
830                             if (++bodyLine > numLines) {   //it has to be 1 more as we have counted the blank line
831                                 break;   //read enough of the body (for TOP commands)
832                             }
833                         }
834                     }
835                 }
836 
837                 //end waitingForBody
838             }
839 
840             //end while
841             String[] tmpArray = new String[0];
842             message.bodyLines = (String[]) bodyLines.toArray(tmpArray);
843             return message;
844         } catch (Exception e) {
845             logger.log(Level.SEVERE, "should not happen", e);
846             throw new MailSessionException("parseMessageHeaderAndBody(" + num + "," + numLines + ") failed : "
847                 + e.getMessage());
848         } finally {
849             try {
850                 if (br != null) {
851                     br.close();
852                 }
853             } catch (IOException ie) {
854                 logger.log(Level.SEVERE, "should not happen", ie);
855             }
856         }
857     }
858 
859     /**
860     * Output the email header
861     */
862     private void outputMessageHeader(MailMessage message, PrintWriter pw) {
863         pw.print("Subject: " + message.header.subject);
864         pw.print("\r\n");
865         pw.print("To: " + message.header.to);
866         pw.print("\r\n");
867         pw.print("From: " + message.header.from);
868         pw.print("\r\n");
869         pw.print("Date: " + message.header.sentDate);
870         pw.print("\r\n");
871 
872         if ((message.attachments == null) || (message.attachments.size() == 0)) {
873             //We'll need to add our header field to tell the email client that it is html body...
874             pw.print("Content-Type: text/html; charset=us-ascii");
875             pw.print("\r\n");
876             pw.print("\r\n");   //ouput the blank header-body separator line
877         } else {
878             //this is a multipart message...
879             pw.print("MIME-Version: 1.0");
880             pw.print("\r\n");
881             message.boundary = createUniqueBoundaryValue(message);
882             pw.print("Content-Type: multipart/mixed; boundary=\"" + message.boundary + "\"");
883             pw.print("\r\n");
884             pw.print("\r\n");
885             pw.print("This is a multi-part message in MIME format.");
886             pw.print("\r\n");
887             pw.print("--" + message.boundary);
888             pw.print("\r\n");
889             pw.print("Content-Type: text/html; charset=us-ascii");
890             pw.print("\r\n");
891             pw.print("Content-Transfer-Encoding: 7bit");
892             pw.print("\r\n");
893             pw.print("\r\n");   //ouput the blank header-body separator line
894         }
895     }
896 
897     /**
898     * Create a unique boundary string
899     */
900     private String createUniqueBoundaryValue(MailMessage message) {
901         StringBuffer sBuffer = new StringBuffer();
902         sBuffer.append("----=_Part_").append(message.partCount++).append("_").append(sBuffer.hashCode()).append('.')
903                .append(System.currentTimeMillis());
904         message.boundary = sBuffer.toString();
905         return message.boundary;
906     }
907 
908     /**
909     * Output the email body
910     */
911     private void outputMessageBody(MailMessage message, PrintWriter pw) {
912         for (int i = 0; i < message.bodyLines.length; i++) {
913             pw.print(message.bodyLines[i]);
914         }
915     }
916 
917     /**
918     * Output an attachment
919     */
920     private void outputAttachment(MailMessage message, AttachmentIdentifier attachment, PrintWriter pw)
921         throws MailSessionException {
922         logger.fine("outputAttachment : " + attachment.filename);
923 
924 
925         //Retrieve the attachment...
926         InputStream is = null;
927 
928         try {
929             //read it and base64 encode it
930             URL url = new URL(baseUrl + "servlet/downloadPart/" + attachment.filename + "?partID=" + attachment.partId);
931 
932             logger.fine("Attachment URL:\n" + url);
933             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
934             conn.setRequestProperty("User-Agent", UserAgent);
935             conn.setRequestMethod("GET");
936             conn.setRequestProperty("Allowed", "GET HEAD PUT");
937             conn.setInstanceFollowRedirects(false);
938             HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
939             hrh.setCookieJar(cj);
940             hrh.connect();
941             cj.addAll(hrh.getCookieJar());
942             String contentType = conn.getContentType();
943             logger.fine("Content-type:" + contentType);
944             is = conn.getInputStream();
945             int size = conn.getContentLength();
946             ByteArrayOutputStream bos = new ByteArrayOutputStream(size);
947 
948             int numRead = -1;
949             byte[] bytes = new byte[128];
950 
951             while ((numRead = is.read(bytes)) > -1) {
952                 if (numRead > 0) {
953                     bos.write(bytes, 0, numRead);
954                 }
955             }
956 
957             logger.fine("Attachment size: " + bos.toByteArray().length);
958             //We have the raw attachment bytes. Output the header with the correct content type....
959             pw.print("--" + message.boundary);
960             pw.print("\r\n");
961             pw.print("Content-Type: " + contentType + "; name=\"" + attachment.filename + "\"");
962             pw.print("\r\n");
963             pw.print("Content-Transfer-Encoding: base64");
964             pw.print("\r\n");
965             pw.print("Content-Disposition: attachment; filename=\"" + attachment.filename + "\"");
966             pw.print("\r\n");
967             pw.print("\r\n");   //ouput the blank header-body separator line
968 
969             //Encode the bytes and output...
970             pw.print(Base64.encodeBytes(bos.toByteArray(), true));
971             pw.print("\r\n");
972             pw.flush();
973         } catch (IOException ie) {
974             throw new MailSessionException("outputAttachment(" + message + "," + attachment.filename + ") failed : "
975                 + ie.getMessage());
976         } finally {
977             try {
978                 if (is != null) {
979                     is.close();
980                 }
981             } catch (IOException ie) {
982                 logger.log(Level.SEVERE, "should not happen", ie);
983             }
984         }
985     }
986 
987     /**
988     * Return the size of the specified msg (indexed from 1!)
989     * This is a bit of a frig - we can't tell how big the message is without reading it.
990     * So we won't actually bother! We'll make up a number and hope the client doesn't mind!
991     * Note: We can't use the figure specified by another.com as that doesn't include attachments and
992     * isn't very accurate anyhow.
993     */
994     public int getSizeOfMessage(int num) {
995         logger.fine("getSizeOfMessage(" + num + ")");
996         return 1;
997     }
998 
999     /**
1000    * Return the module name to the MrPostman controller
1001    */
1002    public String getModuleName() {
1003        return MODULE_INFO.getModuleID();
1004    }
1005
1006    /**
1007    * Return the list of email extensions supported by this module
1008    */
1009    public String[] getRecognizedExtensions() {
1010        String[] extensions = {"@another.com"};
1011        return extensions;
1012    }
1013
1014    /**
1015    * Delete the message with the supplied index number.
1016    */
1017
1018    //public void deleteMessage(int num) throws MailSessionException {
1019    public boolean deleteMessage(int num) {
1020        logger.fine("DEL Email #:" + num);
1021
1022        //We don't do anything about this!
1023        // Add the summary information for this command...
1024        if (CREATE_SUMMARY_INFO) {
1025            summaryInfo.deleSuccess(num);
1026        }
1027
1028        return true;
1029    }
1030
1031    /**
1032    * Handle the QUIT command.
1033    * We need to move all our read messages to our READ folder
1034    * and logout.
1035    * NOTE: We can't do this after each RETR as that would adjust all the
1036    * message ids!
1037    */
1038    public void quitSession() {
1039        logger.fine("quitSession()");
1040
1041        try {
1042            if (FLAG_MSG_AS_READ) {
1043                moveMessagesToRead();
1044            }
1045        } catch (MailSessionException me) {
1046            //Add this to our summary info...
1047            if (CREATE_SUMMARY_INFO) {
1048                summaryInfo.addGeneralError(me.getMessage());
1049            }
1050        }
1051
1052        //Logout from another.com
1053        try {
1054            logout();
1055        } catch (MailSessionException me) {
1056            //Add this to our summary info...
1057            if (CREATE_SUMMARY_INFO) {
1058                summaryInfo.addGeneralError(me.getMessage());
1059            }
1060        }
1061
1062        //Now output all our summary information...
1063        if (CREATE_SUMMARY_INFO) {
1064            logger.fine(summaryInfo.toString());
1065        }
1066    }
1067
1068    /**
1069    * Move all the messages that we have read into the 'READ' folder
1070    */
1071    private void moveMessagesToRead() throws MailSessionException {
1072        BufferedReader br = null;
1073
1074        try {
1075            //We may need to flag our messages as read -we'll do this by moving them into the READ folder
1076            if (readMessages != null) {
1077                Iterator it = readMessages.iterator();
1078
1079                while (it.hasNext()) {
1080                    MailIdentifier mi = (MailIdentifier) it.next();
1081
1082                    logger.fine("Flag message as read: id=" + mi.mailID + " hashId=" + mi.hashId + " hashCode="
1083                        + mi.hashCode);
1084
1085                    URL url = new URL(moveToReadUrl + "&cb=" + cb + "&remove=" + mi.hashId + "&MessageHash" + mi.hashId
1086                            + "=" + mi.hashCode);
1087                    logger.fine("url: " + url);
1088                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1089                    conn.setRequestProperty("User-Agent", UserAgent);
1090                    conn.setRequestProperty("Allowed", "GET HEAD PUT");
1091                    conn.setInstanceFollowRedirects(false);
1092                    HTTPRedirectHandler hrh = new HTTPRedirectHandler(conn);
1093                    hrh.setCookieJar(cj);
1094                    hrh.connect();
1095                    cj.addAll(hrh.getCookieJar());
1096                    br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
1097                    String line = null;
1098
1099                    br.close();   //we're not interested in the response data!
1100
1101                    //Add this move to our summary info...
1102                    if (CREATE_SUMMARY_INFO) {
1103                        summaryInfo.moveMsgSuccess();
1104                    }
1105                }
1106            }
1107        } catch (Exception e) {
1108            logger.log(Level.SEVERE, "should not happen", e);
1109            throw new MailSessionException("Exception whilst moving msg to READ folder: " + e.getMessage());
1110        } finally {
1111            try {
1112                if (br != null) {
1113                    br.close();
1114                }
1115            } catch (IOException ie) {
1116                logger.log(Level.SEVERE, "should not happen", ie);
1117            }
1118        }
1119    }
1120
1121    /**
1122    * Return the unique id for this message. This is the another.com hashcode value
1123    */
1124    public String getUniqueMessageId(int num) throws MailSessionException {
1125        //We return the hashcode for this message...
1126        return ((MailIdentifier) msgHashCodes.get(num - 1)).hashCode;
1127    }
1128
1129    /**
1130    * Return the module info.
1131    */
1132    public ModuleInfo getModuleInfo() {
1133        return MODULE_INFO;
1134    }
1135
1136    /**
1137    * Set a module option with a value from the preferences or from the GUI
1138    * Currently understood options are listed in the MODULE_INFO specification above.
1139    */
1140    public void setOption(String optionName, String value) {
1141        logger.log(Level.FINEST, "setOption(" + optionName + "," + value + ")");
1142    }
1143
1144    /**
1145    * A debug main method
1146    */
1147    public static void main(String[] args) {
1148        if (args.length != 2) {
1149            System.err.println("Syntax AnotherMailSession username password");
1150            System.exit(1);
1151        }
1152
1153        AnotherMailSession ams = new AnotherMailSession();
1154
1155        if (ams.login(args[0], args[1], true) == LOGIN_OK) {
1156            try {
1157                int numMsgs = ams.numMessages();
1158                System.out.println("Number of messages: " + numMsgs);
1159
1160                if (numMsgs > 0) {
1161                    ams.outputMessage(1, new PrintWriter(System.out));
1162                }
1163
1164                ams.logout();
1165            } catch (Exception e) {
1166                logger.log(Level.SEVERE, "should not happen", e);
1167            }
1168        }
1169    }
1170
1171/**
1172* Helper class used to store message info.
1173*/
1174    class MailIdentifier {
1175        int mailID = -1;
1176        String hashCode = null;
1177        String hashId = null;
1178
1179        public String toString() {
1180            return new String("MailID=" + mailID + ",  hashId=" + hashId + ", hashCode=" + hashCode);
1181        }
1182    }
1183
1184/**
1185* Helper class to store attachement info.
1186*/
1187    class AttachmentIdentifier {
1188        String partId = null;
1189        String filename = null;
1190    }
1191
1192/**
1193* Helper class to store Message content
1194*/
1195    class MailMessage {
1196        public HeaderInfo header = null;
1197        public LinkedList attachments = null;
1198        public String[] bodyLines;
1199        public String boundary;
1200        public int partCount = 0;
1201    }
1202}