Issue #3549 - Using FileName properly in Directory Listings.
+ Even though this was reported against Windows, the solution implemented should be sane for all OS or FileSystem combinations. Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
parent
9b19374c5d
commit
3d028ab2ca
|
@ -21,6 +21,8 @@ package org.eclipse.jetty.servlet;
|
|||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.EnumSet;
|
||||
|
@ -51,6 +53,7 @@ import org.junit.Before;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DefaultServletTest
|
||||
|
@ -58,6 +61,9 @@ public class DefaultServletTest
|
|||
@Rule
|
||||
public TestingDir testdir = new TestingDir();
|
||||
|
||||
// The name of the odd-jar used for testing "jar:file://" based resource access.
|
||||
private static final String ODD_JAR = "jar-resource-odd.jar";
|
||||
|
||||
private Server server;
|
||||
private LocalConnector connector;
|
||||
private ServletContextHandler context;
|
||||
|
@ -70,9 +76,16 @@ public class DefaultServletTest
|
|||
connector = new LocalConnector(server);
|
||||
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
|
||||
|
||||
File extraJarResources = MavenTestingUtils.getTestResourceFile(ODD_JAR);
|
||||
URL urls[] = new URL[] { extraJarResources.toURI().toURL() };
|
||||
|
||||
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
URLClassLoader extraClassLoader = new URLClassLoader(urls, parentClassLoader);
|
||||
|
||||
context = new ServletContextHandler();
|
||||
context.setContextPath("/context");
|
||||
context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
|
||||
context.setClassLoader(extraClassLoader);
|
||||
|
||||
server.setHandler(context);
|
||||
server.addConnector(connector);
|
||||
|
@ -178,6 +191,122 @@ public class DefaultServletTest
|
|||
assertResponseNotContains("\"onmouseover", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* A regression on windows allowed the directory listing show
|
||||
* the fully qualified paths within the directory listing.
|
||||
* This test ensures that this behavior will not arise again.
|
||||
*/
|
||||
@Test
|
||||
public void testListingFilenamesOnly() throws Exception
|
||||
{
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/*");
|
||||
defholder.setInitParameter("dirAllowed", "true");
|
||||
defholder.setInitParameter("redirectWelcome", "false");
|
||||
defholder.setInitParameter("gzip", "false");
|
||||
|
||||
testdir.ensureEmpty();
|
||||
|
||||
/* create some content in the docroot */
|
||||
File resBase = testdir.getFile("docroot");
|
||||
FS.ensureDirExists(resBase);
|
||||
File one = new File(resBase, "one");
|
||||
assertTrue(one.mkdir());
|
||||
File deep = new File(one, "deep");
|
||||
assertTrue(deep.mkdir());
|
||||
FS.touch(new File(deep, "foo"));
|
||||
assertTrue(new File(resBase, "two").mkdir());
|
||||
assertTrue(new File(resBase, "three").mkdir());
|
||||
|
||||
String resBasePath = resBase.getAbsolutePath();
|
||||
defholder.setInitParameter("resourceBase", resBasePath);
|
||||
|
||||
StringBuffer req1 = new StringBuffer();
|
||||
req1.append("GET /context/one/deep/ HTTP/1.0\n");
|
||||
req1.append("\n");
|
||||
|
||||
String response = connector.getResponses(req1.toString());
|
||||
|
||||
assertResponseContains("/foo", response);
|
||||
assertResponseNotContains(resBase.getAbsolutePath(), response);
|
||||
}
|
||||
|
||||
/**
|
||||
* A regression on windows allowed the directory listing show
|
||||
* the fully qualified paths within the directory listing.
|
||||
* This test ensures that this behavior will not arise again.
|
||||
*/
|
||||
@Test
|
||||
public void testListingFilenamesOnly_UrlResource() throws Exception
|
||||
{
|
||||
URL extraResource = context.getClassLoader().getResource("rez/one");
|
||||
assertNotNull("Must have extra jar resource in classloader", extraResource);
|
||||
|
||||
String extraResourceBaseString = extraResource.toURI().toASCIIString();
|
||||
extraResourceBaseString = extraResourceBaseString.substring(0, extraResourceBaseString.length() - "/one".length());
|
||||
|
||||
ServletHolder defholder = context.addServlet(DefaultServlet.class, "/extra/*");
|
||||
defholder.setInitParameter("resourceBase", extraResourceBaseString);
|
||||
defholder.setInitParameter("pathInfoOnly", "true");
|
||||
defholder.setInitParameter("dirAllowed", "true");
|
||||
defholder.setInitParameter("redirectWelcome", "false");
|
||||
defholder.setInitParameter("gzip", "false");
|
||||
|
||||
StringBuffer req1;
|
||||
String response;
|
||||
|
||||
// Test that GET works first.
|
||||
req1 = new StringBuffer();
|
||||
req1.append("GET /context/extra/one HTTP/1.0\n");
|
||||
req1.append("\n");
|
||||
|
||||
response = connector.getResponses(req1.toString());
|
||||
assertResponseContains("200 OK", response);
|
||||
assertResponseContains("is this the one?", response);
|
||||
|
||||
// Typical directory listing of location in jar:file:// URL
|
||||
req1 = new StringBuffer();
|
||||
req1.append("GET /context/extra/deep/ HTTP/1.0\r\n");
|
||||
req1.append("\r\n");
|
||||
|
||||
response = connector.getResponses(req1.toString());
|
||||
assertResponseContains("200 OK", response);
|
||||
assertResponseContains("/xxx", response);
|
||||
assertResponseContains("/yyy", response);
|
||||
assertResponseContains("/zzz", response);
|
||||
|
||||
assertResponseNotContains(extraResourceBaseString, response);
|
||||
assertResponseNotContains(ODD_JAR, response);
|
||||
|
||||
// Get deep resource
|
||||
req1 = new StringBuffer();
|
||||
req1.append("GET /context/extra/deep/yyy HTTP/1.0\r\n");
|
||||
req1.append("\r\n");
|
||||
|
||||
response = connector.getResponses(req1.toString());
|
||||
assertResponseContains("200 OK", response);
|
||||
assertResponseContains("a file named yyy", response);
|
||||
|
||||
// Convoluted directory listing of location in jar:file:// URL
|
||||
// This exists to test proper encoding output
|
||||
req1 = new StringBuffer();
|
||||
req1.append("GET /context/extra/oddities/ HTTP/1.0\r\n");
|
||||
req1.append("\r\n");
|
||||
|
||||
response = connector.getResponses(req1.toString());
|
||||
assertResponseContains("200 OK", response);
|
||||
assertResponseContains(">#hashcode <", response); // text on page
|
||||
assertResponseContains("/oddities/%23hashcode", response); // generated link
|
||||
|
||||
assertResponseContains(">other%2fkind%2Fof%2fslash <", response); // text on page
|
||||
assertResponseContains("/oddities/other%252fkind%252Fof%252fslash", response); // generated link
|
||||
|
||||
assertResponseContains(">a file with a space <", response); // text on page
|
||||
assertResponseContains("/oddities/a%20file%20with%20a%20space", response); // generated link
|
||||
|
||||
assertResponseNotContains(extraResourceBaseString, response);
|
||||
assertResponseNotContains(ODD_JAR, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListingProperUrlEncoding() throws Exception
|
||||
{
|
||||
|
@ -603,7 +732,7 @@ public class DefaultServletTest
|
|||
|
||||
response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"\r\n");
|
||||
assertResponseContains("200 OK", response);
|
||||
assertResponseContains("Accept-Ranges: bytes", response);
|
||||
|
@ -622,7 +751,7 @@ public class DefaultServletTest
|
|||
|
||||
response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Range: bytes=0-9,20-29,40-49\r\n" +
|
||||
"\r\n");
|
||||
start = response.indexOf("--jetty");
|
||||
|
@ -639,7 +768,7 @@ public class DefaultServletTest
|
|||
|
||||
response = connector.getResponses("GET /context/nofilesuffix HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Connection: close\r\n"+
|
||||
"Connection: close\r\n"+
|
||||
"Range: bytes=0-9,20-29,40-49,60-60,70-79\r\n" +
|
||||
"\r\n");
|
||||
start = response.indexOf("--jetty");
|
||||
|
@ -722,13 +851,13 @@ public class DefaultServletTest
|
|||
assertResponseContains("Hello Text 0",response);
|
||||
assertResponseContains("Vary: Accept-Encoding",response);
|
||||
assertResponseNotContains("Content-Encoding: gzip",response);
|
||||
|
||||
|
||||
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
|
||||
assertResponseContains("Content-Length: 9", response);
|
||||
assertResponseContains("fake gzip",response);
|
||||
assertResponseContains("Vary: Accept-Encoding",response);
|
||||
assertResponseContains("Content-Encoding: gzip",response);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -49,7 +49,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
/**
|
||||
* Abstract resource class.
|
||||
* <p>
|
||||
* This class provides a resource abstraction, where a resource may be
|
||||
|
@ -78,7 +78,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
return __defaultUseCaches;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a resource from a uri.
|
||||
* @param uri A URI.
|
||||
|
@ -90,7 +90,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
return newResource(uri.toURL());
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a resource from a url.
|
||||
* @param url A URL.
|
||||
|
@ -100,8 +100,8 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
return newResource(url, __defaultUseCaches);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Construct a resource from a url.
|
||||
* @param url the url for which to make the resource
|
||||
|
@ -140,8 +140,8 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
return new URLResource(url,null,useCaches);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a resource from a string.
|
||||
* @param resource A URL or filename.
|
||||
|
@ -153,7 +153,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
return newResource(resource, __defaultUseCaches);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a resource from a string.
|
||||
* @param resource A URL or filename.
|
||||
|
@ -161,7 +161,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
* @return A Resource object.
|
||||
* @throws MalformedURLException Problem accessing URI
|
||||
*/
|
||||
public static Resource newResource(String resource, boolean useCaches)
|
||||
public static Resource newResource(String resource, boolean useCaches)
|
||||
throws MalformedURLException
|
||||
{
|
||||
URL url=null;
|
||||
|
@ -181,7 +181,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
// It's a file.
|
||||
if (resource.startsWith("./"))
|
||||
resource=resource.substring(2);
|
||||
|
||||
|
||||
File file=new File(resource).getCanonicalFile();
|
||||
return new FileResource(file);
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
/** Construct a system resource from a string.
|
||||
* The resource is tried as classloader resource before being
|
||||
* treated as a normal resource.
|
||||
* @param resource Resource as string representation
|
||||
* @param resource Resource as string representation
|
||||
* @return The new Resource
|
||||
* @throws IOException Problem accessing resource.
|
||||
*/
|
||||
|
@ -247,17 +247,17 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
url=loader.getResource(resource.substring(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (url==null)
|
||||
{
|
||||
url=ClassLoader.getSystemResource(resource);
|
||||
if (url==null && resource.startsWith("/"))
|
||||
url=ClassLoader.getSystemResource(resource.substring(1));
|
||||
}
|
||||
|
||||
|
||||
if (url==null)
|
||||
return null;
|
||||
|
||||
|
||||
return newResource(url);
|
||||
}
|
||||
|
||||
|
@ -277,21 +277,21 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
* Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
|
||||
* @param name The relative name of the resource
|
||||
* @param useCaches True if URL caches are to be used.
|
||||
* @param checkParents True if forced searching of parent Classloaders is performed to work around
|
||||
* @param checkParents True if forced searching of parent Classloaders is performed to work around
|
||||
* loaders with inverted priorities
|
||||
* @return Resource or null
|
||||
*/
|
||||
public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
|
||||
{
|
||||
URL url=Resource.class.getResource(name);
|
||||
|
||||
|
||||
if (url==null)
|
||||
url=Loader.getResource(Resource.class,name);
|
||||
if (url==null)
|
||||
return null;
|
||||
return newResource(url,useCaches);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
|
||||
{
|
||||
|
@ -304,11 +304,11 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Release any temporary resources held by the resource.
|
||||
* @deprecated use {@link #close()}
|
||||
|
@ -329,7 +329,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
* Returns true if the respresened resource exists.
|
||||
*/
|
||||
public abstract boolean exists();
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -351,7 +351,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
* Return the length of the resource
|
||||
*/
|
||||
public abstract long length();
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -375,7 +375,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -384,14 +384,14 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
*/
|
||||
public abstract File getFile()
|
||||
throws IOException;
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns the name of the resource
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -414,7 +414,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
// TODO: can throw IOException
|
||||
public abstract boolean delete()
|
||||
throws SecurityException;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Rename the given resource
|
||||
|
@ -422,7 +422,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
// TODO: can throw IOException
|
||||
public abstract boolean renameTo( Resource dest)
|
||||
throws SecurityException;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns a list of resource names contained in the given resource
|
||||
|
@ -462,14 +462,16 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @deprecated
|
||||
/**
|
||||
* @param uri the uri to encode
|
||||
* @return null (this is deprecated)
|
||||
* @deprecated use {@link URIUtil} or {@link UrlEncoded} instead
|
||||
*/
|
||||
public String encode(String uri)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public Object getAssociate()
|
||||
{
|
||||
|
@ -481,7 +483,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
_associate=o;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return The canonical Alias of this resource or null if none.
|
||||
|
@ -490,13 +492,16 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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
|
||||
* @throws IOException if unable to get the list of resources as HTML
|
||||
* @deprecated use {@link #getListHTML(String, boolean, String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public String getListHTML(String base, boolean parent) throws IOException
|
||||
{
|
||||
return getListHTML(base, parent, null);
|
||||
|
@ -668,7 +673,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
buf.append("<tbody>\n");
|
||||
|
||||
String encodedBase = hrefEncodeURI(base);
|
||||
|
||||
|
||||
if (parent)
|
||||
{
|
||||
// Name
|
||||
|
@ -686,12 +691,12 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
DateFormat.MEDIUM);
|
||||
for (Resource item: items)
|
||||
{
|
||||
String name = item.getName();
|
||||
int slashIdx = name.lastIndexOf('/');
|
||||
if (slashIdx != -1)
|
||||
String name = item.getFileName();
|
||||
if (StringUtil.isBlank(name))
|
||||
{
|
||||
name = name.substring(slashIdx + 1);
|
||||
continue; // skip
|
||||
}
|
||||
|
||||
if (item.isDirectory() && !name.endsWith("/"))
|
||||
{
|
||||
name += URIUtil.SLASH;
|
||||
|
@ -708,13 +713,21 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
|
||||
// Last Modified
|
||||
buf.append("<td class=\"lastmodified\">");
|
||||
buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
buf.append("</td>");
|
||||
long lastModified = item.lastModified();
|
||||
if (lastModified > 0)
|
||||
{
|
||||
buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
}
|
||||
buf.append(" </td>");
|
||||
|
||||
// Size
|
||||
buf.append("<td class=\"size\">");
|
||||
buf.append(String.format("%,d", item.length()));
|
||||
buf.append(" bytes </td></tr>\n");
|
||||
long length = item.length();
|
||||
if (length >= 0)
|
||||
{
|
||||
buf.append(String.format("%,d bytes", item.length()));
|
||||
}
|
||||
buf.append(" </td></tr>\n");
|
||||
}
|
||||
buf.append("</tbody>\n");
|
||||
buf.append("</table>\n");
|
||||
|
@ -722,7 +735,56 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the raw (decoded if possible) Filename for this Resource.
|
||||
* This is the last segment of the path.
|
||||
* @return the raw / decoded filename for this resource
|
||||
*/
|
||||
private String getFileName()
|
||||
{
|
||||
try
|
||||
{
|
||||
// if a Resource supports File
|
||||
File file = getFile();
|
||||
if (file != null)
|
||||
{
|
||||
return file.getName();
|
||||
}
|
||||
}
|
||||
catch (Throwable ignore)
|
||||
{
|
||||
}
|
||||
|
||||
// All others use raw getName
|
||||
try
|
||||
{
|
||||
String rawName = getName(); // gets long name "/foo/bar/xxx"
|
||||
int idx = rawName.lastIndexOf('/');
|
||||
if (idx == rawName.length()-1)
|
||||
{
|
||||
// hit a tail slash, aka a name for a directory "/foo/bar/"
|
||||
idx = rawName.lastIndexOf('/', idx-1);
|
||||
}
|
||||
|
||||
String encodedFileName;
|
||||
if (idx >= 0)
|
||||
{
|
||||
encodedFileName = rawName.substring(idx + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
encodedFileName = rawName; // entire name
|
||||
}
|
||||
return UrlEncoded.decodeString(encodedFileName, 0, encodedFileName.length(), UTF_8);
|
||||
}
|
||||
catch (Throwable ignore)
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
|
@ -787,7 +849,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param out
|
||||
* @param out
|
||||
* @param start First byte to write
|
||||
* @param count Bytes to write or -1 for all of them.
|
||||
*/
|
||||
|
@ -873,7 +935,7 @@ public abstract class Resource implements ResourceFactory, Closeable
|
|||
/** Generate a properly encoded URL from a {@link File} instance.
|
||||
* @param file Target file.
|
||||
* @return URL of the target file.
|
||||
* @throws MalformedURLException
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
public static URL toURL(File file) throws MalformedURLException
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue