In web.xml, map directory listing URL to spring dispatcher:
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/download/*</url-pattern>
</servlet-mapping>
The first one is your existing dispatcher and the second one for download.
In spring security configuration set up appropriate configuration:
<intercept-url pattern="/download/**" access="permitAll" />
In Spring servlet config xml set this up to make path configurable:
<bean id="appPropertiesBean" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="singleton" value="true" />
<property name="properties">
<props>
<prop key="downloadPath">C:/</prop>
</props>
</property>
</bean>
In your controller for the path property:
@Value("#{appPropertiesBean.downloadPath}")
private File downloadPath;
In controller add methods to generate directory listing:
@RequestMapping(value = "/files/**", method = RequestMethod.GET)
public ModelAndView archiveDirectoryListing(HttpServletRequest request, HttpServletResponse response) {
try {
//String restOfTheUrl=(String)request.getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE );
//The above line seems to cause issues with filenames
// containing semicolon. Use the below line instead
String restOfTheUrl=request.getPathInfo();
restOfTheUrl=restOfTheUrl.substring("/files".length());
File physicalFileRequested=new File(downloadPath,restOfTheUrl);
System.out.println("Accessing .." + physicalFileRequested);
if (!physicalFileRequested.getCanonicalPath().startsWith(archivePath.getCanonicalPath())) { //Requester is trying to go above the archive directory
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
if (!physicalFileRequested.exists()) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
System.out.println("Access allowed " + physicalFileRequested);
System.out.println("Access allowed " + physicalFileRequested.isFile());
System.out.println("Access allowed " + physicalFileRequested.isDirectory());
System.out.println("Rest of url : " + restOfTheUrl);
if (physicalFileRequested.isFile()) {
System.out.println("Accessing file:" + physicalFileRequested);
streamFile(physicalFileRequested,response);
} else if (physicalFileRequested.isDirectory()){
if (! restOfTheUrl.endsWith("/")) {
response.sendRedirect(request.getContextPath() + "/download/files" + restOfTheUrl + "/");
}
System.out.println("Accessing dir:" + physicalFileRequested);
generateDirectoryListing(physicalFileRequested,response);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error accessing requested file/directory.");
}
}
private void streamFile(File physicalFileRequested, HttpServletResponse response) {
try {
response.setContentType("application/pdf");
// get your file as InputStream
InputStream is = new FileInputStream(physicalFileRequested);
// copy it to response's OutputStream
IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
throw new RuntimeException("Error accessing requested file/directory.");
}
}
private void generateDirectoryListing(File physicalFileRequested, HttpServletResponse response) {
try {
StringBuilder sb=new StringBuilder();
sb.append("<html>");
sb.append("<body>");
sb.append("<a href='..'>..</a><br>");
File [] childFiles=physicalFileRequested.listFiles();
for (File childFile:childFiles) {
String relativePath = childFile.getName();
sb.append("<a href='"+ URLEncoder.encode(relativePath,"UTF-8") + "'>" +relativePath+"</a><br>");
}
sb.append("</body>");
sb.append("</html>");
response.getWriter().print(sb.toString());
} catch (Exception e) {
throw new RuntimeException("Error accessing requested file/directory.");
}
}
Download URL will be like:
http://localhost:9090/context/download/files/
Alternatively, the directory listing HTML could be generated using a view, by using the following alternate implementation of the method (Note: wherever null is returned for ModelAndView, Spring assumes that response has already been handled and no view redirection needs to happen):
@RequestMapping(value = "/files/**", method = RequestMethod.GET)
public ModelAndView archiveDirectoryListing(HttpServletRequest request, HttpServletResponse response) {
try {
String restOfTheUrl=(String)request.getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE );
restOfTheUrl=restOfTheUrl.substring("/files".length());
File physicalFileRequested=new File(archivePath,restOfTheUrl);
System.out.println("Accessing .." + physicalFileRequested);
if (!physicalFileRequested.getCanonicalPath().startsWith(archivePath.getCanonicalPath())) { //Requester is trying to go above the archive directory
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return null;
}
if (!physicalFileRequested.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return null;
}
if (physicalFileRequested.isFile()) {
streamFile(physicalFileRequested,response);
return null;
} else if (physicalFileRequested.isDirectory()){
if (! restOfTheUrl.endsWith("/")) {
response.sendRedirect(request.getContextPath() + "/archive/files" + restOfTheUrl + "/");
}
System.out.println("Accessing dir:" + physicalFileRequested);
ModelAndView modelAndView=new ModelAndView("direcoryListing");
modelAndView.addObject("fileList", physicalFileRequested.listFiles());
return modelAndView;
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error accessing requested file/directory.");
}
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return null;
}
And a freemarker view, direcoryListing.ftl:
<html>
<body>
<h1>Welcome to the download page</h1>
<a href='..'>..</a><br>
<#list fileList as file>
<a href='${file.name}'> ${file.name}</a><br>
</#list>
</body>
</html>
Configure freemarker in application context:
<!-- freemarker config -->
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/pages/" />
</bean>
<!-- View resolvers can also be configured with ResourceBundles or XML files.
If you need different view resolving based on Locale, you have to use the
resource bundle resolver. -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
</bean>