package net.i2p.servlet; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.Charset; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.ServletException; import net.i2p.CoreVersion; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.NativeBigInteger; import net.i2p.util.Translate; import org.eclipse.jetty.server.Server; /** * Common servlet for errors * This is intended for webapps and local plugins. * It is not appropriate for eepsites or remotely-accessible plugins, * as it uses local console resources. * * See http://www.eclipse.org/jetty/documentation/current/custom-error-pages.html * for how to add to web.xml or see examples in bundled webapps. * * Init parameters: * * * Most strings are translated with the console bundle, as we're using the same strings * as in error.jsp and error500.jsp. * * Supported error codes: 403, 404, and 500+. * Others must be added here. * * @since 0.9.34 adapted from routerconsole error.jsp, error500.jsp, and CSSHelper */ public class ErrorServlet extends HttpServlet { private static final long serialVersionUID = 99356750L; private final I2PAppContext _context; private static final String CONSOLE_BUNDLE_NAME = "net.i2p.router.web.messages"; private static final String PROP_THEME_NAME = "routerconsole.theme"; private static final String PROP_THEME_PFX = PROP_THEME_NAME + '.'; private static final String DEFAULT_THEME = "light"; private static final String BASE_THEME_PATH = "/themes/console/"; private static final String DEFAULT_ICO = "images/favicon.ico"; private static final String DEFAULT_CSS = "console.css"; /** to be added to head */ private final String _icoPath = BASE_THEME_PATH + '/' + DEFAULT_ICO; private String _cssPath; /** for webapp translation */ private String _webappName; private String _bundleName; private String _defaultBundle; public ErrorServlet() { super(); _context = I2PAppContext.getGlobalContext(); } @Override public void init() throws ServletException { super.init(); _cssPath = getInitParameter("CSSPath"); if (_cssPath == null) { String dir = BASE_THEME_PATH + _context.getProperty(PROP_THEME_NAME, DEFAULT_THEME); _cssPath = dir + '/' + DEFAULT_CSS; } _webappName = getInitParameter("name"); if (_webappName == null) _webappName = "unknown"; _bundleName = getInitParameter("bundle"); _defaultBundle = _bundleName != null ? _bundleName : CONSOLE_BUNDLE_NAME; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setCharacterEncoding("UTF-8"); resp.setHeader("X-Content-Type-Options", "nosniff"); resp.setHeader("Accept-Ranges", "none"); resp.setDateHeader("Expires", 0); resp.setHeader("Cache-Control", "no-store, max-age=0, no-cache, must-revalidate"); resp.setHeader("Pragma", "no-cache"); resp.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'none'; form-action 'none'; frame-ancestors 'self'; object-src 'none'; media-src 'none'"); Integer ERROR_CODE = (Integer) req.getAttribute("javax.servlet.error.status_code"); String ERROR_URI = (String) req.getAttribute("javax.servlet.error.request_uri"); String ERROR_MESSAGE = (String) req.getAttribute("javax.servlet.error.message"); Throwable ERROR_THROWABLE = (Throwable) req.getAttribute("javax.servlet.error.exception"); int errorCode = ERROR_CODE != null ? ERROR_CODE.intValue() : 0; if (ERROR_CODE != null) { resp.setStatus(errorCode); } if (ERROR_URI == null) ERROR_URI = ""; else ERROR_URI = DataHelper.escapeHTML(ERROR_URI); if (ERROR_MESSAGE == null) ERROR_MESSAGE = ""; else ERROR_MESSAGE = DataHelper.escapeHTML(ERROR_MESSAGE); if (errorCode == 404 && (ERROR_URI.endsWith(".png") || ERROR_URI.endsWith(".jpg") || ERROR_URI.endsWith(".gif") || ERROR_URI.endsWith(".ico") || ERROR_URI.endsWith(".svg") || ERROR_URI.endsWith(".txt") || ERROR_URI.endsWith(".js") || ERROR_URI.endsWith(".css"))) { // keep it simple resp.setContentType("text/plain"); PrintWriter out = resp.getWriter(); out.println(_t("Error {0}", 404) + ": " + ERROR_URI + ' ' + _t("not found")); out.close(); return; } resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println(""); out.print(""); if (errorCode == 404) out.print(_t("Page Not Found")); else out.print(_t("Internal Error")); out.println(""); out.println(""); if (_icoPath != null) out.println(""); out.println(""); out.println(""); out.println("
"); out.println("\""
"); out.println("" + _t("Configuration") + " " + _t("Help") + ""); out.println("
"); out.println("
"); out.println("

" + _t("Error {0}", errorCode) + ": " + ERROR_MESSAGE + "

"); out.println("

" + _t("WebApp") + ": " + _w(_webappName) + "

"); outputMessage(out, errorCode, ERROR_MESSAGE, ERROR_URI, ERROR_THROWABLE); out.println("
"); out.close(); } /** * Needed if the errored page was a POST * @since 0.9.35 */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } /** * Override for specific cases. * This supports 403, 404, and 500+. * Output HTML that goes inside the div. * * @param errorCode e.g. 404 * @param errorMsg non-null, may be empty, already HTML-escaped * @param errorURI non-null, may be empty, already HTML-escaped * @param errorCause may be null */ protected void outputMessage(PrintWriter out, int errorCode, String errorMsg, String errorURI, Throwable errorCause) { if (errorCode == 404) { out.println(_t("Sorry! You appear to be requesting a non-existent Router Console page or resource.")); out.println("
"); out.println(_t("Error {0}", 404) + ": " + errorURI + " " + _t("not found")); } else if (errorCode == 403 || errorCode >= 500 || errorCause != null) { out.println(_t("Sorry! There has been an internal error.")); out.println("
"); out.println("

"); out.println(_t("Please report bugs on {0} or {1}.", "trac.i2p2.i2p", "trac.i2p2.de")); out.println("

"); out.println(_t("Please include this information in bug reports") + ':'); out.print("

"); out.print(_t("Error Details")); out.println("

"); out.println(_t("Error {0}", errorCode) + ": " + errorURI + " " + errorMsg); out.println("

"); if (errorCause != null) { StringWriter sw = new StringWriter(2048); PrintWriter pw = new PrintWriter(sw); errorCause.printStackTrace(pw); pw.flush(); String trace = sw.toString(); trace = trace.replace("&", "&").replace("<", "<").replace(">", ">"); trace = trace.replace("\n", "
    \n"); out.print(trace); } out.print("

"); out.println(_t("I2P Version and Running Environment")); out.println("

"); // router puts its version here String version = System.getProperty("router.version", CoreVersion.VERSION); out.println("I2P version: " + version + "
"); out.println("Java version: " + System.getProperty("java.vendor") + ' ' + System.getProperty("java.version") + " (" + System.getProperty("java.runtime.name") + ' ' + System.getProperty("java.runtime.version") + ")
"); out.println("Wrapper version: " + System.getProperty("wrapper.version", "none") + "
"); try { // wrap in case not running on Jetty out.println("Server version: " + Server.getVersion() + "
"); } catch (Throwable t) {} out.println("Platform: " + System.getProperty("os.name") + ' ' + System.getProperty("os.arch") + ' ' + System.getProperty("os.version") + "
"); out.println("Processor: " + NativeBigInteger.cpuModel() + " (" + NativeBigInteger.cpuType() + ")
"); out.println("Jbigi: " + NativeBigInteger.loadStatus() + "
"); out.println("Encoding: " + System.getProperty("file.encoding") + "
"); out.println("Charset: " + Charset.defaultCharset().name()); out.println("

"); out.println(_t("Note that system information, log timestamps, and log messages may provide clues to your location; please review everything you include in a bug report.")); out.println("

"); } else { out.println("

Unsupported error " + errorCode + "

"); } } /** translate a string, with webapp bundle */ protected String _w(String s) { return Translate.getString(s, _context, _defaultBundle); } /** translate a string, console bundle */ protected String _t(String s) { return Translate.getString(s, _context, CONSOLE_BUNDLE_NAME); } /** translate a string, console bundle */ protected String _t(String s, Object o) { return Translate.getString(s, o, _context, CONSOLE_BUNDLE_NAME); } /** translate a string, console bundle */ protected String _t(String s, Object o, Object o2) { return Translate.getString(s, o, o2, _context, CONSOLE_BUNDLE_NAME); } }