// 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("<HTML><HEAD>");
buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
buf.append(title);
buf.append("</TITLE></HEAD><BODY>\n<H1>");
buf.append(title);
buf.append("</H1>\n<TABLE BORDER=0>\n");
if (parent)
{
buf.append("<TR><TD><A HREF=\"");
buf.append(URIUtil.addPaths(base,"../"));
buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\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<TR><TD><A HREF=\"");
String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
buf.append(path);
if (item.isDirectory() && !path.endsWith("/"))
buf.append(URIUtil.SLASH);
buf.append("\">");
buf.append(deTag(ls[i]));
buf.append(" ");
buf.append("</A></TD><TD ALIGN=right>");
buf.append(item.length());
buf.append(" bytes </TD><TD>");
buf.append(dfmt.format(new Date(item.lastModified())));
buf.append(" UTC</TD></TR>");
}
buf.append("</TABLE>\n");
buf.append("</BODY></HTML>\n");
return buf.toString();
}
/**
* Copied unchanged from Resource.java
*
* Encode any characters that could break the URI string in an HREF.
* Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
*
* 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<raw.length();i++)
{
char c=raw.charAt(i);
switch(c)
{
case '\'':
case '"':
case '<':
case '>':
buf=new StringBuffer(raw.length()<<1);
break loop;
}
}
if (buf==null)
return raw;
for (int i=0;i<raw.length();i++)
{
char c=raw.charAt(i);
switch(c)
{
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
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);
}
}