JETTY-980 & JETTY-1004
git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@199 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
parent
79edb1ebee
commit
456a00161b
|
@ -1,9 +1,11 @@
|
|||
jetty-7.0.0.M2-SNAPSHOT
|
||||
+ JETTY-941 Linux chkconfig hint
|
||||
+ JETTY-959 CGI servlet doesn't kill the CGI in case the client disconnects
|
||||
+ JETTY-959 CGI servlet doesn't kill the CGI in case the client disconnects
|
||||
+ JETTY-980 Fixed ResourceHandler ? handling, and bad URI creation in listings
|
||||
+ JETTY-996 Make start-stop-daemon optional
|
||||
+ 273767 Update to use geronimo annotations spec 1.1.1
|
||||
+ JETTY-1003 java.lang.IllegalArgumentException: timeout can't be negative
|
||||
+ JETTY-1004 Canonical path handling includes ? in path segment
|
||||
|
||||
jetty-7.0.0.M1 22 April 2009
|
||||
+ 271258 FORM Authentication dispatch handling avoids caching
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.server.handler.ResourceHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -47,9 +48,10 @@ public class FileServer
|
|||
response.getWriter().println(listing);
|
||||
}
|
||||
};
|
||||
resource_handler.setWelcomeFiles(new String[]{"index.html"});
|
||||
|
||||
resource_handler.setResourceBase(args.length==2?args[1]:".");
|
||||
|
||||
Log.info("serving "+resource_handler.getBaseResource());
|
||||
HandlerList handlers = new HandlerList();
|
||||
handlers.setHandlers(new Handler[]{resource_handler,new DefaultHandler()});
|
||||
server.setHandler(handlers);
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.jetty.http.HttpGenerator;
|
|||
import org.eclipse.jetty.http.HttpHeaderValues;
|
||||
import org.eclipse.jetty.http.HttpHeaders;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersions;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.BufferCache.CachedBuffer;
|
||||
|
@ -419,12 +420,12 @@ public class Response implements HttpServletResponse
|
|||
{
|
||||
StringBuilder buf = _connection.getRequest().getRootURL();
|
||||
if (location.startsWith("/"))
|
||||
buf.append(URIUtil.canonicalPath(location));
|
||||
buf.append(location);
|
||||
else
|
||||
{
|
||||
String path=_connection.getRequest().getRequestURI();
|
||||
String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path);
|
||||
location=URIUtil.canonicalPath(URIUtil.addPaths(parent,location));
|
||||
location=URIUtil.addPaths(parent,location);
|
||||
if(location==null)
|
||||
throw new IllegalStateException("path cannot be above root");
|
||||
if (!location.startsWith("/"))
|
||||
|
@ -433,6 +434,27 @@ public class Response implements HttpServletResponse
|
|||
}
|
||||
|
||||
location=buf.toString();
|
||||
HttpURI uri = new HttpURI(location);
|
||||
String path=uri.getDecodedPath();
|
||||
String canonical=URIUtil.canonicalPath(path);
|
||||
if (canonical==null)
|
||||
throw new IllegalArgumentException();
|
||||
if (!canonical.equals(path))
|
||||
{
|
||||
buf = _connection.getRequest().getRootURL();
|
||||
buf.append(canonical);
|
||||
if (uri.getQuery()!=null)
|
||||
{
|
||||
buf.append('?');
|
||||
buf.append(uri.getQuery());
|
||||
}
|
||||
if (uri.getFragment()!=null)
|
||||
{
|
||||
buf.append('#');
|
||||
buf.append(uri.getFragment());
|
||||
}
|
||||
location=buf.toString();
|
||||
}
|
||||
}
|
||||
resetBuffer();
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.eclipse.jetty.server.handler.ContextHandler.Context;
|
|||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.resource.FileResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
|
||||
|
@ -56,6 +57,7 @@ public class ResourceHandler extends AbstractHandler
|
|||
String[] _welcomeFiles={"index.html"};
|
||||
MimeTypes _mimeTypes = new MimeTypes();
|
||||
ByteArrayBuffer _cacheControl;
|
||||
boolean _aliases;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public ResourceHandler()
|
||||
|
@ -74,12 +76,41 @@ public class ResourceHandler extends AbstractHandler
|
|||
_mimeTypes = mimeTypes;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return True if resource aliases are allowed.
|
||||
*/
|
||||
public boolean isAliases()
|
||||
{
|
||||
return _aliases;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
|
||||
* Allowing aliases can significantly increase security vulnerabilities.
|
||||
* If this handler is deployed inside a ContextHandler, then the
|
||||
* {@link ContextHandler#isAliases()} takes precedent.
|
||||
* @param aliases True if aliases are supported.
|
||||
*/
|
||||
public void setAliases(boolean aliases)
|
||||
{
|
||||
_aliases = aliases;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void doStart()
|
||||
throws Exception
|
||||
{
|
||||
Context scontext = ContextHandler.getCurrentContext();
|
||||
_context = (scontext==null?null:scontext.getContextHandler());
|
||||
|
||||
if (_context!=null)
|
||||
_aliases=_context.isAliases();
|
||||
|
||||
if (!_aliases && !FileResource.getCheckAliases())
|
||||
throw new IllegalStateException("Alias checking disabled");
|
||||
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
|
@ -239,6 +270,11 @@ public class ResourceHandler extends AbstractHandler
|
|||
|
||||
if (resource==null || !resource.exists())
|
||||
return;
|
||||
if (!_aliases && resource.getAlias()!=null)
|
||||
{
|
||||
Log.info(resource+" aliased to "+resource.getAlias());
|
||||
return;
|
||||
}
|
||||
|
||||
// We are going to server something
|
||||
base_request.setHandled(true);
|
||||
|
|
|
@ -52,6 +52,7 @@ import org.eclipse.jetty.util.MultiPartOutputStream;
|
|||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.resource.FileResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
|
@ -154,9 +155,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
|
|||
_redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
|
||||
_gzip=getInitBoolean("gzip",_gzip);
|
||||
|
||||
String aliases=_servletContext.getInitParameter("aliases");
|
||||
if (aliases!=null)
|
||||
_contextHandler.setAliases(Boolean.parseBoolean(aliases));
|
||||
if (getInitParameter("aliases")!=null)
|
||||
_contextHandler.setAliases(getInitBoolean("aliases",false));
|
||||
boolean aliases=_contextHandler.isAliases();
|
||||
if (!aliases && !FileResource.getCheckAliases())
|
||||
throw new IllegalStateException("Alias checking disabled");
|
||||
if (aliases)
|
||||
_servletContext.log("Aliases are enabled");
|
||||
|
||||
_useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
|
||||
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
public class DefaultServletTest extends TestCase
|
||||
{
|
||||
|
@ -42,37 +46,278 @@ public class DefaultServletTest extends TestCase
|
|||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void testListingXSS() throws Exception
|
||||
|
||||
public void testListingWithSession() throws Exception
|
||||
{
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class,"/listing/*");
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class,"/*");
|
||||
defholder.setInitParameter("dirAllowed","true");
|
||||
defholder.setInitParameter("redirectWelcome","false");
|
||||
defholder.setInitParameter("gzip","false");
|
||||
|
||||
File resBase = new File("src/test/resources");
|
||||
|
||||
assertTrue("resBase.exists",resBase.exists());
|
||||
assertTrue("resBase.isDirectory",resBase.isDirectory());
|
||||
File testDir = new File("target/tests/" + getName());
|
||||
prepareEmptyTestDir(testDir);
|
||||
|
||||
/* create some content in the docroot */
|
||||
File resBase = new File(testDir, "docroot");
|
||||
resBase.mkdirs();
|
||||
new File(resBase, "one").mkdir();
|
||||
new File(resBase, "two").mkdir();
|
||||
new File(resBase, "three").mkdir();
|
||||
|
||||
String resBasePath = resBase.getAbsolutePath();
|
||||
defholder.setInitParameter("resourceBase",resBasePath);
|
||||
|
||||
StringBuffer req1 = new StringBuffer();
|
||||
req1.append("GET /context/listing/;<script>window.alert(\"hi\");</script> HTTP/1.1\n");
|
||||
req1.append("GET /context/;JSESSIONID=1234567890 HTTP/1.1\n");
|
||||
req1.append("Host: localhost\n");
|
||||
req1.append("Connection: close\n");
|
||||
req1.append("\n");
|
||||
|
||||
String response = connector.getResponses(req1.toString());
|
||||
|
||||
assertResponseContains("listing/one/",response);
|
||||
assertResponseContains("listing/two/",response);
|
||||
assertResponseContains("listing/three/",response);
|
||||
assertResponseContains("/one/;JSESSIONID=1234567890",response);
|
||||
assertResponseContains("/two/;JSESSIONID=1234567890",response);
|
||||
assertResponseContains("/three/;JSESSIONID=1234567890",response);
|
||||
|
||||
assertResponseNotContains("<script>",response);
|
||||
}
|
||||
|
||||
public void testListingXSS() throws Exception
|
||||
{
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class,"/*");
|
||||
defholder.setInitParameter("dirAllowed","true");
|
||||
defholder.setInitParameter("redirectWelcome","false");
|
||||
defholder.setInitParameter("gzip","false");
|
||||
|
||||
File testDir = new File("target/tests/" + getName());
|
||||
prepareEmptyTestDir(testDir);
|
||||
|
||||
/* create some content in the docroot */
|
||||
File resBase = new File(testDir, "docroot");
|
||||
resBase.mkdirs();
|
||||
new File(resBase, "one").mkdir();
|
||||
new File(resBase, "two").mkdir();
|
||||
new File(resBase, "three").mkdir();
|
||||
assertTrue("Creating dir 'f??r' (Might not work in Windows)", new File(resBase, "f??r").mkdir());
|
||||
|
||||
String resBasePath = resBase.getAbsolutePath();
|
||||
defholder.setInitParameter("resourceBase",resBasePath);
|
||||
|
||||
StringBuffer req1 = new StringBuffer();
|
||||
/* Intentionally bad request URI.
|
||||
* Sending a non-encoded URI with typically encoded characters '<', '>', and '"'.
|
||||
*/
|
||||
req1.append("GET /context/;<script>window.alert(\"hi\");</script> HTTP/1.1\n");
|
||||
req1.append("Host: localhost\n");
|
||||
req1.append("\n");
|
||||
|
||||
String response = connector.getResponses(req1.toString());
|
||||
|
||||
assertResponseContains("/one/",response);
|
||||
assertResponseContains("/two/",response);
|
||||
assertResponseContains("/three/",response);
|
||||
assertResponseContains("/f%3F%3Fr",response);
|
||||
|
||||
assertResponseNotContains("<script>",response);
|
||||
}
|
||||
|
||||
public void testListingProperUrlEncoding() throws Exception
|
||||
{
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class,"/*");
|
||||
defholder.setInitParameter("dirAllowed","true");
|
||||
defholder.setInitParameter("redirectWelcome","false");
|
||||
defholder.setInitParameter("gzip","false");
|
||||
|
||||
File testDir = new File("target/tests/" + getName());
|
||||
prepareEmptyTestDir(testDir);
|
||||
|
||||
/* create some content in the docroot */
|
||||
File resBase = new File(testDir, "docroot");
|
||||
resBase.mkdirs();
|
||||
File wackyDir = new File(resBase, "dir;"); // this should not be double-encoded.
|
||||
assertTrue(wackyDir.mkdirs());
|
||||
|
||||
new File(wackyDir, "four").mkdir();
|
||||
new File(wackyDir, "five").mkdir();
|
||||
new File(wackyDir, "six").mkdir();
|
||||
|
||||
/* At this point we have the following
|
||||
* testListingProperUrlEncoding/
|
||||
* `-- docroot
|
||||
* `-- dir;
|
||||
* |-- five
|
||||
* |-- four
|
||||
* `-- six
|
||||
*/
|
||||
|
||||
String resBasePath = resBase.getAbsolutePath();
|
||||
defholder.setInitParameter("resourceBase",resBasePath);
|
||||
|
||||
// First send request in improper, unencoded way.
|
||||
String response = connector.getResponses("GET /context/dir;/ HTTP/1.0\r\n\r\n");
|
||||
|
||||
assertResponseContains("HTTP/1.1 404 Not Found", response);
|
||||
|
||||
|
||||
// Now send request in proper, encoded format.
|
||||
connector.reopen();
|
||||
StringBuffer req2 = new StringBuffer();
|
||||
response = connector.getResponses("GET /context/dir%3B/ HTTP/1.0\r\n\r\n");
|
||||
|
||||
// Should not see double-encoded ";"
|
||||
// First encoding: ";" -> "%3b"
|
||||
// Second encoding: "%3B" -> "%253B" (BAD!)
|
||||
assertResponseNotContains("%253B",response);
|
||||
|
||||
assertResponseContains("/dir%3B/",response);
|
||||
assertResponseContains("/dir%3B/four/",response);
|
||||
assertResponseContains("/dir%3B/five/",response);
|
||||
assertResponseContains("/dir%3B/six/",response);
|
||||
}
|
||||
|
||||
public void testListingContextBreakout() throws Exception
|
||||
{
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class,"/*");
|
||||
defholder.setInitParameter("dirAllowed","true");
|
||||
defholder.setInitParameter("redirectWelcome","false");
|
||||
defholder.setInitParameter("gzip","false");
|
||||
|
||||
File testDir = new File("target/tests/" + getName());
|
||||
prepareEmptyTestDir(testDir);
|
||||
|
||||
/* create some content in the docroot */
|
||||
File resBase = new File(testDir, "docroot");
|
||||
resBase.mkdirs();
|
||||
new File(resBase, "one").mkdir();
|
||||
new File(resBase, "two").mkdir();
|
||||
new File(resBase, "three").mkdir();
|
||||
File wackyDir = new File(resBase, "dir");
|
||||
assertTrue(wackyDir.mkdirs());
|
||||
new File(wackyDir, "four").mkdir();
|
||||
new File(wackyDir, "five").mkdir();
|
||||
new File(wackyDir, "six").mkdir();
|
||||
|
||||
wackyDir = new File(resBase, "dir;");
|
||||
assertTrue(wackyDir.mkdirs());
|
||||
|
||||
/* create some content outside of the docroot */
|
||||
File sekret = new File(testDir, "sekret");
|
||||
sekret.mkdirs();
|
||||
File pass = new File(sekret, "pass");
|
||||
createFile(pass, "Sssh, you shouldn't be seeing this");
|
||||
|
||||
/* At this point we have the following
|
||||
* testListingContextBreakout/
|
||||
* |-- docroot
|
||||
* | |-- dir
|
||||
* | | |-- five
|
||||
* | | |-- four
|
||||
* | | `-- six
|
||||
* | |-- dir;
|
||||
* | |-- one
|
||||
* | |-- three
|
||||
* | `-- two
|
||||
* `-- sekret
|
||||
* `-- pass
|
||||
*/
|
||||
|
||||
String resBasePath = resBase.getAbsolutePath();
|
||||
defholder.setInitParameter("resourceBase",resBasePath);
|
||||
|
||||
String response = connector.getResponses("GET /context/dir/?/../../sekret/pass HTTP/1.0\r\n\r\n");
|
||||
|
||||
assertResponseContains("/four/",response);
|
||||
assertResponseContains("/five/",response);
|
||||
assertResponseContains("/six/",response);
|
||||
assertResponseNotContains("Sssh",response);
|
||||
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir/../../../sekret/pass HTTP/1.0\r\n\r\n");
|
||||
assertResponseNotContains("Sssh",response);
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir/%3F/../../sekret/pass HTTP/1.0\r\n\r\n");
|
||||
assertResponseNotContains("Sssh",response);
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir/%3F/../../../sekret/pass HTTP/1.0\r\n\r\n");
|
||||
assertResponseNotContains("Sssh",response);
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir%3F/../../sekret/pass HTTP/1.0\r\n\r\n");
|
||||
assertResponseNotContains("Sssh",response);
|
||||
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir%3B/ HTTP/1.0\r\n\r\n");
|
||||
assertResponseContains("Directory: /context/dir;/<",response);
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir/../ HTTP/1.0\r\n\r\n");
|
||||
assertResponseContains("Directory: /context/<",response);
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir%3B/../ HTTP/1.0\r\n\r\n");
|
||||
assertResponseContains("Directory: /context/<",response);
|
||||
connector.reopen();
|
||||
response = connector.getResponses("GET /context/dir%3B/../../sekret/pass HTTP/1.0\r\n\r\n");
|
||||
assertResponseContains("Not Found",response);
|
||||
}
|
||||
|
||||
private void createFile(File file, String str) throws IOException
|
||||
{
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(file);
|
||||
out.write(str.getBytes());
|
||||
out.flush();
|
||||
} finally {
|
||||
IO.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareEmptyTestDir(File testdir)
|
||||
{
|
||||
if (testdir.exists())
|
||||
{
|
||||
emptyDir(testdir);
|
||||
}
|
||||
else
|
||||
{
|
||||
testdir.mkdirs();
|
||||
}
|
||||
|
||||
assertTrue("test dir should exists",testdir.exists());
|
||||
assertTrue("test dir should be a dir",testdir.isDirectory());
|
||||
assertTrue("test dir should be empty",isEmpty(testdir));
|
||||
}
|
||||
|
||||
private boolean isEmpty(File dir)
|
||||
{
|
||||
if (!dir.isDirectory())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return dir.list().length == 0;
|
||||
}
|
||||
|
||||
private void emptyDir(File dir)
|
||||
{
|
||||
File entries[] = dir.listFiles();
|
||||
for (int i = 0; i < entries.length; i++)
|
||||
{
|
||||
deletePath(entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void deletePath(File path)
|
||||
{
|
||||
if (path.isDirectory())
|
||||
{
|
||||
File entries[] = path.listFiles();
|
||||
for (int i = 0; i < entries.length; i++)
|
||||
{
|
||||
deletePath(entries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue("Deleting: " + path.getAbsolutePath(),path.delete());
|
||||
}
|
||||
|
||||
private void assertResponseNotContains(String forbidden, String response)
|
||||
{
|
||||
int idx = response.indexOf(forbidden);
|
||||
|
|
|
@ -304,8 +304,8 @@ public class URIUtil
|
|||
/** Add two URI path segments.
|
||||
* Handles null and empty paths, path and query params (eg ?a=b or
|
||||
* ;JSESSIONID=xxx) and avoids duplicate '/'
|
||||
* @param p1 URI path segment
|
||||
* @param p2 URI path segment
|
||||
* @param p1 URI path segment (should be encoded)
|
||||
* @param p2 URI path segment (should be encoded)
|
||||
* @return Legally combined path segments.
|
||||
*/
|
||||
public static String addPaths(String p1, String p2)
|
||||
|
@ -395,8 +395,10 @@ public class URIUtil
|
|||
return path;
|
||||
|
||||
int end=path.length();
|
||||
|
||||
int queryIdx=path.indexOf('?');
|
||||
int start = path.lastIndexOf('/', (queryIdx > 0 ? queryIdx : end));
|
||||
// int start = path.lastIndexOf('/', end);
|
||||
|
||||
search:
|
||||
while (end>0)
|
||||
|
|
|
@ -49,7 +49,9 @@ public class FileResource extends URLResource
|
|||
(System.getProperty("org.eclipse.jetty.util.FileResource.checkAliases","true"));
|
||||
|
||||
if (__checkAliases)
|
||||
Log.debug("Checking Resource aliases");
|
||||
Log.debug("Checking Resource aliases");
|
||||
else
|
||||
Log.warn("Resource alias checking is disabled");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -416,11 +416,11 @@ public abstract class Resource implements Serializable
|
|||
* @param parent True if the parent directory should be included
|
||||
* @return String of HTML
|
||||
*/
|
||||
public String getListHTML(String base,
|
||||
boolean parent)
|
||||
public String getListHTML(String base,boolean parent)
|
||||
throws IOException
|
||||
{
|
||||
if (!isDirectory())
|
||||
base=URIUtil.canonicalPath(base);
|
||||
if (base==null || !isDirectory())
|
||||
return null;
|
||||
|
||||
String[] ls = list();
|
||||
|
@ -436,28 +436,32 @@ public abstract class Resource implements Serializable
|
|||
buf.append(title);
|
||||
buf.append("</TITLE></HEAD><BODY>\n<H1>");
|
||||
buf.append(title);
|
||||
buf.append("</H1><TABLE BORDER=0>");
|
||||
buf.append("</H1>\n<TABLE BORDER=0>\n");
|
||||
|
||||
if (parent)
|
||||
{
|
||||
buf.append("<TR><TD><A HREF=\"");
|
||||
URIUtil.encodePath(buf,URIUtil.addPaths(base,"../"));
|
||||
buf.append(URIUtil.addPaths(base,"../"));
|
||||
buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
|
||||
}
|
||||
|
||||
String defangedBase = defangURI(base);
|
||||
|
||||
DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
|
||||
DateFormat.MEDIUM);
|
||||
for (int i=0 ; i< ls.length ; i++)
|
||||
{
|
||||
String encoded=URIUtil.encodePath(ls[i]);
|
||||
Resource item = addPath(ls[i]);
|
||||
|
||||
buf.append("<TR><TD><A HREF=\"");
|
||||
String path=URIUtil.addPaths(base,encoded);
|
||||
buf.append("\n<TR><TD><A HREF=\"");
|
||||
String path=URIUtil.addPaths(defangedBase,URIUtil.encodePath(ls[i]));
|
||||
|
||||
buf.append(path);
|
||||
|
||||
if (item.isDirectory() && !path.endsWith("/"))
|
||||
path=URIUtil.addPaths(path,URIUtil.SLASH);
|
||||
URIUtil.encodePath(buf,path);
|
||||
buf.append(URIUtil.SLASH);
|
||||
|
||||
// URIUtil.encodePath(buf,path);
|
||||
buf.append("\">");
|
||||
buf.append(deTag(ls[i]));
|
||||
buf.append(" ");
|
||||
|
@ -465,7 +469,7 @@ public abstract class Resource implements Serializable
|
|||
buf.append(item.length());
|
||||
buf.append(" bytes </TD><TD>");
|
||||
buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
buf.append("</TD></TR>\n");
|
||||
buf.append("</TD></TR>");
|
||||
}
|
||||
buf.append("</TABLE>\n");
|
||||
buf.append("</BODY></HTML>\n");
|
||||
|
@ -473,7 +477,67 @@ public abstract class Resource implements Serializable
|
|||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String deTag(String raw) {
|
||||
/**
|
||||
* Defang 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 defang.
|
||||
* @return the defanged text.
|
||||
*/
|
||||
private static String defangURI(String raw)
|
||||
{
|
||||
StringBuffer buf = null;
|
||||
|
||||
if (buf==null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
private static String deTag(String raw)
|
||||
{
|
||||
return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue