Content disposition duplicate headers solution for google chrome

Recently I had a problem while serving dynamic content in a Spring MVC application; (more details here) when attempting to open some of the dynamic content with Chrome I was having a weird error message :

Duplicate headers received from server The response from the server contained duplicate headers. This problem is generally the result of a misconfigured website or proxy. Only the website or proxy administrator can fix this issue. Error 349 (net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION): Multiple distinct Content-Disposition headers received. This is disallowed to protect against HTTP response splitting attacks.

I wasn't sure why I was having this problem, since some dynamic content was working properly and I was setting only once the Content-Disposition header on my code

So after searching around a bit I stumbled upon the HTTP specs it turns out the Content-Disposition header should not contain a coma since it will be treated as a header separator

Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.

Personally I decided to create slugs for all my file names using the Slugify library :



    com.github.slugify
    slugify
    2.1.3


  public String slugify(String originalFileName){

      String extension = FilenameUtils.getExtension(originalFileName);
      return new Slugify(true).slugify(FilenameUtils.removeExtension(originalFileName)) +"."+extension;;

  }

Spring mvc send binary content from controller

Sometimes binary files such as images, documents are stored in the database; these binary files need then to be served dynamically

Below I'll show a code snippet showing how to handle this easily with Spring MVC

Before we begin just in case this is the list of libraries I used when coding this example :

  • Spring 3+
  • Spring Data JPA
  • Eclipselink 2.4+
  • Apache Tika
  • Slugify


@Entity
public class Resouce{

    @Id @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "FILE_NAME", length = 200)
    private String fileName;


   @Column(name = "FILE")
    @Lob
    private byte[] file;

   //SETTERS - GETTERS OMMITED
}


@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

}


/**
 * @author ulf
 */
@Controller
@RequestMapping("/resource")
public class ResourceController{

    // a basic Spring data repository
    @Autowired
    private ResourceRepostiory resourceRepository;

    
    // simple tika instance for handling mimetype resolution
    @Autowired
    private Tika tika;

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity resource(@RequestParam("resourceId") Long resourceId) throws IOException {

        byte[] binary = null;
        String fileName = "";


            Resouce resource= resourceRepository.findOne(resourceId);

            if (resource!= null  && section.getFile() !=null ) {
                binary = section.getFile();
                fileName = section.getFileName();
            }


        if (binary == null) {
            throw new ResourceNotFoundException();
        } else {

            String extension = FilenameUtils.getExtension(fileName);

            // slugify the fileName to handle the Google Chrome error complaining of duplicate content disposition headers whenever the file name contains a ,
            fileName = new Slugify(true).slugify(FilenameUtils.removeExtension(fileName)) +"."+extension;

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.parseMediaType(mimetype(fileName)));
            headers.setContentDispositionFormData(fileName, fileName);
            headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
            ResponseEntity responseEntity = new ResponseEntity(binary, headers, HttpStatus.OK);
            return responseEntity;


        }


    }

    private String mimetype(String fileName) {
        return ConfigurableMimeFileTypeMap.getDefaultFileTypeMap().getContentType(fileName);
    }


}


And that's it you should now be able to serve resources dynamically based on their id