0001: /* CVS ID: $Id: WebMailSession.java,v 1.1.1.1 2002/10/02 18:42:53 wastl Exp $ */
0002: package net.wastl.webmail.server;
0003:
0004: import java.net.*;
0005: import java.io.*;
0006: import java.util.*;
0007: import java.text.*;
0008: import javax.mail.*;
0009: import javax.mail.event.*;
0010: import javax.mail.internet.*;
0011: import net.wastl.webmail.misc.*;
0012: import net.wastl.webmail.xml.*;
0013: import net.wastl.webmail.ui.html.Fancyfier;
0014: import net.wastl.webmail.server.http.HTTPRequestHeader;
0015: import net.wastl.webmail.exceptions.*; // Modified by exce, start
0016: import org.bulbul.webmail.util.TranscodeUtil; // Modified by exce, end
0017:
0018: //import org.w3c.tidy.Tidy;
0019:
0020: import org.w3c.dom.*;
0021:
0022: // HTML parser:
0023: import org.xml.sax.InputSource;
0024:
0025: //import org.cyberneko.html.parsers.DOMParser;
0026:
0027: /*
0028: * WebMailSession.java
0029: *
0030: * Created: Thu Feb 4 12:59:30 1999
0031: *
0032: * Copyright (C) 1999-2001 Sebastian Schaffert
0033: *
0034: * This program is free software; you can redistribute it and/or
0035: * modify it under the terms of the GNU General Public License
0036: * as published by the Free Software Foundation; either version 2
0037: * of the License, or (at your option) any later version.
0038: *
0039: * This program is distributed in the hope that it will be useful,
0040: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0041: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0042: * GNU General Public License for more details.
0043: *
0044: * You should have received a copy of the GNU General Public License
0045: * along with this program; if not, write to the Free Software
0046: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0047: */
0048:
0049: /**
0050: * A user session for WebMail.
0051: * Contains the state of the actual user (loads it from disk).
0052: * Has a unique session-ID.
0053: *
0054: *
0055: * @author Sebastian Schaffert
0056: * @version $Revision: 1.1.1.1 $
0057: */
0058: /* 9/24/2000 devink - updated for challenge/response auth */
0059: public class WebMailSession implements HTTPSession {
0060:
0061: /** When has the session been last accessed? */
0062: private long last_access;
0063: /** The session-ID for this session */
0064: private String session_code;
0065: /** Parent WebMailServer */
0066: private WebMailServer parent;
0067: /** State of the current users configuration */
0068: private XMLUserData user;
0069:
0070: private XMLUserModel model;
0071:
0072: /** Connections to Mailboxes */
0073: private Hashtable connections;
0074:
0075: /** Connections to hosts */
0076: private Hashtable stores;
0077:
0078: /** javax.mail Mailsession */
0079: private Session mailsession;
0080:
0081: private InetAddress remote;
0082:
0083: /* Files attached to messages will be stored here. We will have to take care of
0084: possible memory problems! */
0085: private Hashtable mime_parts_decoded;
0086:
0087: private boolean sent;
0088:
0089: private String remote_agent;
0090: private String remote_accepts;
0091:
0092: private int attachments_size = 0;
0093:
0094: private String last_login;
0095:
0096: /** Save the login password. It will be used for the second try password if
0097: * opening a folder fails.
0098: */
0099: private String login_password;
0100:
0101: private Object sess = null;
0102:
0103: private Hashtable folders;
0104:
0105: protected Vector need_expunge_folders;
0106:
0107: protected boolean is_logged_out = false;
0108:
0109: public WebMailSession(WebMailServer parent, Object parm,
0110: HTTPRequestHeader h) throws UserDataException,
0111: InvalidPasswordException, WebMailException {
0112: try {
0113: Class srvltreq = Class
0114: .forName("javax.servlet.http.HttpServletRequest");
0115: if (srvltreq.isInstance(parm)) {
0116: javax.servlet.http.HttpServletRequest req = (javax.servlet.http.HttpServletRequest) parm;
0117: this .sess = req.getSession(false);
0118: session_code = ((javax.servlet.http.HttpSession) sess)
0119: .getId();
0120:
0121: try {
0122: remote = InetAddress.getByName(req.getRemoteHost());
0123: } catch (UnknownHostException e) {
0124: try {
0125: remote = InetAddress.getByName(req
0126: .getRemoteAddr());
0127: } catch (Exception ex) {
0128: try {
0129: remote = InetAddress.getByName("localhost");
0130: } catch (Exception ex2) {
0131: }
0132: }
0133: }
0134: } else {
0135: throw new Exception(
0136: "Servlet class found but not running as servlet");
0137: }
0138: } catch (Throwable t) {
0139: this .remote = (InetAddress) parm;
0140: session_code = Helper.calcSessionCode(remote, h);
0141: }
0142: doInit(parent, h);
0143:
0144: }
0145:
0146: /**
0147: * This method does the actual initialisation
0148: *
0149: * devink 7/15/2000 - added TwoPassAuthenticationException
0150: * - updated call to getUserData(), to use my new one.
0151: * devink 9/24/2000 - reverted back to old getUserData call
0152: */
0153: protected void doInit(WebMailServer parent, HTTPRequestHeader h)
0154: throws UserDataException, InvalidPasswordException, WebMailException {
0155: setLastAccess();
0156: this .parent=parent;
0157: remote_agent=h.getHeader("User-Agent").replace('\n',' ');
0158: remote_accepts=h.getHeader("Accept").replace('\n',' ');
0159: parent.getStorage().log(Storage.LOG_INFO,"WebMail: New Session ("+session_code+")");
0160: user=WebMailServer.getStorage().getUserData(h.getContent("login"),h.getContent("vdom"),h.getContent("password"),true);
0161: last_login=user.getLastLogin();
0162: user.login();
0163: login_password=h.getContent("password");
0164: model=parent.getStorage().createXMLUserModel(user);
0165: connections=new Hashtable();
0166: stores=new Hashtable();
0167: folders=new Hashtable();
0168: mailsession=Session.getDefaultInstance(System.getProperties(),null);
0169:
0170: /* If the user logs in for the first time we want all folders subscribed */
0171: if(user.getLoginCount().equals("1")) {
0172: Enumeration enum=user.mailHosts();
0173: while(enum.hasMoreElements()) {
0174: String id=(String)enum.nextElement();
0175: if(user.getMailHost(id).getName().equals("Default")) {
0176: try {
0177: setSubscribedAll(id,true);
0178: } catch(MessagingException ex) {
0179: ex.printStackTrace();
0180: }
0181: break;
0182: }
0183: }
0184: }
0185: setEnv();
0186: }
0187:
0188: public XMLUserModel getUserModel() {
0189: return model;
0190: }
0191:
0192: public Document getModel() {
0193: return model.getRoot();
0194: }
0195:
0196: /**
0197: * Calculate session-ID for a session.
0198: *
0199: * @param a Adress of the remote host
0200: * @param h Requestheader of the remote user agent
0201: * @returns Session-ID
0202: */
0203: public String calcCode(InetAddress a, HTTPRequestHeader h) {
0204: if (sess == null) {
0205: return Helper.calcSessionCode(a, h);
0206: } else {
0207: try {
0208: Class srvltreq = Class
0209: .forName("javax.servlet.http.HttpSession");
0210: if (srvltreq.isInstance(sess)) {
0211: return ((javax.servlet.http.HttpSession) sess)
0212: .getId();
0213: } else {
0214: return "error";
0215: }
0216: } catch (Throwable t) {
0217: return "error";
0218: }
0219: }
0220: }
0221:
0222: /**
0223: * Login to this session.
0224: * Establishes connections to a userīs Mailhosts
0225: *
0226: * @param h RequestHeader with content from Login-POST operation.
0227: * @deprecated Use login() instead, no need for parameters and exception handling
0228: */
0229: public void login(HTTPRequestHeader h)
0230: throws InvalidPasswordException {
0231: //user.login(h.getContent("password"));
0232: login();
0233: }
0234:
0235: /**
0236: * Login this session.
0237: *
0238: * Updates access time, sets initial environment and connects all configured mailboxes.
0239: */
0240: public void login() {
0241: setLastAccess();
0242: setEnv();
0243: connectAll();
0244: }
0245:
0246: /**
0247: * Return a locale-specific string resource
0248: */
0249: public String getStringResource(String key) {
0250: return parent.getStorage().getStringResource(key,
0251: user.getPreferredLocale());
0252: }
0253:
0254: /**
0255: * Create a Message List.
0256: * Fetches a list of headers in folder foldername for part list_part.
0257: * The messagelist will be stored in the "MESSAGES" environment.
0258: *
0259: * @param foldername folder for which a message list should be built
0260: * @param list_part part of list to display (1 = last xx messages, 2 = total-2*xx - total-xx messages)
0261: */
0262: public void createMessageList(String folderhash, int list_part)
0263: throws NoSuchFolderException {
0264:
0265: long time_start = System.currentTimeMillis();
0266: TimeZone tz = TimeZone.getDefault();
0267: DateFormat df = DateFormat.getDateTimeInstance(
0268: DateFormat.DEFAULT, DateFormat.SHORT, user
0269: .getPreferredLocale());
0270: df.setTimeZone(tz);
0271:
0272: try {
0273:
0274: Folder folder = getFolder(folderhash);
0275: Element xml_folder = model.getFolder(folderhash);
0276: Element xml_current = model.setCurrentFolder(folderhash);
0277: Element xml_messagelist = model.getMessageList(xml_folder);
0278:
0279: if (folder == null) {
0280: throw new NoSuchFolderException(folderhash);
0281: }
0282:
0283: long fetch_start = System.currentTimeMillis();
0284:
0285: if (!folder.isOpen()) {
0286: folder.open(Folder.READ_ONLY);
0287: } else {
0288: folder.close(false);
0289: folder.open(Folder.READ_ONLY);
0290: }
0291:
0292: /* Calculate first and last message to show */
0293: int total_messages = folder.getMessageCount();
0294: int new_messages = folder.getNewMessageCount();
0295: int show_msgs = user.getMaxShowMessages();
0296:
0297: xml_messagelist.setAttribute("total", total_messages + "");
0298: xml_messagelist.setAttribute("new", new_messages + "");
0299:
0300: // System.err.println("Total: "+total_messages);
0301:
0302: /* Handle small messagelists correctly */
0303: if (total_messages < show_msgs) {
0304: show_msgs = total_messages;
0305: }
0306: /* Don't accept list-parts smaller than 1 */
0307: if (list_part < 1) {
0308: list_part = 1;
0309: }
0310: for (int k = 0; k < list_part; k++) {
0311: total_messages -= show_msgs;
0312: }
0313: /* Handle beginning of message list */
0314: if (total_messages < 0) {
0315: total_messages = 0;
0316: }
0317: int first = total_messages + 1;
0318: int last = total_messages + show_msgs;
0319: /* Set environment variable */
0320: setEnv();
0321: xml_current.setAttribute("first_msg", first + "");
0322: xml_current.setAttribute("last_msg", last + "");
0323: xml_current.setAttribute("list_part", list_part + "");
0324:
0325: /* Fetch headers */
0326: FetchProfile fp = new FetchProfile();
0327: fp.add(FetchProfile.Item.ENVELOPE);
0328: fp.add(FetchProfile.Item.FLAGS);
0329: fp.add(FetchProfile.Item.CONTENT_INFO);
0330: // System.err.println("Last: "+last+", first: "+first);
0331: Message[] msgs = folder.getMessages(first, last);
0332: //System.err.println(msgs.length + " messages fetching...");
0333: folder.fetch(msgs, fp);
0334: long fetch_stop = System.currentTimeMillis();
0335:
0336: Hashtable header = new Hashtable(15);
0337:
0338: Flags.Flag[] sf;
0339: String from, to, cc, bcc, replyto, subject;
0340: String messageid;
0341:
0342: for (int i = msgs.length - 1; i >= 0; i--) {
0343: // if(((MimeMessage)msgs[i]).getMessageID() == null) {
0344: // folder.close(false);
0345: // folder.open(Folder.READ_WRITE);
0346: // ((MimeMessage)msgs[i]).setHeader("Message-ID","<"+user.getLogin()+"."+System.currentTimeMillis()+".jwebmail@"+user.getDomain()+">");
0347: // ((MimeMessage)msgs[i]).saveChanges();
0348: // folder.close(false);
0349: // folder.open(Folder.READ_ONLY);
0350: // }
0351:
0352: try {
0353: StringTokenizer tok = new StringTokenizer(
0354: ((MimeMessage) msgs[i]).getMessageID(),
0355: "<>");
0356: messageid = tok.nextToken();
0357: } catch (NullPointerException ex) {
0358: // For mail servers that don't generate a Message-ID (Outlook et al)
0359: messageid = user.getLogin() + "." + i
0360: + ".jwebmail@" + user.getDomain();
0361: }
0362:
0363: XMLMessage xml_message = model.getMessage(xml_folder,
0364: msgs[i].getMessageNumber() + "", messageid);
0365:
0366: /* Addresses */
0367: from = "";
0368: replyto = "";
0369: to = "";
0370: cc = "";
0371: bcc = "";
0372: try {
0373: from = MimeUtility.decodeText(Helper
0374: .joinAddress(msgs[i].getFrom()));
0375: replyto = MimeUtility.decodeText(Helper
0376: .joinAddress(msgs[i].getReplyTo()));
0377: to = MimeUtility
0378: .decodeText(Helper
0379: .joinAddress(msgs[i]
0380: .getRecipients(Message.RecipientType.TO)));
0381: cc = MimeUtility
0382: .decodeText(Helper
0383: .joinAddress(msgs[i]
0384: .getRecipients(Message.RecipientType.CC)));
0385: bcc = MimeUtility
0386: .decodeText(Helper
0387: .joinAddress(msgs[i]
0388: .getRecipients(Message.RecipientType.BCC)));
0389: } catch (UnsupportedEncodingException e) {
0390: parent.getStorage().log(Storage.LOG_WARN,
0391: "Unsupported Encoding: " + e.getMessage());
0392: }
0393: if (from == "")
0394: from = getStringResource("unknown sender");
0395: if (to == "")
0396: to = getStringResource("unknown recipient");
0397:
0398: /* Flags */
0399: sf = msgs[i].getFlags().getSystemFlags();
0400: String basepath = parent.getBasePath();
0401:
0402: for (int j = 0; j < sf.length; j++) {
0403: if (sf[j] == Flags.Flag.RECENT)
0404: xml_message.setAttribute("recent", "true");
0405: if (sf[j] == Flags.Flag.SEEN)
0406: xml_message.setAttribute("seen", "true");
0407: if (sf[j] == Flags.Flag.DELETED)
0408: xml_message.setAttribute("deleted", "true");
0409: if (sf[j] == Flags.Flag.ANSWERED)
0410: xml_message.setAttribute("answered", "true");
0411: if (sf[j] == Flags.Flag.DRAFT)
0412: xml_message.setAttribute("draft", "true");
0413: if (sf[j] == Flags.Flag.FLAGGED)
0414: xml_message.setAttribute("flagged", "true");
0415: if (sf[j] == Flags.Flag.USER)
0416: xml_message.setAttribute("user", "true");
0417: }
0418: if (msgs[i] instanceof MimeMessage
0419: && ((MimeMessage) msgs[i]).getContentType()
0420: .toUpperCase().startsWith("MULTIPART/")) {
0421: xml_message.setAttribute("attachment", "true");
0422: }
0423:
0424: if (msgs[i] instanceof MimeMessage) {
0425: int size = ((MimeMessage) msgs[i]).getSize();
0426: size /= 1024;
0427: xml_message.setAttribute("size", (size > 0 ? size
0428: + "" : "<1")
0429: + " kB");
0430: }
0431:
0432: /* Subject */
0433: subject = "";
0434: if (msgs[i].getSubject() != null) {
0435: try {
0436: subject = MimeUtility.decodeText(msgs[i]
0437: .getSubject());
0438: } catch (UnsupportedEncodingException ex) {
0439: parent.getStorage().log(
0440: Storage.LOG_WARN,
0441: "Unsupported Encoding: "
0442: + ex.getMessage());
0443: }
0444: }
0445: if (subject == null || subject.equals("")) {
0446: subject = getStringResource("no subject");
0447: }
0448:
0449: /* Set all of what we found into the DOM */
0450: xml_message.setHeader("FROM", from);
0451: try {
0452: // Modified by exce, start.
0453: // hmm, why decode subject twice? Though it doesn't matter..
0454: // Modified by exce, end.
0455: xml_message.setHeader("SUBJECT", MimeUtility
0456: .decodeText(subject));
0457: } catch (UnsupportedEncodingException e) {
0458: parent.getStorage().log(Storage.LOG_WARN,
0459: "Unsupported Encoding: " + e.getMessage());
0460: }
0461: xml_message.setHeader("TO", to);
0462: xml_message.setHeader("CC", cc);
0463: xml_message.setHeader("BCC", bcc);
0464: xml_message.setHeader("REPLY-TO", replyto);
0465:
0466: /* Date */
0467: Date d = msgs[i].getSentDate();
0468: String ds = "";
0469: if (d != null) {
0470: ds = df.format(d);
0471: }
0472: xml_message.setHeader("DATE", ds);
0473: }
0474: long time_stop = System.currentTimeMillis();
0475: // try {
0476: // XMLCommon.writeXML(model.getRoot(),new FileOutputStream("/tmp/wmdebug"),"");
0477: // } catch(IOException ex) {}
0478:
0479: parent.getStorage().log(
0480: Storage.LOG_DEBUG,
0481: "Construction of message list took "
0482: + (time_stop - time_start)
0483: + " ms. Time for IMAP transfer was "
0484: + (fetch_stop - fetch_start) + " ms.");
0485: folder.close(false);
0486: } catch (NullPointerException e) {
0487: e.printStackTrace();
0488: throw new NoSuchFolderException(folderhash);
0489: } catch (MessagingException ex) {
0490: ex.printStackTrace();
0491: }
0492: }
0493:
0494: /**
0495: * This indicates standard getMessage behaviour: Fetch the message from the IMAP server and store it in the
0496: * current UserModel.
0497: * @see getMessage(String,int,int)
0498: */
0499: public static final int GETMESSAGE_MODE_STANDARD = 0;
0500:
0501: /**
0502: * Set this mode in getMessage to indicate that the message is requested to generate a reply message and
0503: * should therefore be set as the current "work" message.
0504: * @see getMessage(String,int,int)
0505: */
0506: public static final int GETMESSAGE_MODE_REPLY = 1;
0507:
0508: /**
0509: * Set this mode in getMessage to indicate that the message is to be forwarded to someone else and a "work"
0510: * message should be generated.
0511: * @see getMessage(String,int,int)
0512: */
0513: public static final int GETMESSAGE_MODE_FORWARD = 2;
0514:
0515: /**
0516: * This is a wrapper to call getMessage with standard mode.
0517: * @see getMessage(String,int,int)
0518: */
0519: public void getMessage(String folderhash, int msgnum)
0520: throws NoSuchFolderException {
0521: getMessage(folderhash, msgnum, GETMESSAGE_MODE_STANDARD);
0522: }
0523:
0524: /**
0525: * Fetch a message from a folder.
0526: * Will put the messages parameters in the sessions environment
0527: *
0528: * @param foldername Name of the folder were the message should be fetched from
0529: * @param msgnum Number of the message to fetch
0530: * @param mode there are three different modes: standard, reply and forward. reply and forward will enter the message
0531: * into the current work element of the user and set some additional flags on the message if the user
0532: * has enabled this option.
0533: * @see net.wastl.webmail.server.WebMailSession.GETMESSAGE_MODE_STANDARD
0534: * @see net.wastl.webmail.server.WebMailSession.GETMESSAGE_MODE_REPLY
0535: * @see net.wastl.webmail.server.WebMailSession.GETMESSAGE_MODE_FORWARD
0536: */
0537: public void getMessage(String folderhash, int msgnum, int mode)
0538: throws NoSuchFolderException {
0539: // security reasons:
0540: // attachments=null;
0541:
0542: try {
0543: TimeZone tz = TimeZone.getDefault();
0544: DateFormat df = DateFormat.getDateTimeInstance(
0545: DateFormat.DEFAULT, DateFormat.SHORT, user
0546: .getPreferredLocale());
0547: df.setTimeZone(tz);
0548: Folder folder = getFolder(folderhash);
0549: Element xml_folder = model.getFolder(folderhash);
0550:
0551: if (folder == null) {
0552: throw new NoSuchFolderException("No such folder: "
0553: + folderhash);
0554: }
0555:
0556: if (folder.isOpen()
0557: && folder.getMode() == Folder.READ_WRITE) {
0558: folder.close(false);
0559: folder.open(Folder.READ_ONLY);
0560: } else if (!folder.isOpen()) {
0561: folder.open(Folder.READ_ONLY);
0562: }
0563:
0564: MimeMessage m = (MimeMessage) folder.getMessage(msgnum);
0565:
0566: String messageid;
0567: try {
0568: StringTokenizer tok = new StringTokenizer(m
0569: .getMessageID(), "<>");
0570: messageid = tok.nextToken();
0571: } catch (NullPointerException ex) {
0572: // For mail servers that don't generate a Message-ID (Outlook et al)
0573: messageid = user.getLogin() + "." + msgnum
0574: + ".jwebmail@" + user.getDomain();
0575: }
0576:
0577: Element xml_current = model.setCurrentMessage(messageid);
0578: XMLMessage xml_message = model.getMessage(xml_folder, m
0579: .getMessageNumber()
0580: + "", messageid);
0581:
0582: /* Check whether we already cached this message (not only headers but complete)*/
0583: boolean cached = xml_message.messageCompletelyCached();
0584: /* If we cached the message, we don't need to fetch it again */
0585: if (!cached) {
0586: //Element xml_header=model.getHeader(xml_message);
0587:
0588: try {
0589: String from = MimeUtility.decodeText(Helper
0590: .joinAddress(m.getFrom()));
0591: String replyto = MimeUtility.decodeText(Helper
0592: .joinAddress(m.getReplyTo()));
0593: String to = MimeUtility
0594: .decodeText(Helper
0595: .joinAddress(m
0596: .getRecipients(Message.RecipientType.TO)));
0597: String cc = MimeUtility
0598: .decodeText(Helper
0599: .joinAddress(m
0600: .getRecipients(Message.RecipientType.CC)));
0601: String bcc = MimeUtility
0602: .decodeText(Helper
0603: .joinAddress(m
0604: .getRecipients(Message.RecipientType.BCC)));
0605: Date date_orig = m.getSentDate();
0606: String date = getStringResource("no date");
0607: if (date_orig != null) {
0608: date = df.format(date_orig);
0609: }
0610: String subject = "";
0611: if (m.getSubject() != null) {
0612: subject = MimeUtility
0613: .decodeText(m.getSubject());
0614: }
0615: if (subject == null || subject.equals("")) {
0616: subject = getStringResource("no subject");
0617: }
0618:
0619: try {
0620: Flags.Flag[] sf = m.getFlags().getSystemFlags();
0621: for (int j = 0; j < sf.length; j++) {
0622: if (sf[j] == Flags.Flag.RECENT)
0623: xml_message.setAttribute("recent",
0624: "true");
0625: if (sf[j] == Flags.Flag.SEEN)
0626: xml_message
0627: .setAttribute("seen", "true");
0628: if (sf[j] == Flags.Flag.DELETED)
0629: xml_message.setAttribute("deleted",
0630: "true");
0631: if (sf[j] == Flags.Flag.ANSWERED)
0632: xml_message.setAttribute("answered",
0633: "true");
0634: if (sf[j] == Flags.Flag.DRAFT)
0635: xml_message.setAttribute("draft",
0636: "true");
0637: if (sf[j] == Flags.Flag.FLAGGED)
0638: xml_message.setAttribute("flagged",
0639: "true");
0640: if (sf[j] == Flags.Flag.USER)
0641: xml_message
0642: .setAttribute("user", "true");
0643: }
0644: } catch (NullPointerException ex) {
0645: }
0646: if (m.getContentType().toUpperCase().startsWith(
0647: "MULTIPART/")) {
0648: xml_message.setAttribute("attachment", "true");
0649: }
0650:
0651: int size = m.getSize();
0652: size /= 1024;
0653: xml_message.setAttribute("size", (size > 0 ? size
0654: + "" : "<1")
0655: + " kB");
0656:
0657: /* Set all of what we found into the DOM */
0658: xml_message.setHeader("FROM", from);
0659: xml_message.setHeader("SUBJECT", Fancyfier
0660: .apply(subject));
0661: xml_message.setHeader("TO", to);
0662: xml_message.setHeader("CC", cc);
0663: xml_message.setHeader("BCC", bcc);
0664: xml_message.setHeader("REPLY-TO", replyto);
0665: xml_message.setHeader("DATE", date);
0666:
0667: /* Decode MIME contents recursively */
0668: xml_message.removeAllParts();
0669: parseMIMEContent(m, xml_message, messageid);
0670:
0671: } catch (UnsupportedEncodingException e) {
0672: parent.getStorage().log(
0673: Storage.LOG_WARN,
0674: "Unsupported Encoding in parseMIMEContent: "
0675: + e.getMessage());
0676: System.err
0677: .println("Unsupported Encoding in parseMIMEContent: "
0678: + e.getMessage());
0679: }
0680: }
0681: /* Set seen flag (Maybe make that threaded to improve performance) */
0682: if (user.wantsSetFlags()) {
0683: if (folder.isOpen()
0684: && folder.getMode() == Folder.READ_ONLY) {
0685: folder.close(false);
0686: folder.open(Folder.READ_WRITE);
0687: } else if (!folder.isOpen()) {
0688: folder.open(Folder.READ_WRITE);
0689: }
0690: folder.setFlags(msgnum, msgnum, new Flags(
0691: Flags.Flag.SEEN), true);
0692: folder.setFlags(msgnum, msgnum, new Flags(
0693: Flags.Flag.RECENT), false);
0694: if ((mode & GETMESSAGE_MODE_REPLY) == GETMESSAGE_MODE_REPLY) {
0695: folder.setFlags(msgnum, msgnum, new Flags(
0696: Flags.Flag.ANSWERED), true);
0697: }
0698: }
0699: folder.close(false);
0700:
0701: /* In this part we determine whether the message was requested so that it may be used for
0702: further editing (replying or forwarding). In this case we set the current "work" message to the
0703: message we just fetched and then modifiy it a little (quote, add a "Re" to the subject, etc). */
0704: XMLMessage work = null;
0705: if ((mode & GETMESSAGE_MODE_REPLY) == GETMESSAGE_MODE_REPLY
0706: || (mode & GETMESSAGE_MODE_FORWARD) == GETMESSAGE_MODE_FORWARD) {
0707: //System.err.println("Setting work message!");
0708: work = model.setWorkMessage(xml_message);
0709:
0710: String newmsgid = WebMailServer.generateMessageID(user
0711: .getUserName());
0712:
0713: if (work != null
0714: && (mode & GETMESSAGE_MODE_REPLY) == GETMESSAGE_MODE_REPLY) {
0715: String from = work.getHeader("FROM");
0716: work.setHeader("FROM", user.getEmail());
0717: work.setHeader("TO", from);
0718: work.prepareReply(
0719: getStringResource("reply subject prefix"),
0720: getStringResource("reply subject postfix"),
0721: getStringResource("reply message prefix"),
0722: getStringResource("reply message postfix"));
0723:
0724: } else if (work != null
0725: && (mode & GETMESSAGE_MODE_FORWARD) == GETMESSAGE_MODE_FORWARD) {
0726: String from = work.getHeader("FROM");
0727: work.setHeader("FROM", user.getEmail());
0728: work.setHeader("TO", "");
0729: work.setHeader("CC", "");
0730: work
0731: .prepareForward(
0732: getStringResource("forward subject prefix"),
0733: getStringResource("forward subject postfix"),
0734: getStringResource("forward message prefix"),
0735: getStringResource("forward message postfix"));
0736:
0737: /* Copy all references to MIME parts to the new message id */
0738: Enumeration attids = getMimeParts(work
0739: .getAttribute("msgid"));
0740: while (attids.hasMoreElements()) {
0741: String key = (String) attids.nextElement();
0742: StringTokenizer tok2 = new StringTokenizer(key,
0743: "/");
0744: tok2.nextToken();
0745: String newkey = tok2.nextToken();
0746: mime_parts_decoded.put(newmsgid + "/" + newkey,
0747: mime_parts_decoded.get(key));
0748: }
0749: }
0750:
0751: /* Clear the msgnr and msgid fields at last */
0752: work.setAttribute("msgnr", "0");
0753: work.setAttribute("msgid", newmsgid);
0754: prepareCompose();
0755: }
0756: } catch (MessagingException ex) {
0757: ex.printStackTrace();
0758: }
0759: }
0760:
0761: /**
0762: Use depth-first search to go through MIME-Parts recursively.
0763:
0764: @param p Part to begin with
0765: */
0766: protected void parseMIMEContent(Part p, XMLMessagePart parent_part, String msgid) throws MessagingException {
0767: StringBuffer content=new StringBuffer(1000);
0768: XMLMessagePart xml_part;
0769: try {
0770: if(p.getContentType().toUpperCase().startsWith("TEXT/HTML")) {
0771: /* The part is a text in HTML format. We will try to use "Tidy" to create a well-formated
0772: XHTML DOM from it and then remove JavaScript and other "evil" stuff.
0773: For replying to such a message, it will be useful to just remove all of the tags and display
0774: only the text.
0775: */
0776:
0777:
0778: xml_part=parent_part.createPart("html");
0779:
0780: /* Here we create a DOM tree. */
0781: //Tidy tidy=new Tidy();
0782: //tidy.setUpperCaseTags(true);
0783: //Document htmldoc=tidy.parseDOM(p.getInputStream(),null);
0784: org.cyberneko.html.parsers.DOMParser parser =
0785: new org.cyberneko.html.parsers.DOMParser();
0786: parser.parse(new InputSource(p.getInputStream()));
0787: Document htmldoc = parser.getDocument();
0788:
0789: //XMLCommon.debugXML(htmldoc);
0790:
0791: /* Now let's look for all the malicious JavaScript and other <SCRIPT> tags,
0792: URLS containing the "javascript:" and tags containing "onMouseOver" and such
0793: stuff. */
0794: // if(user.getBoolVar("filter javascript")) new JavaScriptCleaner(htmldoc);
0795: new JavaScriptCleaner(htmldoc);
0796:
0797: //XMLCommon.debugXML(htmldoc);
0798: /* HTML doesn't allow us to do such fancy stuff like different quote colors,
0799: perhaps this will be implemented in the future */
0800:
0801: /* So we just add this HTML document to the message part, which will deal with
0802: removing headers and tags that we don't need */
0803: xml_part.addContent(htmldoc);
0804:
0805: } else if(p.getContentType().toUpperCase().startsWith("TEXT") ||
0806: p.getContentType().toUpperCase().startsWith("MESSAGE")) {
0807: /* The part is a standard message part in some incarnation of text (html or plain).
0808: We should decode it and take care of some extra issues like recognize quoted parts,
0809: filter JavaScript parts and replace smileys with smiley-icons if the user has
0810: set wantsFancy() */
0811:
0812: xml_part=parent_part.createPart("text");
0813: // TODO:
0814: System.err.println("text hit");
0815:
0816: BufferedReader in;
0817: if(p instanceof MimeBodyPart) {
0818: int size=p.getSize();
0819: MimeBodyPart mpb=(MimeBodyPart)p;
0820: InputStream is=mpb.getInputStream();
0821:
0822: /* Workaround for Java or Javamail Bug */
0823: is=new BufferedInputStream(is);
0824: ByteStore ba=ByteStore.getBinaryFromIS(is,size);
0825: in=new BufferedReader(new InputStreamReader(new ByteArrayInputStream(ba.getBytes())));
0826: /* End of workaround */
0827: size=is.available();
0828:
0829: } else {
0830: in=new BufferedReader(new InputStreamReader(p.getInputStream()));
0831: }
0832:
0833:
0834: //System.err.println("Content-Type: "+p.getContentType());
0835:
0836:
0837: String token="";
0838: int quote_level=0, old_quotelevel=0;
0839: boolean javascript_mode=false;
0840: /* Read in the message part line by line */
0841: while((token=in.readLine()) != null) {
0842: /* First decode all language and MIME dependant stuff */
0843: // Default to ISO-8859-1 (Western Latin 1)
0844: String charset="ISO-8859-1";
0845:
0846: // Check whether the part contained a charset in the content-type header
0847: StringTokenizer tok2=new StringTokenizer(p.getContentType(),";=");
0848: String blah=tok2.nextToken();
0849: if(tok2.hasMoreTokens()) {
0850: blah=tok2.nextToken().trim();
0851: if(blah.toLowerCase().equals("charset") && tok2.hasMoreTokens()) {
0852: charset=tok2.nextToken().trim();
0853: }
0854: }
0855:
0856: try {
0857: token=new String(token.getBytes(),charset);
0858: } catch(UnsupportedEncodingException ex1) {
0859: parent.getStorage().log(Storage.LOG_INFO,"Java Engine does not support charset "+charset+". Trying to convert from MIME ...");
0860:
0861: try {
0862: charset=MimeUtility.javaCharset(charset);
0863: token=new String(token.getBytes(),charset);
0864:
0865: } catch(UnsupportedEncodingException ex) {
0866: parent.getStorage().log(Storage.LOG_WARN,"Converted charset ("+charset+") does not work. Using default charset (ouput may contain errors)");
0867: token=new String(token.getBytes());
0868: }
0869: }
0870:
0871: /* Here we figure out which quote level this line has, simply by counting how many
0872: ">" are in front of the line, ignoring all whitespaces. */
0873: int current_quotelevel=Helper.getQuoteLevel(token);
0874:
0875:
0876: /* When we are in a different quote level than the last line, we append all we got
0877: so far to the part with the old quotelevel and begin with a clean String buffer */
0878: if(current_quotelevel != old_quotelevel) {
0879: xml_part.addContent(content.toString(),old_quotelevel);
0880: old_quotelevel = current_quotelevel;
0881: content=new StringBuffer(1000);
0882: }
0883:
0884: if(user.wantsBreakLines()) {
0885: Enumeration enum=Helper.breakLine(token,user.getMaxLineLength(),current_quotelevel);
0886:
0887: while(enum.hasMoreElements()) {
0888: String s=(String)enum.nextElement();
0889: if(user.wantsShowFancy()) {
0890: content.append(Fancyfier.apply(s)).append("\n");
0891: } else {
0892: content.append(s).append("\n");
0893: }
0894: }
0895: } else {
0896: if(user.wantsShowFancy()) {
0897: content.append(Fancyfier.apply(token)).append("\n");
0898: } else {
0899: content.append(token).append("\n");
0900: }
0901: }
0902: }
0903: xml_part.addContent(content.toString(),old_quotelevel);
0904: // Modified by exce, start
0905: // Why the following code???
0906: content=new StringBuffer(1000);
0907: // Modified by exce, end.
0908: } else if(p.getContentType().toUpperCase().startsWith("MULTIPART/ALTERNATIVE")) {
0909: /* This is a multipart/alternative part. That means that we should pick one of
0910: the formats and display it for this part. Our current precedence list is
0911: to choose HTML first and then to choose plain text. */
0912: MimeMultipart m=(MimeMultipart)p.getContent();
0913: String[] preferred={"TEXT/HTML","TEXT"};
0914: boolean found=false;
0915: int alt=0;
0916: // Walk though our preferred list of encodings. If we have found a fitting part,
0917: // decode it and replace it for the parent (this is what we really want with an
0918: // alternative!)
0919: // Modified by exce, start
0920: /**
0921: findalt: while(!found && alt < preferred.length) {
0922: for(int i=0;i<m.getCount();i++) {
0923: Part p2=m.getBodyPart(i);
0924: if(p2.getContentType().toUpperCase().startsWith(preferred[alt])) {
0925: parseMIMEContent(p2,parent_part,msgid);
0926: found=true;
0927: break findalt;
0928: }
0929: }
0930: alt++;
0931: }
0932: **/
0933: /**
0934: * When user try to reply a mail, there may be 3 conditions:
0935: * 1. only TEXT exists.
0936: * 2. both HTML and TEXT exist.
0937: * 3. only HTML exists.
0938: *
0939: * We have to choose which part should we quote, that is, we must
0940: * decide the prority of parts to quote. Since quoting HTML is not
0941: * easy and precise (consider a html: <body><div><b>some text..</b>
0942: * </div></body>. Even we try to get text node under <body>, we'll
0943: * just get nothing, because "some text..." is marked up by
0944: * <div><b>. There is no easy way to retrieve text from html
0945: * unless we parse the html to analyse its semantics.
0946: *
0947: * Here is our policy for alternative part:
0948: * 1. Displays HTML but hides TEXT.
0949: * 2. When replying this mail, try to quote TEXT part. If no TEXT
0950: * part exists, quote HTML in best effort(use
0951: * XMLMessagePart.quoteContent() by Sebastian Schaffert.)
0952: */
0953: while (alt < preferred.length) {
0954: for(int i=0;i<m.getCount();i++) {
0955: Part p2=m.getBodyPart(i);
0956: if(p2.getContentType().toUpperCase().startsWith(preferred[alt])) {
0957: System.err.println("Processing: " + p2.getContentType());
0958: parseMIMEContent(p2,parent_part,msgid);
0959: found=true;
0960: break;
0961: }
0962: }
0963: /**
0964: * If we've selected HTML part from alternative part, the TEXT
0965: * part should be hidden from display but keeping in XML for
0966: * later quoting operation.
0967: *
0968: * Of course, this requires some modification on showmessage.xsl.
0969: */
0970: if (found && (alt == 1)) {
0971: Node textPartNode = parent_part.getPartElement().getLastChild();
0972: NamedNodeMap attributes = textPartNode.getAttributes();
0973: boolean hit = false;
0974:
0975: for (int i = 0; i < attributes.getLength(); ++i) {
0976: Node attr = attributes.item(i);
0977: // If type=="TEXT", add a hidden attribute.
0978: if (attr.getNodeName().toUpperCase().equals("TYPE") &&
0979: attr.getNodeValue().toUpperCase().equals("TEXT")) {
0980: ((Element)textPartNode).setAttribute("hidden", "true");
0981: }
0982: }
0983: }
0984: alt++;
0985: }
0986: // Modified by exce, end
0987: if(!found) {
0988: // If we didn't find one of our preferred encodings, choose the first one
0989: // simply pass the parent part because replacement is what we really want with
0990: // an alternative.
0991: parseMIMEContent(m.getBodyPart(0),parent_part,msgid);
0992: }
0993:
0994: } else if(p.getContentType().toUpperCase().startsWith("MULTIPART/")) {
0995: /* This is a standard multipart message. We should recursively walk thorugh all of
0996: the parts and decode them, appending as children to the current part */
0997:
0998: xml_part=parent_part.createPart("multi");
0999:
1000: MimeMultipart m=(MimeMultipart)p.getContent();
1001: for(int i=0;i<m.getCount();i++) {
1002: parseMIMEContent(m.getBodyPart(i),xml_part,msgid);
1003: }
1004: } else {
1005: /* Else treat the part as a binary part that the user should either download or
1006: get displayed immediately in case of an image */
1007: InputStream in=null;
1008: String type="";
1009: if(p.getContentType().toUpperCase().startsWith("IMAGE/JPG") ||
1010: p.getContentType().toUpperCase().startsWith("IMAGE/JPEG")) {
1011: type="jpg";
1012: xml_part=parent_part.createPart("image");
1013: } else if(p.getContentType().toUpperCase().startsWith("IMAGE/GIF")) {
1014: type="gif";
1015: xml_part=parent_part.createPart("image");
1016: } else if(p.getContentType().toUpperCase().startsWith("IMAGE/PNG")) {
1017: type="png";
1018: xml_part=parent_part.createPart("image");
1019: } else {
1020: xml_part=parent_part.createPart("binary");
1021: }
1022: int size=p.getSize();
1023: if(p instanceof MimeBodyPart) {
1024: MimeBodyPart mpb=(MimeBodyPart)p;
1025: System.err.println("MIME Body part (image), Encoding: "+mpb.getEncoding());
1026: InputStream is=mpb.getInputStream();
1027:
1028: /* Workaround for Java or Javamail Bug */
1029: in=new BufferedInputStream(is);
1030: ByteStore ba=ByteStore.getBinaryFromIS(in,size);
1031: in=new ByteArrayInputStream(ba.getBytes());
1032: /* End of workaround */
1033: size=in.available();
1034:
1035: } else {
1036: System.err.println("*** No MIME Body part!! ***");
1037: in=p.getInputStream();
1038: }
1039:
1040: ByteStore data=ByteStore.getBinaryFromIS(in,size);
1041: if(mime_parts_decoded==null) {
1042: mime_parts_decoded=new Hashtable();
1043: }
1044: String name=p.getFileName();
1045: if(name == null || name.equals("")) {
1046: name="unknown."+type;
1047: }
1048: // Modified by exce, start
1049: /**
1050: * As described in FileAttacher.java line #95 and
1051: * SendMessage.java line #390, we use MimeUtility.decodeText() to
1052: * decode attachment file name.
1053: */
1054: try {
1055: name = MimeUtility.decodeText(name);
1056: } catch (Exception e) {
1057: System.err.println(e);
1058: }
1059: // Modified by exce, end
1060: // Eliminate space characters. Should do some more things in the future
1061: name=name.replace(' ','_');
1062: data.setContentType(p.getContentType());
1063: data.setContentEncoding("BINARY");
1064: mime_parts_decoded.put(msgid+"/"+name,data);
1065:
1066: // Modified by exce, start
1067: /**
1068: * For multibytes language system, we have to separate filename into
1069: * 2 format: one for display (UTF-8 encoded), another for encode the
1070: * url of hyperlink.
1071: * `filename' is for display, while `hrefFileName' is for hyperlink.
1072: * To make use of these two attributes, `showmessage.xsl' is slightly
1073: * modified.
1074: */
1075: data.setName(name);
1076: xml_part.setAttribute("filename",name);
1077: // Transcode name into UTF-8 bytes then make a new ISO8859_1 string to encode URL.
1078: xml_part.setAttribute("hrefFileName", URLEncoder.encode(new String(name.getBytes("UTF-8"), "ISO8859_1")));
1079: // Modified by exce, end
1080: xml_part.setAttribute("size",size+"");
1081: String description=p.getDescription()==null?"":p.getDescription();
1082: xml_part.setAttribute("description",description);
1083: StringTokenizer tok=new StringTokenizer(p.getContentType(),";");
1084: xml_part.setAttribute("content-type",tok.nextToken().toLowerCase());
1085: }
1086: } catch(java.io.IOException ex) {
1087: ex.printStackTrace();
1088: } catch(MessagingException ex) {
1089: throw ex;
1090: } catch(Exception ex) {
1091: ex.printStackTrace();
1092: }
1093: }
1094:
1095: public ByteStore getMIMEPart(String msgid, String name) {
1096: if (mime_parts_decoded != null) {
1097: return (ByteStore) mime_parts_decoded.get(msgid + "/"
1098: + name);
1099: } else {
1100: return null;
1101: }
1102: }
1103:
1104: public Enumeration getMimeParts(String msgid) {
1105: if(mime_parts_decoded == null) {
1106: mime_parts_decoded=new Hashtable();
1107: }
1108: Enumeration enum=mime_parts_decoded.keys();
1109: Vector v=new Vector();
1110: while(enum.hasMoreElements()) {
1111: String key=(String)enum.nextElement();
1112: if(key.startsWith(msgid)) {
1113: v.addElement(key);
1114: }
1115: }
1116: return v.elements();
1117: }
1118:
1119: public void clearWork() {
1120: clearAttachments();
1121: model.clearWork();
1122: }
1123:
1124: public void prepareCompose() {
1125: model.getWorkMessage().getFirstMessageTextPart().addContent(
1126: "\n--\n", 0);
1127: model.getWorkMessage().getFirstMessageTextPart().addContent(
1128: user.getSignature(), 0);
1129: }
1130:
1131: /**
1132: * This method removes all of the attachments of the current "work" message
1133: */
1134: public void clearAttachments() {
1135: attachments_size=0;
1136:
1137: XMLMessage xml_message=model.getWorkMessage();
1138:
1139: String msgid=xml_message.getAttribute("msgid");
1140:
1141: Enumeration enum=getMimeParts(msgid);
1142: attachments_size=0;
1143: while(enum.hasMoreElements()) {
1144: mime_parts_decoded.remove((String)enum.nextElement());
1145: }
1146: }
1147:
1148: /**
1149: * This method returns a table of attachments for the current "work" message
1150: */
1151: public Hashtable getAttachments() {
1152: Hashtable hash=new Hashtable();
1153: XMLMessage xml_message=model.getWorkMessage();
1154:
1155: String msgid=xml_message.getAttribute("msgid");
1156:
1157: Enumeration enum=getMimeParts(msgid);
1158: while(enum.hasMoreElements()) {
1159: String key=(String)enum.nextElement();
1160: String filename=key.substring(msgid.length()+1);
1161: hash.put(filename,mime_parts_decoded.get(key));
1162: }
1163:
1164: return hash;
1165: }
1166:
1167: /**
1168: * This method returns the attachment with the given name of the current "work" message
1169: */
1170: public ByteStore getAttachment(String key) {
1171: XMLMessage xml_message = model.getWorkMessage();
1172: String msgid = xml_message.getAttribute("msgid");
1173:
1174: return getMIMEPart(msgid, key);
1175: }
1176:
1177: /**
1178: * Add an attachment to the current work message.
1179: * @param name Name of the attachment (e.g. filename)
1180: * @param bs The contents of the attachment, as a ByteStore object
1181: * @param description A short description of the contents (will be used as the "Description:" header
1182: */
1183: public void addWorkAttachment(String name, ByteStore bs, String description) throws WebMailException {
1184: XMLMessage xml_message=model.getWorkMessage();
1185: XMLMessagePart xml_multipart=xml_message.getFirstMessageMultiPart();
1186:
1187: String msgid=xml_message.getAttribute("msgid");
1188:
1189: bs.setDescription(description);
1190:
1191: Enumeration enum=getMimeParts(msgid);
1192: attachments_size=0;
1193: while(enum.hasMoreElements()) {
1194: ByteStore b=(ByteStore)mime_parts_decoded.get((String)enum.nextElement());
1195: attachments_size+=b.getSize();
1196: }
1197:
1198: int max_size=0;
1199: try {
1200: max_size=Integer.parseInt( parent.getStorage().getConfig("MAX ATTACH SIZE"));
1201: } catch(NumberFormatException e) {
1202: parent.getStorage().log(Storage.LOG_WARN,"Invalid setting for parameter \"MAX ATTACH SIZE\". Must be a number!");
1203: }
1204:
1205: if(attachments_size+bs.getSize() > max_size) {
1206: throw new WebMailException("Attachments are too big. The sum of the sizes may not exceed "+max_size+" bytes.");
1207: } else {
1208: mime_parts_decoded.put(msgid+"/"+name,bs);
1209: attachments_size+=bs.getSize();
1210: XMLMessagePart xml_part=xml_multipart.createPart("binary");
1211:
1212: xml_part.setAttribute("filename",name);
1213: xml_part.setAttribute("size",bs.getSize()+"");
1214: xml_part.setAttribute("description",description);
1215: xml_part.setAttribute("content-type",bs.getContentType().toLowerCase());
1216: }
1217: setEnv();
1218: //XMLCommon.debugXML(model.getRoot());
1219: }
1220:
1221: /**
1222: * Remove the attachment with the given name from the current work message.
1223: */
1224: public void removeWorkAttachment(String name) {
1225: XMLMessage xml_message=model.getWorkMessage();
1226: XMLMessagePart xml_multipart=xml_message.getFirstMessageMultiPart();
1227:
1228: String msgid=xml_message.getAttribute("msgid");
1229:
1230: mime_parts_decoded.remove(msgid+"/"+name);
1231:
1232: Enumeration enum=getMimeParts(msgid);
1233: attachments_size=0;
1234: while(enum.hasMoreElements()) {
1235: ByteStore b=(ByteStore)mime_parts_decoded.get((String)enum.nextElement());
1236: attachments_size+=b.getSize();
1237: }
1238:
1239: enum=xml_multipart.getParts();
1240: XMLMessagePart oldpart=null;
1241: while(enum.hasMoreElements()) {
1242: XMLMessagePart tmp=(XMLMessagePart)enum.nextElement();
1243: if(tmp.getAttribute("filename") != null &&
1244: tmp.getAttribute("filename").equals(name)) {
1245: oldpart=tmp;
1246: break;
1247: }
1248: }
1249: if(oldpart != null) {
1250: xml_multipart.removePart(oldpart);
1251: }
1252: setEnv();
1253: //XMLCommon.debugXML(model.getRoot());
1254: }
1255:
1256: /**
1257: * Store a message in the environment for further processing.
1258: */
1259: public void storeMessage(HTTPRequestHeader head) {
1260: XMLMessage xml_message=model.getWorkMessage();
1261: XMLMessagePart xml_textpart=xml_message.getFirstMessageTextPart();
1262:
1263: /* Store the already typed message if necessary/possible */
1264: if(head.isContentSet("BODY")) {
1265: StringBuffer content=new StringBuffer();
1266: // Modified by exce, start
1267: /**
1268: * Because the data transfered through HTTP should be ISO8859_1,
1269: * HTTPRequestHeader is also ISO8859_1 encoded. Furthermore, the
1270: * string we used in brwoser is UTF-8 encoded, hence we have to
1271: * transcode the stored variables from ISO8859_1 to UTF-8 so that
1272: * the client browser displays correctly.
1273: */
1274: String bodyString;
1275: try {
1276: bodyString = new String(head.getContent("BODY").getBytes("ISO8859_1"), "UTF-8");
1277: } catch (UnsupportedEncodingException e) {
1278: e.printStackTrace();
1279: bodyString = head.getContent("BODY");
1280: }
1281:
1282: // If the user enabled "break line", then do it!
1283: if(user.wantsBreakLines()) {
1284: // StringTokenizer tok=new StringTokenizer(head.getContent("BODY"),"\n");
1285: StringTokenizer tok=new StringTokenizer(bodyString,"\n");
1286: while(tok.hasMoreTokens()) {
1287: String line=tok.nextToken();
1288: Enumeration enum=Helper.breakLine(line,user.getMaxLineLength(),
1289: Helper.getQuoteLevel(line));
1290: while(enum.hasMoreElements()) {
1291: content.append((String)enum.nextElement()).append('\n');
1292: }
1293: }
1294: } else {
1295: // content.append(head.getContent("BODY"));
1296: content.append(bodyString);
1297: // Modified by exce, end
1298: }
1299: xml_textpart.removeAllContent();
1300: xml_textpart.addContent(content.toString(),0);
1301: }
1302:
1303: if(head.isContentSet("TO")) {
1304: // Modified by exce, start
1305: // xml_message.setHeader("TO",head.getContent("TO"));
1306: try {
1307: xml_message.setHeader("TO", new String(head.getContent("TO").getBytes("ISO8859_1"), "UTF-8"));
1308: } catch (UnsupportedEncodingException e) {
1309: e.printStackTrace();
1310: xml_message.setHeader("TO",head.getContent("TO"));
1311: }
1312: // Modified by exce, end
1313: }
1314: if(head.isContentSet("CC")) {
1315: // Modified by exce, start
1316: // xml_message.setHeader("CC",head.getContent("CC"));
1317: try {
1318: xml_message.setHeader("CC", new String(head.getContent("CC").getBytes("ISO8859_1"), "UTF-8"));
1319: } catch (UnsupportedEncodingException e) {
1320: e.printStackTrace();
1321: xml_message.setHeader("CC",head.getContent("CC"));
1322: }
1323: // Modified by exce, end
1324: }
1325: if(head.isContentSet("BCC")) {
1326: // Modified by exce, start
1327: // xml_message.setHeader("BCC",head.getContent("BCC"));
1328: try {
1329: xml_message.setHeader("BCC", new String(head.getContent("BCC").getBytes("ISO8859_1"), "UTF-8"));
1330: } catch (UnsupportedEncodingException e) {
1331: e.printStackTrace();
1332: xml_message.setHeader("BCC",head.getContent("BCC"));
1333: }
1334: // Modified by exce, end
1335: }
1336: if(head.isContentSet("REPLY-TO")) {
1337: // Modified by exce, start
1338: // xml_message.setHeader("REPLY-TO",head.getContent("REPLY-TO"));
1339: try {
1340: xml_message.setHeader("REPLY-TO", new String(head.getContent("REPLY-TO").getBytes("ISO8859_1"), "UTF-8"));
1341: } catch (UnsupportedEncodingException e) {
1342: e.printStackTrace();
1343: xml_message.setHeader("REPLY-TO",head.getContent("REPLY-TO"));
1344: }
1345: // Modified by exce, end
1346: }
1347: if(head.isContentSet("SUBJECT")) {
1348: // Modified by exce, start
1349: // xml_message.setHeader("SUBJECT",head.getContent("SUBJECT"));
1350: try {
1351: xml_message.setHeader("SUBJECT", new String(head.getContent("SUBJECT").getBytes("ISO8859_1"), "UTF-8"));
1352: } catch (UnsupportedEncodingException e) {
1353: e.printStackTrace();
1354: xml_message.setHeader("SUBJECT",head.getContent("SUBJECT"));
1355: }
1356: // Modified by exce, end
1357: }
1358: setEnv();
1359: }
1360: /**
1361: * Connect to all Mailhosts
1362: * @deprecated Should use refreshFolderInformation now.
1363: */
1364: public void connectAll() {
1365: refreshFolderInformation();
1366: }
1367:
1368: /**
1369: Get a childfolder of a rootfolder for a specified hash value
1370: */
1371: public Folder getChildFolder(Folder root, String folderhash) {
1372: return getFolder(folderhash);
1373: }
1374:
1375: /**
1376: * Get the folder with the given hashvalue.
1377: * @returns Folder with the given hashvalue
1378: */
1379: public Folder getFolder(String folderhash) {
1380: return (Folder) folders.get(folderhash);
1381: }
1382:
1383: /**
1384: * This method tries to generate a unique folder identifier for the given folder.
1385: * This method generates an MD5 sum over the complete folder URL, if possible.
1386: * @see getFolderTree
1387: * @see net.wastl.webmail.misc.MD5
1388: */
1389: protected String generateFolderHash(Folder folder) {
1390: String id = Integer.toHexString(folder.hashCode());
1391: // If possible, use the MD5-Sum for the folder ID because it is persistant over sessions
1392: try {
1393: MD5 md5 = new MD5(folder.getURLName());
1394: id = md5.asHex();
1395: } catch (MessagingException ex) {
1396: }
1397:
1398: return id;
1399: }
1400:
1401: /**
1402: * Construct the folder subtree for the given folder and append it to xml_parent.
1403: *
1404: * @param folder the folder where we begin
1405: * @param xml_parent the XML Element where the gathered information will be appended
1406: * @param subscribed_only Only list subscribed folders
1407: * @returns maximum depth of the folder tree (needed to calculate the necessary columns in a table)
1408: */
1409: protected int getFolderTree(Folder folder, Element xml_parent,
1410: boolean subscribed_only) {
1411: int depth = 1;
1412:
1413: String id = generateFolderHash(folder);
1414:
1415: boolean holds_folders = false, holds_messages = false;
1416: Element xml_folder;
1417: try {
1418: holds_folders = (folder.getType() & Folder.HOLDS_FOLDERS) == Folder.HOLDS_FOLDERS;
1419: holds_messages = (folder.getType() & Folder.HOLDS_MESSAGES) == Folder.HOLDS_MESSAGES;
1420: xml_folder = model.createFolder(id, folder.getName(),
1421: holds_folders, holds_messages);
1422: if (folder.isSubscribed()) {
1423: xml_folder.setAttribute("subscribed", "true");
1424: } else {
1425: xml_folder.setAttribute("subscribed", "false");
1426: }
1427: } catch (MessagingException ex) {
1428: xml_folder = model.createFolder(id, folder.getName(),
1429: holds_folders, holds_messages);
1430: xml_folder.setAttribute("error", ex.getMessage());
1431: }
1432:
1433: folders.put(id, folder);
1434:
1435: try {
1436: /* This folder can contain messages */
1437: if (holds_messages) {
1438:
1439: Element messagelist = model.createMessageList();
1440:
1441: int total_messages = folder.getMessageCount();
1442: int new_messages = folder.getNewMessageCount();
1443:
1444: if ((total_messages == -1 || new_messages == -1)
1445: || !folder.isOpen()) {
1446: folder.open(Folder.READ_ONLY);
1447: total_messages = folder.getMessageCount();
1448: new_messages = folder.getNewMessageCount();
1449: }
1450: folder.close(false);
1451:
1452: messagelist.setAttribute("total", total_messages + "");
1453: messagelist.setAttribute("new", new_messages + "");
1454: xml_folder.appendChild(messagelist);
1455: }
1456: } catch (MessagingException ex) {
1457: xml_folder.setAttribute("error", ex.getMessage());
1458: }
1459:
1460: try {
1461: /* There are subfolders, get them! */
1462: if (holds_folders) {
1463: Folder[] subfolders;
1464:
1465: /* If the user only wanted to see subscribed folders, call listSubscribed
1466: otherwise call list() */
1467: if (subscribed_only) {
1468: try {
1469: subfolders = folder.listSubscribed();
1470: } catch (MessagingException ex) {
1471: System.err.println("Subscribe not supported");
1472: subfolders = folder.list();
1473: }
1474: } else {
1475: subfolders = folder.list();
1476: }
1477: int max_tree_depth = 0;
1478:
1479: /* Recursiveley add subfolders to the XML model */
1480: for (int i = 0; i < subfolders.length; i++) {
1481: int tree_depth = getFolderTree(subfolders[i],
1482: xml_folder, subscribed_only);
1483: if (tree_depth > max_tree_depth) {
1484: max_tree_depth = tree_depth;
1485: }
1486: }
1487: depth += max_tree_depth;
1488: }
1489: } catch (MessagingException ex) {
1490: xml_folder.setAttribute("error", ex.getMessage());
1491: }
1492:
1493: xml_parent.appendChild(xml_folder);
1494: return depth;
1495: }
1496:
1497: public void refreshFolderInformation() {
1498: refreshFolderInformation(false);
1499: }
1500:
1501: /**
1502: * Refresh Information about folders.
1503: * Tries to connect folders that are not yet connected.
1504: */
1505: public void refreshFolderInformation(boolean subscribed_only) {
1506: setEnv();
1507: if (folders == null)
1508: folders = new Hashtable();
1509: Folder cur_folder = null;
1510: String cur_mh_id = "";
1511: Enumeration mailhosts = user.mailHosts();
1512: int max_depth = 0;
1513: while (mailhosts.hasMoreElements()) {
1514: cur_mh_id = (String) mailhosts.nextElement();
1515:
1516: MailHostData mhd = user.getMailHost(cur_mh_id);
1517:
1518: URLName url = new URLName(mhd.getHostURL());
1519:
1520: Element mailhost = model.createMailhost(mhd.getName(), mhd
1521: .getID(), url.toString());
1522:
1523: int depth = 0;
1524:
1525: try {
1526:
1527: cur_folder = getRootFolder(cur_mh_id);
1528:
1529: /* Cannot unsubscribe root folder! */
1530: try {
1531: cur_folder.setSubscribed(true);
1532: } catch (MessagingException ex) {
1533: // Only IMAP supports subscription
1534: }
1535:
1536: /* Here we try to determine the remote IMAP or POP host. There is no problem if this fails
1537: (it will most likely for POP3), so the exception is caught and not handled */
1538: try {
1539: // Washington University
1540: if (cur_folder.getFolder(
1541: "~" + mhd.getLogin() + "/mail").exists()) {
1542: /* Washington University stores user mailboxes as
1543: * ~user/mail/... */
1544: depth = getFolderTree(cur_folder
1545: .getFolder("INBOX"), mailhost,
1546: subscribed_only);
1547: if (depth > max_depth) {
1548: max_depth = depth;
1549: }
1550: depth = getFolderTree(cur_folder.getFolder("~"
1551: + mhd.getLogin() + "/mail"), mailhost,
1552: subscribed_only);
1553: }
1554: /* Cyrus, Courier & Co have their folders beneath the INBOX */
1555: else if (cur_folder.getFolder("INBOX").exists()) {
1556: depth = getFolderTree(cur_folder
1557: .getFolder("INBOX"), mailhost,
1558: subscribed_only);
1559: }
1560: } /* If it didn't work it failed in the "if" statement, since "getFolderTree" doesn't throw exceptions
1561: so what we want to do is to simply construct the folder tree for INBOX */
1562: catch (MessagingException ex) {
1563: depth = getFolderTree(
1564: cur_folder.getFolder("INBOX"), mailhost,
1565: subscribed_only);
1566: }
1567: }
1568: // Here a more serious exception has been caught (Connection failed)
1569: catch (MessagingException ex) {
1570: mailhost.setAttribute("error", ex.getMessage());
1571: parent.getStorage().log(
1572: Storage.LOG_WARN,
1573: "Error connecting to mailhost ("
1574: + url.toString() + "): "
1575: + ex.getMessage());
1576: }
1577:
1578: if (depth > max_depth) {
1579: max_depth = depth;
1580: }
1581:
1582: model.addMailhost(mailhost);
1583:
1584: }
1585:
1586: model.setStateVar("max folder depth", (1 + max_depth) + "");
1587: }
1588:
1589: public void refreshFolderInformation(String folderhash) {
1590: Folder folder = getFolder(folderhash);
1591: Element xml_folder = model.getFolder(folderhash);
1592:
1593: if (xml_folder.getAttribute("holds_messages").toLowerCase()
1594: .equals("true")) {
1595: try {
1596: Element messagelist = model.createMessageList();
1597:
1598: int total_messages = folder.getMessageCount();
1599: int new_messages = folder.getNewMessageCount();
1600:
1601: if ((total_messages == -1 || new_messages == -1)
1602: && !folder.isOpen()) {
1603: folder.open(Folder.READ_ONLY);
1604: total_messages = folder.getMessageCount();
1605: new_messages = folder.getNewMessageCount();
1606: }
1607: if (folder.isOpen())
1608: folder.close(false);
1609:
1610: messagelist.setAttribute("total", total_messages + "");
1611: messagelist.setAttribute("new", new_messages + "");
1612:
1613: model.removeMessageList(xml_folder);
1614: xml_folder.appendChild(messagelist);
1615:
1616: } catch (MessagingException ex) {
1617: xml_folder.setAttribute("error", ex.getMessage());
1618: }
1619: }
1620:
1621: }
1622:
1623: /**
1624: * Try to subscribe to a folder (i.e. unhide it)
1625: */
1626: public void subscribeFolder(String folderhash) {
1627: Folder folder = getFolder(folderhash);
1628:
1629: // Only IMAP supports subscription...
1630: try {
1631: folder.setSubscribed(true);
1632: } catch (MessagingException ex) {
1633: //System.err.println("Folder subscription not supported");
1634: }
1635: }
1636:
1637: /**
1638: * Try to unsubscribe from a folder (i.e. hide it)
1639: */
1640: public void unsubscribeFolder(String folderhash) {
1641: Folder folder = getFolder(folderhash);
1642:
1643: // Only IMAP supports subscription...
1644: try {
1645: folder.setSubscribed(false);
1646: } catch (MessagingException ex) {
1647: //System.err.println("Folder subscription not supported");
1648: }
1649: }
1650:
1651: /**
1652: * Subscribe all folders for a Mailhost
1653: * Do it the non-recursive way: Uses a simple Queue :-)
1654: */
1655: public void setSubscribedAll(String id, boolean subscribed)
1656: throws MessagingException {
1657: Folder folder = getRootFolder(id);
1658: Queue q = new Queue();
1659: q.queue(folder);
1660: // Only IMAP supports subscription...
1661: try {
1662: while (!q.isEmpty()) {
1663: folder = (Folder) q.next();
1664:
1665: folder.setSubscribed(subscribed);
1666: Folder[] list = folder.list();
1667: for (int i = 0; i < list.length; i++) {
1668: q.queue(list[i]);
1669: }
1670: }
1671: } catch (MessagingException ex) {
1672: }
1673: }
1674:
1675: /**
1676: Disconnect from all Mailhosts
1677: */
1678: public void disconnectAll() {
1679: Enumeration e = user.mailHosts();
1680: while (e.hasMoreElements()) {
1681: String name = (String) e.nextElement();
1682: disconnect(name);
1683: }
1684: e = stores.keys();
1685: while (e.hasMoreElements()) {
1686: String name = (String) e.nextElement();
1687: Store st = (Store) stores.get(name);
1688: try {
1689: st.close();
1690: parent.getStorage().log(
1691: Storage.LOG_INFO,
1692: "Mail: Connection to " + st.toString()
1693: + " closed.");
1694: } catch (Exception ex) {
1695: parent.getStorage().log(
1696: Storage.LOG_WARN,
1697: "Mail: Failed to close connection to "
1698: + st.toString() + ". Reason: "
1699: + ex.getMessage());
1700: }
1701: stores.remove(name);
1702: }
1703: folders = null;
1704: }
1705:
1706: public Folder getRootFolder(String name) throws MessagingException {
1707: if (connections != null && connections.containsKey(name)) {
1708: return (Folder) connections.get(name);
1709: } else {
1710: return connect(name);
1711: }
1712: }
1713:
1714: protected Store connectStore(String host, String protocol,
1715: String login, String password) throws MessagingException {
1716: /* Check whether the domain of this user allows to connect to the host */
1717: WebMailVirtualDomain vdom = parent.getStorage()
1718: .getVirtualDomain(user.getDomain());
1719: if (!vdom.isAllowedHost(host)) {
1720: throw new MessagingException(
1721: "You are not allowed to connect to this host");
1722: }
1723:
1724: /* Check if this host is already connected. Use connection if true, create a new one if false. */
1725: Store st = (Store) stores.get(host + "-" + protocol);
1726: if (st == null) {
1727: st = mailsession.getStore(protocol);
1728: stores.put(host + "-" + protocol, st);
1729: }
1730:
1731: /* Maybe this is a new store or this store has been disconnected. Reconnect if this is the case. */
1732: if (!st.isConnected()) {
1733: try {
1734: st.connect(host, login, password);
1735: parent.getStorage().log(Storage.LOG_INFO,
1736: "Mail: Connection to " + st.toString() + ".");
1737: } catch (AuthenticationFailedException ex) {
1738: /* If login fails, try the login_password */
1739: if (!login_password.equals(password)
1740: && parent.getStorage().getConfig(
1741: "FOLDER TRY LOGIN PASSWORD")
1742: .toUpperCase().equals("YES")) {
1743: st.connect(host, login, login_password);
1744: parent
1745: .getStorage()
1746: .log(
1747: Storage.LOG_INFO,
1748: "Mail: Connection to "
1749: + st.toString()
1750: + ", second attempt with login password succeeded.");
1751: } else {
1752: throw ex;
1753: }
1754: }
1755: }
1756: return st;
1757: }
1758:
1759: /**
1760: Connect to mailhost "name"
1761: */
1762: public Folder connect(String name) throws MessagingException {
1763: MailHostData m = user.getMailHost(name);
1764: URLName url = new URLName(m.getHostURL());
1765:
1766: Store st = connectStore(url.getHost(), url.getProtocol(), m
1767: .getLogin(), m.getPassword());
1768:
1769: //System.err.println("Default folder: "+st.getDefaultFolder().toString());
1770:
1771: Folder f = st.getDefaultFolder();
1772: connections.put(name, f);
1773: parent.getStorage().log(
1774: Storage.LOG_INFO,
1775: "Mail: Folder " + f.toString() + " opened at store "
1776: + st.toString() + ".");
1777: return f;
1778: }
1779:
1780: /**
1781: Disconnect from mailhost "name"
1782: */
1783: public void disconnect(String name) {
1784: try {
1785: Folder f = (Folder) connections.get(name);
1786: if (f != null && f.isOpen()) {
1787: f.close(true);
1788: Store st = ((Folder) connections.get(name)).getStore();
1789: //st.close();
1790: parent.getStorage().log(
1791: Storage.LOG_INFO,
1792: "Mail: Disconnected from folder "
1793: + f.toString() + " at store "
1794: + st.toString() + ".");
1795: } else {
1796: parent.getStorage().log(Storage.LOG_WARN,
1797: "Mail: Folder " + name + " was null???.");
1798: }
1799: } catch (MessagingException ex) {
1800: // Should not happen
1801: ex.printStackTrace();
1802: } catch (NullPointerException ex) {
1803: // This happens when deleting a folder with an error
1804: ex.printStackTrace();
1805: } finally {
1806: connections.remove(name);
1807: }
1808: }
1809:
1810: /**
1811: * Terminate this session.
1812: *
1813: * This will expunge deleted messages, close all mailbox connections, save the user data and then
1814: * remove this session from the session list, effectively destroying this session.
1815: */
1816: public void logout() {
1817: if (!is_logged_out) {
1818: is_logged_out = true;
1819: expungeFolders();
1820: disconnectAll();
1821: user.logout();
1822: saveData();
1823: parent.getStorage()
1824: .log(
1825: Storage.LOG_INFO,
1826: "WebMail: Session " + getSessionCode()
1827: + " logout.");
1828: // Make sure the session is invalidated
1829: if (sess != null) {
1830: try {
1831: Class srvltreq = Class
1832: .forName("javax.servlet.http.HttpSession");
1833: if (srvltreq.isInstance(sess)) {
1834: ((javax.servlet.http.HttpSession) sess)
1835: .invalidate();
1836: }
1837: } catch (Throwable t) {
1838: }
1839: }
1840: if (parent.getSession(getSessionCode()) != null) {
1841: parent.removeSession(this );
1842: }
1843: } else {
1844: System.err
1845: .println("WARNING: Session was already logged out. Ignoring logout request.");
1846: }
1847: }
1848:
1849: /**
1850: * Check whether this session is already logged out.
1851: * Useful to avoid loops.
1852: */
1853: public boolean isLoggedOut() {
1854: return is_logged_out;
1855: }
1856:
1857: /**
1858: * Return the session id that was generated for this session.
1859: */
1860: public String getSessionCode() {
1861: return session_code;
1862: }
1863:
1864: /**
1865: * Return the last access time of this session
1866: *
1867: * @see TimeableConnection
1868: */
1869: public long getLastAccess() {
1870: return last_access;
1871: }
1872:
1873: /**
1874: * Update the last access time.
1875: * Sets the last access time to the current time.
1876: *
1877: * @see TimeableConnection
1878: */
1879: public void setLastAccess() {
1880: last_access = System.currentTimeMillis();
1881: //System.err.println("Setting last access to session: "+last_access);
1882: }
1883:
1884: /**
1885: * Handle a timeout for this session.
1886: * This calls the logout method, effectively terminating this session.
1887: *
1888: * @see TimeableConnection
1889: * @see logout()
1890: */
1891: public void timeoutOccured() {
1892: parent.getStorage().log(Storage.LOG_WARN,
1893: "WebMail: Session " + getSessionCode() + " timeout.");
1894: logout();
1895: }
1896:
1897: public long getTimeout() {
1898: long i = 600000;
1899: try {
1900: i = Long.parseLong(parent.getStorage().getConfig(
1901: "session timeout"));
1902: } catch (NumberFormatException ex) {
1903: ex.printStackTrace();
1904: }
1905: return i;
1906: }
1907:
1908: public Locale getLocale() {
1909: return user.getPreferredLocale();
1910: }
1911:
1912: public void saveData() {
1913: parent.getStorage().saveUserData(user.getUserName(),
1914: user.getDomain());
1915: }
1916:
1917: protected static int[] getSelectedMessages(HTTPRequestHeader head,
1918: int max) {
1919: // System.err.print(" - select messages...");
1920:
1921: Enumeration e = head.getContent().keys();
1922: int _msgs[] = new int[max];
1923: int j = 0;
1924:
1925: while (e.hasMoreElements()) {
1926: String s = (String) e.nextElement();
1927: if (s.startsWith("CH") && head.getContent(s).equals("on")) {
1928: try {
1929: _msgs[j] = Integer.parseInt(s.substring(3));
1930: // System.err.print(_msgs[j]+" ");
1931: j++;
1932: } catch (NumberFormatException ex) {
1933: ex.printStackTrace();
1934: }
1935: }
1936: }
1937: //System.err.println();
1938:
1939: int msgs[] = new int[j];
1940: for (int i = 0; i < j; i++) {
1941: msgs[i] = _msgs[i];
1942: }
1943: return msgs;
1944: }
1945:
1946: /**
1947: * Expunge all folders that have messages waiting to be deleted
1948: */
1949: public void expungeFolders() {
1950: if(need_expunge_folders != null) {
1951: Enumeration enum=need_expunge_folders.elements();
1952: while(enum.hasMoreElements()) {
1953: String hash=(String)enum.nextElement();
1954: if(user.wantsSetFlags()) {
1955: Folder f=getFolder(hash);
1956: try {
1957: if(f.isOpen()) {
1958: f.close(false);
1959: }
1960: f.open(Folder.READ_WRITE);
1961: // POP3 doesn't support expunge!
1962: try {
1963: f.expunge();
1964: } catch(MessagingException ex) {}
1965: f.close(true);
1966: } catch(MessagingException ex) {
1967: // XXXX
1968: ex.printStackTrace();
1969: }
1970: }
1971: }
1972: }
1973: }
1974:
1975: /**
1976: Change the Flags of the messages the user selected.
1977:
1978: */
1979: public void setFlags(String folderhash, HTTPRequestHeader head)
1980: throws MessagingException {
1981:
1982: if (head.isContentSet("copymovemsgs")
1983: && head.getContent("COPYMOVE").equals("COPY")) {
1984: copyMoveMessage(folderhash, head.getContent("TO"), head,
1985: false);
1986: } else if (head.isContentSet("copymovemsgs")
1987: && head.getContent("COPYMOVE").equals("MOVE")) {
1988: copyMoveMessage(folderhash, head.getContent("TO"), head,
1989: true);
1990: } else if (head.isContentSet("flagmsgs")) {
1991:
1992: System.err.println("setting message flags");
1993: Folder folder = getFolder(folderhash);
1994:
1995: //System.err.println("Processing Request Header...");
1996:
1997: /* Get selected messages */
1998: int msgs[] = getSelectedMessages(head, folder
1999: .getMessageCount());
2000:
2001: //System.err.println(" - get flags...");
2002:
2003: /* Get selected flags */
2004: Flags fl = new Flags(Flags.Flag.USER);
2005: if (head.getContent("MESSAGE FLAG").equals("DELETED")) {
2006: fl = new Flags(Flags.Flag.DELETED);
2007: if (need_expunge_folders == null) {
2008: need_expunge_folders = new Vector();
2009: }
2010: need_expunge_folders.addElement(folderhash);
2011: } else if (head.getContent("MESSAGE FLAG").equals("SEEN")) {
2012: fl = new Flags(Flags.Flag.SEEN);
2013: } else if (head.getContent("MESSAGE FLAG").equals("RECENT")) {
2014: fl = new Flags(Flags.Flag.RECENT);
2015: } else if (head.getContent("MESSAGE FLAG").equals(
2016: "ANSWERED")) {
2017: fl = new Flags(Flags.Flag.ANSWERED);
2018: } else if (head.getContent("MESSAGE FLAG").equals("DRAFT")) {
2019: fl = new Flags(Flags.Flag.DRAFT);
2020: }
2021:
2022: boolean value = true;
2023: if (head.getContent("MARK").equals("UNMARK")) {
2024: value = false;
2025: }
2026:
2027: //System.err.println("Done!");
2028: //System.err.println("Setting flags...");
2029:
2030: if (user.wantsSetFlags()) {
2031: if (folder.isOpen()
2032: && folder.getMode() == Folder.READ_ONLY) {
2033: folder.close(false);
2034: folder.open(Folder.READ_WRITE);
2035: } else if (!folder.isOpen()) {
2036: folder.open(Folder.READ_WRITE);
2037: }
2038: folder.setFlags(msgs, fl, value);
2039: if (user.getBoolVar("autoexpunge")) {
2040: folder.close(true);
2041: if (need_expunge_folders != null) {
2042: need_expunge_folders.removeElement(folderhash);
2043: }
2044: } else {
2045: folder.close(false);
2046: }
2047: }
2048:
2049: refreshFolderInformation(folderhash);
2050:
2051: }
2052: }
2053:
2054: /**
2055: * Copy or move the selected messages from folder fromfolder to folder tofolder.
2056: */
2057: public void copyMoveMessage(String fromfolder, String tofolder,
2058: HTTPRequestHeader head, boolean move)
2059: throws MessagingException {
2060: Folder from = getFolder(fromfolder);
2061: Folder to = getFolder(tofolder);
2062: if (user.wantsSetFlags()) {
2063: if (from.isOpen() && from.getMode() == Folder.READ_ONLY) {
2064: from.close(false);
2065: from.open(Folder.READ_WRITE);
2066: } else if (!from.isOpen()) {
2067: from.open(Folder.READ_WRITE);
2068: }
2069: if (to.isOpen() && to.getMode() == Folder.READ_ONLY) {
2070: to.close(false);
2071: to.open(Folder.READ_WRITE);
2072: } else if (!to.isOpen()) {
2073: to.open(Folder.READ_WRITE);
2074: }
2075: } else {
2076: if (!from.isOpen()) {
2077: from.open(Folder.READ_ONLY);
2078: }
2079: if (to.isOpen() && to.getMode() == Folder.READ_ONLY) {
2080: to.close(false);
2081: to.open(Folder.READ_WRITE);
2082: } else if (!to.isOpen()) {
2083: to.open(Folder.READ_WRITE);
2084: }
2085: }
2086: int m[] = getSelectedMessages(head, from.getMessageCount());
2087: Message msgs[] = from.getMessages(m);
2088: from.copyMessages(msgs, to);
2089: if (move && user.wantsSetFlags()) {
2090: from.setFlags(m, new Flags(Flags.Flag.DELETED), true);
2091: if (user.getBoolVar("autoexpunge")) {
2092: from.close(true);
2093: to.close(true);
2094: } else {
2095: if (need_expunge_folders == null) {
2096: need_expunge_folders = new Vector();
2097: }
2098: need_expunge_folders.addElement(fromfolder);
2099: from.close(false);
2100: to.close(false);
2101: }
2102: } else {
2103: from.close(false);
2104: if (user.getBoolVar("autoexpunge")) {
2105: to.close(true);
2106: } else {
2107: to.close(false);
2108: }
2109: }
2110: refreshFolderInformation(fromfolder);
2111: refreshFolderInformation(tofolder);
2112: }
2113:
2114: /**
2115: * Change a user's configuration.
2116: * Header fields given in the requestheader are parsed and turned into user options (probably should not be in WebMailSession
2117: * but in a plugin or something; this is very hacky).
2118: */
2119: public void changeSetup(HTTPRequestHeader head)
2120: throws WebMailException {
2121:
2122: Enumeration contentkeys = head.getContentKeys();
2123: user.resetBoolVars();
2124: while (contentkeys.hasMoreElements()) {
2125: String key = ((String) contentkeys.nextElement())
2126: .toLowerCase();
2127: if (key.startsWith("intvar")) {
2128: try {
2129: long value = Long.parseLong(head.getContent(key));
2130: user.setIntVar(key.substring(7), value);
2131: } catch (NumberFormatException ex) {
2132: System.err
2133: .println("Warning: Remote provided illegal intvar in request header: \n("
2134: + key
2135: + ","
2136: + head.getContent(key)
2137: + ")");
2138: }
2139: } else if (key.startsWith("boolvar")) {
2140: boolean value = head.getContent(key).toUpperCase()
2141: .equals("ON");
2142: user.setBoolVar(key.substring(8), value);
2143: }
2144: }
2145:
2146: // Modified by exce, start
2147: /**
2148: * As described in line #1088, we have to transcode these strings.
2149: * We only allow SIGNATURE and FULLNAME to contain locale-specific
2150: * characters.
2151: */
2152: // user.setSignature(head.getContent("SIGNATURE"));
2153: // user.setFullName(head.getContent("FULLNAME"));
2154: try {
2155: user.setSignature(new String(head.getContent("SIGNATURE")
2156: .getBytes("ISO8859_1"), "UTF-8"));
2157: user.setFullName(new String(head.getContent("FULLNAME")
2158: .getBytes("ISO8859_1"), "UTF-8"));
2159: } catch (UnsupportedEncodingException e) {
2160: e.printStackTrace();
2161: user.setSignature(head.getContent("SIGNATURE"));
2162: user.setFullName(head.getContent("FULLNAME"));
2163: }
2164: // Modified by exce, end
2165: user.setEmail(head.getContent("EMAIL"));
2166: if (!head.getContent("PASSWORD").equals("")) {
2167: net.wastl.webmail.server.Authenticator auth = parent
2168: .getStorage().getAuthenticator();
2169: if (auth.canChangePassword()) {
2170: auth.changePassword(user, head.getContent("PASSWORD"),
2171: head.getContent("VERIFY"));
2172: } else {
2173: throw new InvalidDataException(
2174: getStringResource("EX NO CHANGE PASSWORD"));
2175: }
2176: }
2177: user.setPreferredLocale(head.getContent("LANGUAGE"));
2178: user.setTheme(head.getContent("THEME"));
2179: if (head.isContentSet("SENTFOLDER")) {
2180: System.err.println("SENTFOLDER="
2181: + head.getContent("SENTFOLDER"));
2182: user.setSentFolder(head.getContent("SENTFOLDER"));
2183: }
2184:
2185: // Not sure if this is really necessary:
2186: //refreshFolderInformation(true);
2187: setEnv();
2188: model.update();
2189: }
2190:
2191: /**
2192: * Add the mailbox with the given parameters to this user's configuration. Subscribe all folders on startup (the
2193: * user can later unsubscribe them) and update the model.
2194: *
2195: * @param name Name for the mailbox (used for identification within the session)
2196: * @param protocol The protocol used for this mailbox (most likely IMAP or POP3)
2197: * @param host The hostname of the host this mailbox lives on
2198: * @param login Login name the user provided for the host
2199: * @param password Password the user provided to the given login
2200: */
2201: public void addMailbox(String name, String protocol, String host, String login, String password)
2202: throws MessagingException {
2203: disconnectAll();
2204: String host_url=protocol+"://"+host;
2205: user.addMailHost(name,
2206: host_url,
2207: login,
2208: password);
2209: Enumeration enum=user.mailHosts();
2210: while(enum.hasMoreElements()) {
2211: String id=(String)enum.nextElement();
2212: if(user.getMailHost(id).getName().equals(name)) {
2213: setSubscribedAll(id,true);
2214: break;
2215: }
2216: }
2217: model.update();
2218: }
2219:
2220: /**
2221: * Remove the mailbox with the given name.
2222: * Will first disconnect all mailboxes, remove the given mailbox and then update the model.
2223: *
2224: * @param name Name of the mailbox that is to be removed.
2225: */
2226: public void removeMailbox(String name) {
2227: disconnectAll();
2228: user.removeMailHost(name);
2229: model.update();
2230: // Should be called from FolderSetup Plugin
2231: //refreshFolderInformation(true);
2232: }
2233:
2234: public void setAddToFolder(String id) {
2235: model.setStateVar("add to folder", id);
2236: }
2237:
2238: public void addFolder(String toid, String name,
2239: boolean holds_messages, boolean holds_folders)
2240: throws MessagingException {
2241:
2242: Folder parent = getFolder(toid);
2243: Folder folder = parent.getFolder(name);
2244: if (!folder.exists()) {
2245: int type = 0;
2246: if (holds_messages) {
2247: type += Folder.HOLDS_MESSAGES;
2248: }
2249: if (holds_folders) {
2250: type += Folder.HOLDS_FOLDERS;
2251: }
2252: folder.create(type);
2253: }
2254: // Should be called from FolderSetup Plugin
2255: //refreshFolderInformation();
2256: }
2257:
2258: public void removeFolder(String id, boolean recurse)
2259: throws MessagingException {
2260: Folder folder = getFolder(id);
2261: folder.close(false);
2262: folder.delete(recurse);
2263:
2264: // Should be called from FolderSetup Plugin
2265: //refreshFolderInformation();
2266: }
2267:
2268: public String getEnv(String key) {
2269: return "";
2270: }
2271:
2272: public void setEnv(String key, String value) {
2273: }
2274:
2275: public void setException(Exception ex) {
2276: model.setException(ex);
2277: }
2278:
2279: public void setEnv() {
2280: // This will soon replace "ENV":
2281: model.setStateVar("base uri", parent.getBasePath());
2282: model.setStateVar("img base uri", parent.getImageBasePath()
2283: + "/" + user.getPreferredLocale().getLanguage() + "/"
2284: + user.getTheme());
2285:
2286: model.setStateVar("webmail version", parent.getVersion());
2287: model.setStateVar("operating system", System
2288: .getProperty("os.name")
2289: + " "
2290: + System.getProperty("os.version")
2291: + "/"
2292: + System.getProperty("os.arch"));
2293: model.setStateVar("java virtual machine", System
2294: .getProperty("java.vendor")
2295: + " "
2296: + System.getProperty("java.vm.name")
2297: + " "
2298: + System.getProperty("java.version"));
2299:
2300: model.setStateVar("last login", user.getLastLogin());
2301: model.setStateVar("first login", user.getFirstLogin());
2302: model.setStateVar("session id", session_code);
2303: model.setStateVar("date",
2304: formatDate(System.currentTimeMillis()));
2305: model.setStateVar("max attach size", parent.getStorage()
2306: .getConfig("MAX ATTACH SIZE"));
2307: model.setStateVar("current attach size", "" + attachments_size);
2308:
2309: // Add all languages to the state
2310: model.removeAllStateVars("language");
2311: String lang = parent.getConfig("languages");
2312: StringTokenizer tok = new StringTokenizer(lang, " ");
2313: while (tok.hasMoreTokens()) {
2314: String t = tok.nextToken();
2315: model.addStateVar("language", t);
2316: model.removeAllStateVars("themes_" + t);
2317: StringTokenizer tok2 = new StringTokenizer(parent
2318: .getConfig("THEMES_" + t.toUpperCase()), " ");
2319: while (tok2.hasMoreElements()) {
2320: model.addStateVar("themes_" + t, (String) tok2
2321: .nextToken());
2322: }
2323: }
2324:
2325: model.removeAllStateVars("protocol");
2326: Provider[] stores = parent.getStoreProviders();
2327: for (int i = 0; i < stores.length; i++) {
2328: model.addStateVar("protocol", stores[i].getProtocol());
2329: }
2330:
2331: model
2332: .setStateVar("themeset", "themes_"
2333: + user.getPreferredLocale().getLanguage()
2334: .toLowerCase());
2335: }
2336:
2337: public UserData getUser() {
2338: return user;
2339: }
2340:
2341: public String getUserName() {
2342: return user.getLogin();
2343: }
2344:
2345: public InetAddress getRemoteAddress() {
2346: return remote;
2347: }
2348:
2349: public Hashtable getActiveConnections() {
2350: return connections;
2351: }
2352:
2353: public void setSent(boolean b) {
2354: sent = b;
2355: }
2356:
2357: public boolean isSent() {
2358: return sent;
2359: }
2360:
2361: private String formatDate(long date) {
2362: TimeZone tz = TimeZone.getDefault();
2363: DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG,
2364: DateFormat.DEFAULT, getLocale());
2365: df.setTimeZone(tz);
2366: String now = df.format(new Date(date));
2367: return now;
2368: }
2369:
2370: public void handleTransportException(SendFailedException e) {
2371: model.setStateVar("send status", e.getNextException()
2372: .getMessage());
2373: model.setStateVar("valid sent addresses", Helper.joinAddress(e
2374: .getValidSentAddresses()));
2375: model.setStateVar("valid unsent addresses", Helper
2376: .joinAddress(e.getValidUnsentAddresses()));
2377: model.setStateVar("invalid addresses", Helper.joinAddress(e
2378: .getInvalidAddresses()));
2379: sent = true;
2380: }
2381:
2382: } // WebMailSession
|