Thursday, April 11, 2013

File download page using Spring


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>

No comments:

Post a Comment