diff --git a/apps/jetty/build.xml b/apps/jetty/build.xml index 7f500eca19..cd59f01587 100644 --- a/apps/jetty/build.xml +++ b/apps/jetty/build.xml @@ -304,6 +304,7 @@ + @@ -318,7 +319,7 @@ debug="true" deprecation="on" source="${javac.version}" target="${javac.version}" destdir="./build/obj" includeAntRuntime="false" - classpath="../../core/java/build/i2p.jar:./jettylib/commons-logging.jar:./jettylib/javax.servlet.jar:./jettylib/org.mortbay.jetty.jar:./jettylib/jetty-http.jar:./jettylib/jetty-io.jar:./jettylib/jetty-security.jar:./jettylib/jetty-util.jar:./jettylib/jetty-xml.jar" > + classpath="../../core/java/build/i2p.jar:./jettylib/commons-logging.jar:./jettylib/javax.servlet.jar:./jettylib/org.mortbay.jetty.jar:./jettylib/jetty-http.jar:./jettylib/jetty-io.jar:./jettylib/jetty-security.jar:./jettylib/jetty-servlet.jar:./jettylib/jetty-util.jar:./jettylib/jetty-xml.jar" > diff --git a/apps/jetty/java/src/net/i2p/servlet/I2PDefaultServlet.java b/apps/jetty/java/src/net/i2p/servlet/I2PDefaultServlet.java new file mode 100644 index 0000000000..455a8fe498 --- /dev/null +++ b/apps/jetty/java/src/net/i2p/servlet/I2PDefaultServlet.java @@ -0,0 +1,316 @@ +// Contains code from Jetty 9.2.21: + +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package net.i2p.servlet; + +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import javax.servlet.ServletContext; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollection; + + + +/** + * Extends DefaultServlet to set locale for the displayed time of directory listings, + * to prevent leaking of the locale. + * + * @since 0.9.31 + * + */ +public class I2PDefaultServlet extends DefaultServlet +{ + // shadows of private fields in super + private ContextHandler _contextHandler; + private boolean _dirAllowed=true; + private Resource _resourceBase; + private Resource _stylesheet; + + private static final String FORMAT = "yyyy-MM-dd HH:mm"; + + /** + * Overridden to save local copies of dirAllowed, locale, resourceBase, and stylesheet. + * Calls super. + */ + @Override + public void init() + throws UnavailableException + { + super.init(); + _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed); + + String rb=getInitParameter("resourceBase"); + if (rb!=null) + { + try{_resourceBase=_contextHandler.newResource(rb);} + catch (Exception e) + { + throw new UnavailableException(e.toString()); + } + } + + String css=getInitParameter("stylesheet"); + try + { + if(css!=null) + { + _stylesheet = Resource.newResource(css); + if(!_stylesheet.exists()) + { + _stylesheet = null; + } + } + if(_stylesheet == null) + { + _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css")); + } + } + catch(Exception e) + { + } + } + + /** + * Overridden to save the result + * Calls super. + */ + @Override + protected ContextHandler initContextHandler(ServletContext servletContext) + { + ContextHandler rv = super.initContextHandler(servletContext); + _contextHandler = rv; + return rv; + } + + /* copied from DefaultServlet unchanged */ + private boolean getInitBoolean(String name, boolean dft) + { + String value=getInitParameter(name); + if (value==null || value.length()==0) + return dft; + return (value.startsWith("t")|| + value.startsWith("T")|| + value.startsWith("y")|| + value.startsWith("Y")|| + value.startsWith("1")); + } + + /** + * Copied and modified from DefaultServlet.java. + * Overridden to set the Locale for the dates. + * + * Get the resource list as a HTML directory listing. + * @param base The base URL + * @param parent True if the parent directory should be included + * @return String of HTML + */ + @Override + protected void sendDirectory(HttpServletRequest request, + HttpServletResponse response, + Resource resource, + String pathInContext) + throws IOException + { + if (!_dirAllowed) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + byte[] data=null; + String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH); + + //If the DefaultServlet has a resource base set, use it + if (_resourceBase != null) + { + // handle ResourceCollection + if (_resourceBase instanceof ResourceCollection) + resource=_resourceBase.addPath(pathInContext); + } + //Otherwise, try using the resource base of its enclosing context handler + else if (_contextHandler.getBaseResource() instanceof ResourceCollection) + resource=_contextHandler.getBaseResource().addPath(pathInContext); + + String dir = getListHTML(resource, base, pathInContext.length()>1); + if (dir==null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN, + "No directory"); + return; + } + + data=dir.getBytes("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + response.setContentLength(data.length); + response.getOutputStream().write(data); + } + + /** + * Copied and modified from Resource.java + * Modified to set the Locale for the dates. + * + * Get the resource list as a HTML directory listing. + * @param base The base URL + * @param parent True if the parent directory should be included + * @return String of HTML + */ + private static String getListHTML(Resource res, String base, boolean parent) + throws IOException + { + base=URIUtil.canonicalPath(base); + if (base==null || !res.isDirectory()) + return null; + + String[] ls = res.list(); + if (ls==null) + return null; + Arrays.sort(ls); + + String decodedBase = URIUtil.decodePath(base); + String title = "Directory: "+deTag(decodedBase); + + StringBuilder buf=new StringBuilder(4096); + buf.append(""); + buf.append(""); + buf.append(title); + buf.append("\n

"); + buf.append(title); + buf.append("

\n\n"); + + if (parent) + { + buf.append("\n"); + } + + String encodedBase = hrefEncodeURI(base); + + DateFormat dfmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.UK); + TimeZone utc = TimeZone.getTimeZone("GMT"); + dfmt.setTimeZone(utc); + for (int i=0 ; i< ls.length ; i++) + { + Resource item = res.addPath(ls[i]); + + buf.append("\n"); + } + buf.append("
Parent Directory
"); + buf.append(deTag(ls[i])); + buf.append(" "); + buf.append(""); + buf.append(item.length()); + buf.append(" bytes "); + buf.append(dfmt.format(new Date(item.lastModified()))); + buf.append(" UTC
\n"); + buf.append("\n"); + + return buf.toString(); + } + + /** + * Copied unchanged from Resource.java + * + * Encode any characters that could break the URI string in an HREF. + * Such as ">Link + * + * The above example would parse incorrectly on various browsers as the "<" or '"' characters + * would end the href attribute value string prematurely. + * + * @param raw the raw text to encode. + * @return the defanged text. + */ + private static String hrefEncodeURI(String raw) + { + StringBuffer buf = null; + + loop: + for (int i=0;i': + buf=new StringBuffer(raw.length()<<1); + break loop; + } + } + if (buf==null) + return raw; + + for (int i=0;i': + buf.append("%3E"); + continue; + default: + buf.append(c); + continue; + } + } + + return buf.toString(); + } + + /** + * Copied unchanged from Resource.java + */ + private static String deTag(String raw) + { + return StringUtil.sanitizeXmlString(raw); + } +} diff --git a/history.txt b/history.txt index 70f984a1b3..e88f9933f3 100644 --- a/history.txt +++ b/history.txt @@ -1,6 +1,8 @@ 2017-05-05 zzz * Blockfile: Move from i2p.jar to addressbook.jar * i2psnark: Initial support for ut_comment, no UI yet + * Jetty: New default servlet for eepsite, with + locale-independent directory listing (ticket #1965) * 2017-05-03 0.9.30 released diff --git a/installer/resources/eepsite/contexts/base-context.xml b/installer/resources/eepsite/contexts/base-context.xml index ef18daf499..70b681ba58 100644 --- a/installer/resources/eepsite/contexts/base-context.xml +++ b/installer/resources/eepsite/contexts/base-context.xml @@ -34,7 +34,7 @@ to serve static html files and images. - org.eclipse.jetty.servlet.DefaultServlet + net.i2p.servlet.I2PDefaultServlet / diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 282c18b422..0725033fa6 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 2; + public final static long BUILD = 3; /** for example "-test" */ public final static String EXTRA = "";