/*
 * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

import java.io.*;
import java.util.*;
import java.text.*;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.mail.*;
import javax.mail.Part;
import javax.mail.internet.*;
import javax.activation.*;


/**
 * This is a servlet that demonstrates the use of Jakarta Mail APIs
 * in a 3-tier application. It allows the user to login to an 
 * IMAP store, list all the messages in the INBOX folder, view
 * selected messages, compose and send a message, and logout.
 * <p>
 * Please note: This is NOT an example of how to write servlets! 
 * This is simply to show that Jakarta Mail can be used in a servlet.
 * <p>
 * For more information on this servlet, see the 
 * JakartaMailServlet.README.txt file. 
 * <p>
 * For more information on servlets, see 
 * <a href="http://java.sun.com/products/java-server/servlets/index.html">
 * http://java.sun.com/products/java-server/servlets/index.html</a>
 *
 * @author Max Spivak
 */
public class JakartaMailServlet extends HttpServlet implements SingleThreadModel {
    String protocol = "imap";
    String mbox = "INBOX";


    /**
     * This method handles the "POST" submission from two forms: the
     * login form and the message compose form. The login form has the
     * following parameters: <code>hostname</code>, <code>username</code>,
     * and <code>password</code>. The <code>send</code> parameter denotes
     * that the method is processing the compose form submission.
     */
    public  void doPost(HttpServletRequest req, HttpServletResponse res)
	throws ServletException, IOException {

	// get the session
	HttpSession ssn = req.getSession(true);

	String send = req.getParameter("send");
	String host = req.getParameter("hostname");
	String user = req.getParameter("username");
	String passwd = req.getParameter("password");
	URLName url = new URLName(protocol, host, -1, mbox, user, passwd);

	ServletOutputStream out = res.getOutputStream();
	res.setContentType("text/html");
	out.println("<html><body bgcolor=\"#CCCCFF\">");

	if (send != null) {
	    // process message sending
	    send(req, res, out, ssn);

	} else {
	    // initial login

	    // create 
	    MailUserData mud = new MailUserData(url);
	    ssn.putValue("jakartamailservlet", mud);
	    
	    try {
		Properties props = System.getProperties();
		props.put("mail.smtp.host", host);
		Session session = Session.getDefaultInstance(props, null);
		session.setDebug(false);
		Store store = session.getStore(url);
		store.connect();
		Folder folder = store.getDefaultFolder();
		if (folder == null) 
		    throw new MessagingException("No default folder");
		
		folder = folder.getFolder(mbox);
		if (folder == null)
		    throw new MessagingException("Invalid folder");
		
		folder.open(Folder.READ_WRITE);
		int totalMessages = folder.getMessageCount();
		Message[] msgs = folder.getMessages();
		FetchProfile fp = new FetchProfile();
		fp.add(FetchProfile.Item.ENVELOPE);
		folder.fetch(msgs, fp);
		
		// track who logged in
		System.out.println("Login from: " + store.getURLName());
		
		// save stuff into MUD
		mud.setSession(session);
		mud.setStore(store);
		mud.setFolder(folder);
		
		// splash
		out.print("<center>");
		out.print("<font face=\"Arial,Helvetica\" font size=+3>");
		out.println("<b>Welcome to Jakarta Mail!</b></font></center><p>");

		// folder table
		out.println("<table width=\"50%\" border=0 align=center>");
		// folder name column header
		out.print("<tr><td width=\"75%\" bgcolor=\"#ffffcc\">");
		out.print("<font face=\"Arial,Helvetica\" font size=-1>");
		out.println("<b>FolderName</b></font></td><br>");
		// msg count column header
		out.print("<td width=\"25%\" bgcolor=\"#ffffcc\">");
		out.print("<font face=\"Arial,Helvetica\" font size=-1>");
		out.println("<b>Messages</b></font></td><br>");
		out.println("</tr>");
		// folder name
		out.print("<tr><td width=\"75%\" bgcolor=\"#ffffff\">");
		out.print("<a href=\"" + HttpUtils.getRequestURL(req) + "\">" +
			  "Inbox" + "</a></td><br>");
		// msg count
		out.println("<td width=\"25%\" bgcolor=\"#ffffff\">" + 
			    totalMessages + "</td>");
		out.println("</tr>");
		out.println("</table");
	    } catch (Exception ex) {
		out.println(ex.toString());            
	    } finally {
		out.println("</body></html>");
		out.close();
	    }
	}
    }


    /**
     * This method handles the GET requests for the client.
     */
    public void doGet (HttpServletRequest req, HttpServletResponse res)
	throws ServletException, IOException {

	HttpSession ses = req.getSession(false); // before we write to out
	ServletOutputStream out = res.getOutputStream();
	MailUserData mud = getMUD(ses);

	if (mud == null) {
	    res.setContentType("text/html");
	    out.println("<html><body>Please Login (no session)</body></html>");
	    out.close();
	    return;
	}

	if (!mud.getStore().isConnected()) {
	    res.setContentType("text/html");
	    out.println("<html><body>Not Connected To Store</body></html>");
	    out.close();
	    return;
	}


	// mux that takes a GET request, based on parameters figures
	// out what it should do, and routes it to the
	// appropriate method

	// get url parameters
	String msgStr = req.getParameter("message");
	String logout = req.getParameter("logout");
	String compose = req.getParameter("compose");
	String part = req.getParameter("part");
	int msgNum = -1;
	int partNum = -1;

	// process url params
	if (msgStr != null) {
	    // operate on message "msgStr"
	    msgNum = Integer.parseInt(msgStr);

	    if (part == null) {
		// display message "msgStr"
		res.setContentType("text/html");
		displayMessage(mud, req, out, msgNum);

	    } else {
		// display part "part" in message "msgStr"
		partNum = Integer.parseInt(part);
		displayPart(mud, msgNum, partNum, out, res);
	    }

	} else if (compose != null) {
	    // display compose form
	    compose(mud, res, out);

	} else if (logout != null) {
	    // process logout
	    try {
		mud.getFolder().close(false);
		mud.getStore().close();
		ses.invalidate();
		out.println("<html><body>Logged out OK</body></html>");
	    } catch (MessagingException mex) {
		out.println(mex.toString());
	    }

	} else {
	    // display headers
	    displayHeaders(mud, req, out);
	}
    }

    /* main method to display messages */
    private void displayMessage(MailUserData mud, HttpServletRequest req, 
				ServletOutputStream out, int msgNum) 
	throws IOException {
	    
	out.println("<html>");
	out.println("<HEAD><TITLE>Jakarta Mail Servlet</TITLE></HEAD>");
	out.println("<BODY bgcolor=\"#ccccff\">");
	out.print("<center><font face=\"Arial,Helvetica\" ");
	out.println("font size=\"+3\"><b>");
	out.println("Message " + (msgNum+1) + " in folder " + 
		    mud.getStore().getURLName() + 
		    "/INBOX</b></font></center><p>");

	try {
	    Message msg = mud.getFolder().getMessage(msgNum);

	    // first, display this message's headers
	    displayMessageHeaders(mud, msg, out);

	    // and now, handle the content
	    Object o = msg.getContent();
	    
	    //if (o instanceof String) {
	    if (msg.isMimeType("text/plain")) {
		out.println("<pre>");
		out.println((String)o);
		out.println("</pre>");
	    //} else if (o instanceof Multipart){
	    } else if (msg.isMimeType("multipart/*")) {
		Multipart mp = (Multipart)o;
		int cnt = mp.getCount();
		for (int i = 0; i < cnt; i++) {
		    displayPart(mud, msgNum, mp.getBodyPart(i), i, req, out);
		}
	    } else {
		out.println(msg.getContentType());
	    }

	} catch (MessagingException mex) {
	    out.println(mex.toString());
	}

	out.println("</BODY></html>");
	out.close();
    }

    /** 
     * This method displays a message part. <code>text/plain</code>
     * content parts are displayed inline. For all other parts,
     * a URL is generated and displayed; clicking on the URL
     * brings up the part in a separate page.
     */
    private void displayPart(MailUserData mud, int msgNum, Part part, 
			     int partNum, HttpServletRequest req, 
			     ServletOutputStream out) 
	throws IOException {

	if (partNum != 0)
	    out.println("<p><hr>");

	try {

	    String sct = part.getContentType();
	    if (sct == null) {
		out.println("invalid part");
		return;
	    }
	    ContentType ct = new ContentType(sct);
	    
	    if (partNum != 0)
		out.println("<b>Attachment Type:</b> " +   
			    ct.getBaseType() + "<br>");

	    if (ct.match("text/plain")) {  
		// display text/plain inline
		out.println("<pre>");
		out.println((String)part.getContent());
		out.println("</pre>");

	    } else {
		// generate a url for this part
		String s;
		if ((s = part.getFileName()) != null)
		    out.println("<b>Filename:</b> " + s + "<br>");
		s = null;
		if ((s = part.getDescription()) != null)
		    out.println("<b>Description:</b> " + s + "<br>");
		
		out.println("<a href=\"" +
			    HttpUtils.getRequestURL(req) + 
			    "?message=" +
			    msgNum + "&part=" +
			    partNum + "\">Display Attachment</a>");
	    }
	} catch (MessagingException mex) {
	    out.println(mex.toString());
	}
    }

    /**
     * This method gets the stream from for a given msg part and 
     * pushes it out to the browser with the correct content type.
     * Used to display attachments and relies on the browser's
     * content handling capabilities.
     */
    private void displayPart(MailUserData mud, int msgNum,
			     int partNum, ServletOutputStream out, 
			     HttpServletResponse res) 
	throws IOException {

	Part part = null;
	
	try {
	    Message msg = mud.getFolder().getMessage(msgNum);

	    Multipart mp = (Multipart)msg.getContent();
	    part = mp.getBodyPart(partNum);
	    
	    String sct = part.getContentType();
	    if (sct == null) {
		out.println("invalid part");
		return;
	    }
	    ContentType ct = new ContentType(sct);

	    res.setContentType(ct.getBaseType());
	    InputStream is = part.getInputStream();
	    int i;
	    while ((i = is.read()) != -1)
		out.write(i);
	    out.flush();
	    out.close();
	} catch (MessagingException mex) {
	    out.println(mex.toString());
	}
    }

    /**
     * This is a utility message that pretty-prints the message 
     * headers for message that is being displayed.
     */
    private void displayMessageHeaders(MailUserData mud, Message msg, 
				       ServletOutputStream out) 
	throws IOException {

	try {
	    out.println("<b>Date:</b> " + msg.getSentDate() + "<br>");

	    Address[] fr = msg.getFrom();
	    if (fr != null) {
		boolean tf = true;
		out.print("<b>From:</b> ");
		for (int i = 0; i < fr.length; i++) {
		    out.print(((tf) ? " " : ", ") + getDisplayAddress(fr[i]));
		    tf = false;
		}
		out.println("<br>");
	    }

	    Address[] to = msg.getRecipients(Message.RecipientType.TO);
	    if (to != null) {
		boolean tf = true;
		out.print("<b>To:</b> ");
		for (int i = 0; i < to.length; i++) {
		    out.print(((tf) ? " " : ", ") + getDisplayAddress(to[i]));
		    tf = false;
		}
		out.println("<br>");
	    }

	    Address[] cc = msg.getRecipients(Message.RecipientType.CC);
	    if (cc != null) {
		boolean cf = true;
		out.print("<b>CC:</b> ");
		for (int i = 0; i < cc.length; i++) {
		    out.print(((cf) ? " " : ", ") + getDisplayAddress(cc[i]));
		    cf = false;
		}
		out.println("<br>");
	    }
	    
	    out.print("<b>Subject:</b> " + 
		      ((msg.getSubject() !=null) ? msg.getSubject() : "") + 
		      "<br>");

	} catch (MessagingException mex) {
	    out.println(msg.toString());
	}
    }

    /**
     * This method displays the URL's for the available commands and the
     * INBOX headerlist 
     */
    private void displayHeaders(MailUserData mud,
				HttpServletRequest req, 
				ServletOutputStream out)
	throws IOException {

	SimpleDateFormat df = new SimpleDateFormat("EE M/d/yy");

	out.println("<html>");
	out.println("<HEAD><TITLE>Jakarta Mail Servlet</TITLE></HEAD>");
	out.println("<BODY bgcolor=\"#ccccff\"><hr>");
	out.print("<center><font face=\"Arial,Helvetica\" font size=\"+3\">");
	out.println("<b>Folder " + mud.getStore().getURLName() + 
		    "/INBOX</b></font></center><p>");

	// URL's for the commands that are available
	out.println("<font face=\"Arial,Helvetica\" font size=\"+3\"><b>");
	out.println("<a href=\"" +
		    HttpUtils.getRequestURL(req) +
		    "?logout=true\">Logout</a>");
	out.println("<a href=\"" +
		    HttpUtils.getRequestURL(req) +
		    "?compose=true\" target=\"compose\">Compose</a>");
	out.println("</b></font>");
	out.println("<hr>");

	// List headers in a table
	out.print("<table cellpadding=1 cellspacing=1 "); // table
	out.println("width=\"100%\" border=1>");          // settings

	// sender column header
	out.println("<tr><td width=\"25%\" bgcolor=\"ffffcc\">");
	out.println("<font face=\"Arial,Helvetica\" font size=\"+1\">");
	out.println("<b>Sender</b></font></td>");
	// date column header
	out.println("<td width=\"15%\" bgcolor=\"ffffcc\">");
	out.println("<font face=\"Arial,Helvetica\" font size=\"+1\">");
	out.println("<b>Date</b></font></td>");
	// subject column header
	out.println("<td bgcolor=\"ffffcc\">");
	out.println("<font face=\"Arial,Helvetica\" font size=\"+1\">");
	out.println("<b>Subject</b></font></td></tr>");

	try {
	    Folder f = mud.getFolder();
	    int msgCount = f.getMessageCount();
	    Message m = null;
	    // for each message, show its headers
	    for (int i = 1; i <= msgCount; i++) {
		m = f.getMessage(i);
		
		// if message has the DELETED flag set, don't display it
		if (m.isSet(Flags.Flag.DELETED))
		    continue;

		// from 
		out.println("<tr valigh=middle>");
		out.print("<td width=\"25%\" bgcolor=\"ffffff\">");
		out.println("<font face=\"Arial,Helvetica\">" + 
			    ((m.getFrom() != null) ? 
				       m.getFrom()[0].toString() : 
				       "" ) +
			    "</font></td>");

		// date
		out.print("<td nowrap width=\"15%\" bgcolor=\"ffffff\">");
		out.println("<font face=\"Arial,Helvetica\">" + 
			    df.format((m.getSentDate()!=null) ? 
				      m.getSentDate() : m.getReceivedDate()) +
			    "</font></td>");

		// subject & link
		out.print("<td bgcolor=\"ffffff\">");
		out.println("<font face=\"Arial,Helvetica\">" + 
			    "<a href=\"" +
			    HttpUtils.getRequestURL(req) + 
			    "?message=" +
			    i + "\">" +
			    ((m.getSubject() != null) ? 
				   m.getSubject() :
				   "<i>No Subject</i>") +
			    "</a>" +
			    "</font></td>");
		out.println("</tr>");
	    }
	} catch (MessagingException mex) {
	    out.println("<tr><td>" + mex.toString() + "</td></tr>");
	    mex.printStackTrace();
	}

	out.println("</table>");
	out.println("</BODY></html>");
	out.flush();
	out.close();
    }

    /** 
     * This method handles the request when the user hits the
     * <i>Compose</i> link. It send the compose form to the browser.
     */
    private void compose(MailUserData mud, HttpServletResponse res,
			 ServletOutputStream out) 
	throws IOException {
	
	res.setContentType("text/html");
	out.println(composeForm);
	out.close();
    }

    /**
     * This method processes the send request from the compose form
     */
    private void send(HttpServletRequest req, HttpServletResponse res,
		      ServletOutputStream out, HttpSession ssn)
	throws IOException {
	    
	String to = req.getParameter("to");
	String cc = req.getParameter("cc");
	String subj = req.getParameter("subject");
	String text = req.getParameter("text");

	try {
	    MailUserData mud = getMUD(ssn);
	    if (mud == null)
		throw new Exception("trying to send, but not logged in");

	    Message msg = new MimeMessage(mud.getSession());
	    InternetAddress[] toAddrs = null, ccAddrs = null;

	    if (to != null) {
		toAddrs = InternetAddress.parse(to, false);
		msg.setRecipients(Message.RecipientType.TO, toAddrs);
	    } else
		throw new MessagingException("No \"To\" address specified");

	    if (cc != null) {
		ccAddrs = InternetAddress.parse(cc, false);
		msg.setRecipients(Message.RecipientType.CC, ccAddrs);
	    }

	    if (subj != null)
		msg.setSubject(subj);

	    URLName u = mud.getURLName();
	    msg.setFrom(new InternetAddress(u.getUsername() + "@" +
					    u.getHost()));

	    if (text != null)
		msg.setText(text);

	    Transport.send(msg);
	    
	    out.println("<h1>Message sent successfully</h1></body></html>");
	    out.close();
	    
	} catch (Exception mex) {
	    out.println("<h1>Error sending message.</h1>");
	    out.println(mex.toString());
	    out.println("<br></body></html>");
	}
    }


    // utility method; returns a string suitable for msg header display
    private String getDisplayAddress(Address a) {
	String pers = null;
	String addr = null;
	if (a instanceof InternetAddress &&
	    ((pers = ((InternetAddress)a).getPersonal()) != null)) {
	    
	    addr = pers + "  "+"&lt;"+((InternetAddress)a).getAddress()+"&gt;";
	} else 
	    addr = a.toString();
	
	return addr;
    }

    // utility method; retrieve the MailUserData 
    // from the HttpSession and return it
    private MailUserData getMUD(HttpSession ses) throws IOException {
	MailUserData mud = null;

	if (ses == null) {
	    return null;
	} else {
	    if ((mud = (MailUserData)ses.getValue("jakartamailservlet")) == null){
		return null;
	    }
	}
	return mud;
    }


    public String getServletInfo() {
	return "A mail reader servlet";
    }

    /**
     * This is the HTML code for the compose form. Another option would
     * have been to use a separate html page.
     */
    private static String composeForm = "<HTML><HEAD><TITLE>Jakarta Mail Compose</TITLE></HEAD><BODY BGCOLOR=\"#CCCCFF\"><FORM ACTION=\"/servlet/JakartaMailServlet\" METHOD=\"POST\"><input type=\"hidden\" name=\"send\" value=\"send\"><P ALIGN=\"CENTER\"><B><FONT SIZE=\"4\" FACE=\"Verdana, Arial, Helvetica\">Jakarta Mail Compose Message</FONT></B><P><TABLE BORDER=\"0\" WIDTH=\"100%\"><TR><TD WIDTH=\"16%\" HEIGHT=\"22\">	<P ALIGN=\"RIGHT\"><B><FONT FACE=\"Verdana, Arial, Helvetica\">To:</FONT></B></TD><TD WIDTH=\"84%\" HEIGHT=\"22\"><INPUT TYPE=\"TEXT\" NAME=\"to\" SIZE=\"30\"> <FONT SIZE=\"1\" FACE=\"Verdana, Arial, Helvetica\"> (separate addresses with commas)</FONT></TD></TR><TR><TD WIDTH=\"16%\"><P ALIGN=\"RIGHT\"><B><FONT FACE=\"Verdana, Arial, Helvetica\">CC:</FONT></B></TD><TD WIDTH=\"84%\"><INPUT TYPE=\"TEXT\" NAME=\"cc\" SIZE=\"30\"> <FONT SIZE=\"1\" FACE=\"Verdana, Arial, Helvetica\"> (separate addresses with commas)</FONT></TD></TR><TR><TD WIDTH=\"16%\"><P ALIGN=\"RIGHT\"><B><FONT FACE=\"Verdana, Arial, Helvetica\">Subject:</FONT></B></TD><TD WIDTH=\"84%\"><INPUT TYPE=\"TEXT\" NAME=\"subject\" SIZE=\"55\"></TD></TR><TR><TD WIDTH=\"16%\">&nbsp;</TD><TD WIDTH=\"84%\"><TEXTAREA NAME=\"text\" ROWS=\"15\" COLS=\"53\"></TEXTAREA></TD></TR><TR><TD WIDTH=\"16%\" HEIGHT=\"32\">&nbsp;</TD><TD WIDTH=\"84%\" HEIGHT=\"32\"><INPUT TYPE=\"SUBMIT\" NAME=\"Send\" VALUE=\"Send\"><INPUT TYPE=\"RESET\" NAME=\"Reset\" VALUE=\"Reset\"></TD></TR></TABLE></FORM></BODY></HTML>";

}


/**
 * This class is used to store session data for each user's session. It
 * is stored in the HttpSession.
 */
class MailUserData {
    URLName url;
    Session session;
    Store store;
    Folder folder;

    public MailUserData(URLName urlname) {
	url = urlname;
    }

    public URLName getURLName() {
	return url;
    }

    public Session getSession() {
	return session;
    }

    public void setSession(Session s) {
	session = s;
    }

    public Store getStore() {
	return store;
    }

    public void setStore(Store s) {
	store = s;
    }

    public Folder getFolder() {
	return folder;
    }

    public void setFolder(Folder f) {
	folder = f;
    }
}
