/*
* @(#)$Id: WebMailServlet.java 136 2008-10-31 21:14:28Z unsaved $
*
* Copyright 2008 by the JWebMail Development Team and Sebastian Schaffert.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.wastl.webmail.server;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import net.wastl.webmail.exceptions.DocumentNotFoundException;
import net.wastl.webmail.exceptions.InvalidPasswordException;
import net.wastl.webmail.exceptions.UserDataException;
import net.wastl.webmail.exceptions.WebMailException;
import net.wastl.webmail.misc.ByteStore;
import net.wastl.webmail.misc.Helper;
import net.wastl.webmail.server.http.HTTPRequestHeader;
import net.wastl.webmail.ui.html.HTMLDocument;
import net.wastl.webmail.ui.html.HTMLImage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.ParamPart;
import com.oreilly.servlet.multipart.Part;
/**
* This is WebMails main server. From here most parts will be administered. This
* is the servlet implementation of WebMail (introduced in 0.6.1)
* Created: Tue Feb 2 12:07:25 1999
*
* @author Sebastian Schaffert
*/
public class WebMailServlet extends WebMailServer implements Servlet {
/*
* TODO:
* Redo the application startup and shutdown stuff in a dedicated lifecycle
* listener. Should not invoke super.shutdown() from the destroy method,
* as happens below.
*/
private static Log log = LogFactory.getLog(WebMailServer.class);
ServletConfig srvlt_config;
/** Size of the chunks that are sent. Must not be greater than 65536 */
private static final int chunk_size = 8192;
protected String basepath;
protected String imgbase;
public WebMailServlet() {
}
public void init(ServletConfig config) throws ServletException {
final ServletContext sc = config.getServletContext();
log.debug("Init");
final String depName = (String) sc.getAttribute("deployment.name");
final Properties rtProps =
(Properties) sc.getAttribute("meta.properties");
log.debug("RT configs retrieved for application '" + depName + "'");
srvlt_config = config;
this.config = new Properties();
final Enumeration enumVar = config.getInitParameterNames();
while (enumVar.hasMoreElements()) {
final String s = (String) enumVar.nextElement();
this.config.put(s, config.getInitParameter(s));
log.debug(s + ": " + config.getInitParameter(s));
}
/*
* Issue a warning if webmail.basepath and/or webmail.imagebase are not
* set.
*/
if (config.getInitParameter("webmail.basepath") == null) {
log.warn("webmail.basepath initArg should be set to the WebMail "
+ "Servlet's base path");
basepath = "";
} else {
basepath = config.getInitParameter("webmail.basepath");
}
if (config.getInitParameter("webmail.imagebase") == null) {
log.error("webmail.basepath initArg should be set to the WebMail "
+ "Servlet's base path");
imgbase = "";
} else {
imgbase = config.getInitParameter("webmail.imagebase");
}
/*
* Try to get the pathnames from the URL's if no path was given in the
* initargs.
*/
if (config.getInitParameter("webmail.data.path") == null) {
this.config.put("webmail.data.path", sc.getRealPath("/data"));
}
if (config.getInitParameter("webmail.lib.path") == null) {
this.config.put("webmail.lib.path", sc.getRealPath("/lib"));
}
if (config.getInitParameter("webmail.template.path") == null) {
this.config.put("webmail.template.path",
sc.getRealPath("/lib/templates"));
}
if (config.getInitParameter("webmail.xml.path") == null) {
this.config.put("webmail.xml.path", sc.getRealPath("/lib/xml"));
}
if (config.getInitParameter("webmail.log.facility") == null) {
this.config.put("webmail.log.facility",
"net.wastl.webmail.logger.ServletLogger");
}
// Override settings with webmail.* meta.properties
final Enumeration rte = rtProps.propertyNames();
int overrides = 0;
String k;
while (rte.hasMoreElements()) {
k = (String) rte.nextElement();
if (!k.startsWith("webmail.")) {
continue;
}
overrides++;
this.config.put(k, rtProps.getProperty(k));
}
log.debug(Integer.toString(overrides)
+ " settings passed to WebMailServer, out of "
+ rtProps.size() + " RT properties");
/*
* Call the WebMailServer's initialization method and forward all
* Exceptions to the ServletServer
*/
try {
doInit();
} catch (final Exception e) {
log.error("Could not intialize", e);
throw new ServletException("Could not intialize: "
+ e.getMessage(), e);
}
Helper.logThreads("Bottom of WebMailServlet.init()");
}
public ServletConfig getServletConfig() {
return srvlt_config;
}
public ServletContext getServletContext() {
return srvlt_config.getServletContext();
}
public String getServletInfo() {
return getVersion() + "\n(c)2008 by the JWebMail Development Team and "
+ "Sebastian Schaffert\nThis software is distributed under the "
+ "Apache 2.0 License";
}
public void destroy() {
shutdown();
}
/**
* Handle a request to the WebMail servlet. This is the central method of
* the WebMailServlet. It first gathers all of the necessary information
* from the client, then either creates or gets a Session and executes the
* URL handler for the given path.
*/
public void service(ServletRequest req1, ServletResponse res1)
throws ServletException {
final HttpServletRequest req = (HttpServletRequest) req1;
final HttpServletResponse res = (HttpServletResponse) res1;
final HTTPRequestHeader http_header = new HTTPRequestHeader();
if (req.getServletPath().equals("/admin")) try {
log.debug("Forwarding /admin request back to self");
req.getRequestDispatcher("WebMail/admin").forward(req1, res1);
return;
} catch (IOException ioe) {
log.fatal("Forward from '/admin' failed", ioe);
throw new ServletException(ioe.getMessage());
}
final Enumeration en = req.getHeaderNames();
while (en.hasMoreElements()) {
final String s = (String) en.nextElement();
http_header.setHeader(s, req.getHeader(s));
}
http_header.setPath(
req.getPathInfo() == null ? "/" : req.getPathInfo());
InetAddress addr;
try {
addr = InetAddress.getByName(req.getRemoteHost());
} catch (final UnknownHostException e) {
try {
addr = InetAddress.getByName(req.getRemoteAddr());
} catch (final Exception ex) {
throw new ServletException("Remote host must identify!");
}
}
HTMLDocument content = null;
final int err_code = 400;
HTTPSession sess = null;
/*
* Here we try to parse the MIME content that the Client sent in his
* POST since the JServ doesn't do that for us:-( At least we can use
* the functionality provided by the standalone server where we need to
* parse the content ourself anyway.
*/
try {
final BufferedOutputStream out =
new BufferedOutputStream(res.getOutputStream());
/*
* First we try to use the Servlet API's methods to parse the
* parameters. Unfortunately, it doesn't know how to handle MIME
* multipart POSTs, so we will have to handle that ourselves
*/
/*
* First get all the parameters and set their values into
* http_header
*/
Enumeration enum2 = req.getParameterNames();
while (enum2.hasMoreElements()) {
final String s = (String) enum2.nextElement();
http_header.setContent(s, req.getParameter(s));
// log.info("Parameter "+s);
}
/* Then we set all the headers in http_header */
enum2 = req.getHeaderNames();
while (enum2.hasMoreElements()) {
final String s = (String) enum2.nextElement();
http_header.setHeader(s, req.getHeader(s));
}
/*
* In Servlet API 2.2 we might want to fetch the attributes also,
* but this doesn't work in API 2.0, so we leave it commented out
*/
// enum2=req.getAttributeNames();
// while(enum2.hasMoreElements()) {
// String s=(String)enum2.nextElement();
// log.info("Attribute "+s);
// }
/* Now let's try to handle multipart/form-data posts */
if (req.getContentType() != null
&& req.getContentType().toUpperCase().
startsWith("MULTIPART/FORM-DATA")) {
final int size = Integer.parseInt(WebMailServer.
getStorage().getConfig("max attach size"));
final MultipartParser mparser = new MultipartParser(req, size);
Part p;
while ((p = mparser.readNextPart()) != null) {
if (p.isFile()) {
final ByteStore bs = ByteStore.getBinaryFromIS(
((FilePart) p).getInputStream(), size);
bs.setName(((FilePart) p).getFileName());
bs.setContentType(getStorage().getMimeType(
((FilePart) p).getFileName()));
http_header.setContent(p.getName(), bs);
log.info("File name " + bs.getName());
log.info("Type " + bs.getContentType());
} else if (p.isParam()) {
http_header.setContent(p.getName(),
((ParamPart) p).getStringValue());
}
// log.info("Parameter "+p.getName());
}
}
try {
final String url = http_header.getPath();
try {
/* Find out about the session id */
sess = req.getSession(false) == null
? null
: (HTTPSession) req.getSession(false).
getAttribute("webmail.session");
/*
* If the user was logging on, he doesn't have a session id,
* so generate one. If he already had one, all the better,
* we will take the old one
*/
if (sess == null && url.startsWith("/login")) {
sess = newSession(req, http_header);
} else if (sess == null && url.startsWith("/admin/login")) {
http_header.setHeader("LOGIN", "Administrator");
sess = newAdminSession(req, http_header);
}
if (sess == null && !url.equals("/")
&& !url.startsWith("/passthrough")
&& !url.startsWith("/admin")) {
content = getURLHandler().handleURL(
"/logout", sess, http_header);
} else {
/* Ensure that the session state is up-to-date */
if (sess != null) {
sess.setEnv();
}
/* Let the URLHandler determine the result of the query */
content = getURLHandler().
handleURL(url, sess, http_header);
}
} catch (final InvalidPasswordException e) {
log.error("Connection to " + addr.toString()
+ ": Authentication failed!");
if (url.startsWith("/admin/login")) {
content = getURLHandler().
handleURL("/admin", null, http_header);
} else if (url.startsWith("/login")) {
content = getURLHandler().
handleURL("/", null, http_header);
} else
// content=new
// HTMLErrorMessage(getStorage(),e.getMessage());
throw new ServletException("Invalid URL called!");
} catch (final Exception ex) {
content = getURLHandler().
handleException(ex, sess, http_header);
log.debug("Some strange error while handling request", ex);
}
/*
* Set some HTTP headers: Date is now, the document should
* expire in 5 minutes, proxies and clients shouldn't cache it
* and all WebMail documents must be revalidated when they think
* they don't have to follow the "no-cache".
*/
res.setDateHeader("Date:", System.currentTimeMillis());
res.setDateHeader(
"Expires", System.currentTimeMillis() + 300000);
res.setHeader("Pragma", "no-cache");
res.setHeader("Cache-Control", "must-revalidate");
synchronized (out) {
res.setStatus(content.getReturnCode());
if (content.hasHTTPHeader()) {
final Enumeration enumVar = content.getHTTPHeaderKeys();
while (enumVar.hasMoreElements()) {
final String s = (String) enumVar.nextElement();
res.setHeader(s, content.getHTTPHeader(s));
}
}
/*
* What we will send is an image or some other sort of
* binary
*/
if (content instanceof HTMLImage) {
final HTMLImage img = (HTMLImage) content;
/*
* the HTMLImage class provides us with most of the
* necessary information that we want to send
*/
res.setHeader("Content-Type", img.getContentType());
res.setHeader("Content-Transfer-Encoding",
img.getContentEncoding());
res.setHeader("Content-Length", "" + img.size());
res.setHeader("Connection", "Keep-Alive");
/* Send 8k junks */
int offset = 0;
while (offset + chunk_size < img.size()) {
out.write(img.toBinary(), offset, chunk_size);
offset += chunk_size;
}
out.write(img.toBinary(), offset, img.size() - offset);
out.flush();
out.close();
} else {
final byte[] encoded_content =
content.toString().getBytes("UTF-8");
/*
* We are sending HTML text. Set the encoding to UTF-8
* for Unicode messages
*/
res.setHeader("Content-Length",
"" + (encoded_content.length + 2));
res.setHeader("Connection", "Keep-Alive");
res.setHeader(
"Content-Type", "text/html; charset=\"UTF-8\"");
out.write(encoded_content);
out.write("\r\n".getBytes());
out.flush();
out.close();
}
}
} catch (final DocumentNotFoundException e) {
log.info("Connection to " + addr.toString()
+ ": Could not handle request (" + err_code
+ ") (Reason: " + e.getMessage() + ")");
throw new ServletException("Error: " + e.getMessage(), e);
// res.setStatus(err_code);
// res.setHeader("Content-type","text/html");
// res.setHeader("Connection","close");
// content=new HTMLErrorMessage(getStorage(),e.getMessage());
// out.write((content+"\r\n").getBytes("UTF-8"));
// out.write("</HTML>\r\n".getBytes());
// out.flush();
// out.close();
}
} catch (final Exception e) {
log.info("Connection to " + addr.toString()
+ " closed unexpectedly", e);
throw new ServletException(e.getMessage());
}
}
/**
* Init possible servers of this main class
*/
@Override
protected void initServers() {
}
@Override
protected void shutdownServers() {
}
@Override
public String getBasePath() {
return basepath;
}
@Override
public String getImageBasePath() {
return imgbase;
}
public WebMailSession newSession(HttpServletRequest req,
HTTPRequestHeader h) throws UserDataException,
InvalidPasswordException, WebMailException {
final HttpSession sess = req.getSession(true);
if (sess.getAttribute("webmail.session") == null) {
final WebMailSession n = new WebMailSession(this, req, h);
timer.addTimeableConnection(n);
n.login();
sess.setAttribute("webmail.session", n);
sessions.put(sess.getId(), n);
log.debug("Created new Session: " + sess.getId());
return n;
} else {
final Object tmp = sess.getAttribute("webmail.session");
if (tmp instanceof WebMailSession) {
final WebMailSession n = (WebMailSession) tmp;
n.login();
log.debug("Using old Session: " + sess.getId());
return n;
} else {
/*
* If we have an admin session, get rid of it and create a new
* session
*/
sess.setAttribute("webmail.session", null);
log.debug("Reusing old AdminSession: " + sess.getId());
return newSession(req, h);
}
}
}
public AdminSession newAdminSession(
HttpServletRequest req, HTTPRequestHeader h)
throws InvalidPasswordException, WebMailException {
final HttpSession sess = req.getSession(true);
if (sess.getAttribute("webmail.session") == null) {
final AdminSession n = new AdminSession(this, req, h);
timer.addTimeableConnection(n);
n.login(h);
sess.setAttribute("webmail.session", n);
sessions.put(sess.getId(), n);
log.debug("Created new Session: " + sess.getId());
return n;
} else {
final Object tmp = sess.getAttribute("webmail.session");
if (tmp instanceof AdminSession) {
final AdminSession n = (AdminSession) tmp;
n.login(h);
log.debug("Using old Session: " + sess.getId());
return n;
} else {
sess.setAttribute("webmail.session", null);
log.debug("Reusing old UserSession: " + sess.getId());
return newAdminSession(req, h);
}
}
}
/** Overwrite the old session handling methods */
public WebMailSession newSession(InetAddress a, HTTPRequestHeader h)
throws InvalidPasswordException {
throw new RuntimeException("newSession invalid call");
}
public AdminSession newAdminSession(InetAddress a, HTTPRequestHeader h)
throws InvalidPasswordException {
throw new RuntimeException("newAdminSession invalid call");
}
public HTTPSession getSession(
String sessionid, InetAddress a, HTTPRequestHeader h)
throws InvalidPasswordException {
throw new RuntimeException("getSession invalid call");
}
@Override
public Enumeration getServers() {
return new Enumeration() {
public boolean hasMoreElements() {
return false;
}
public Object nextElement() {
return null;
}
};
}
@Override
public String toString() {
return "Server: " + srvlt_config.getServletContext().getServerInfo()
+ "\nMount Point: " + getBasePath() + '\n' + getServletInfo();
}
@Override
public Object getServer(String ID) {
return null;
}
@Override
public void reinitServer(String ID) {
}
public static String getVersion() {
return "WebMail/Java Servlet v" + VERSION;
}
}