14

I'm currently implementing functionality for uploading files using jersey rest. I would like to set a maximum allowed file size which to me seems like a pretty common requirement.

My first approach was to use Jerseys FormDataContentDisposition which should hold all the information I could possibly need about the file. But all information except the file name seems to be missing, including file size.

This is my rest method:

@POST
@Path("uploadFile/")
@Consumes("multipart/form-data")
@Produces("text/html")
public String handleDocumentUpload(
    @FormDataParam("file") InputStream uploadedInputStream,
    @FormDataParam("file") FormDataContentDisposition fileDetail)
{
    if(fileDetail.getSize() > MAX_FILE_SIZE)
    {
        throw new IllegalArgumentException(
                "File is to big! Max size is " + MAX_FILE_SIZE);
    }
    // ...more file handling logic
}

Which isn't working since the returned size is always "-1"!

I use a extremely simple html form for the file upload:

<html>
  <head>
    <title>File upload</title>
  </head>
  <body>
 <p>Choose file</p>
 <form enctype="multipart/form-data" method="POST" action="uploadFile">
   <input type="file" name="file" size="50">
   <input type="submit" value="Upload">
 </form>
  </body>
</html>

So now to my question; how would you enforce a file size restriction using jersey? There must be some simple way without having to resort to reading the whole file into memory (ByteArray) and then get the actuall size, right?

mattias_avelin
  • 143
  • 1
  • 1
  • 6
  • Just to clarify, aside from not being able to restrict the file size it works like a charm. – mattias_avelin Nov 11 '11 at 08:34
  • Currently for jersey 2, following post solves the issue https://stackoverflow.com/questions/6301973/multipart-file-upload-on-google-appengine-using-jersey-1-7/25848569#25848569 – Indraneel Aug 02 '16 at 03:43

5 Answers5

8

If the client does not send the file size, fall back to reading the file from stream. Once you hit the size limit, stop reading and refuse the file. You should do this anyway, since you can't trust the client (anyone can create an app that sends http requests to your service and those requests may not have correct data you expect - so have to take that into account).

In addition, it may be possible to add some validation to the web form as well to fail fast, but I am not a JavaScript expert, so not sure if/how that can be done.

Martin Matula
  • 7,969
  • 1
  • 31
  • 35
  • Thank you Martin! You are of course right in that I should not count on the client giving me the correct file size. We implemented it so that if the file size stated by the client > MAX_SIZE then we "fail fast" otherwise we read the stream up until we reach MAX_SIZE. We found no simple way of validating the file size on the client side so this will have to do. Thanks again for your input! – mattias_avelin Nov 22 '11 at 06:48
  • To prevent your API from receiving undesired HTTP calls, you could create a CORS filter. – joninx Mar 30 '17 at 14:32
6

If you're using tomcat you can set the the size threshold at which the file will be written to the disk to a sensible value for your machine.

e.g. if the servlet is in web.xml

<servlet>
  <servlet-name>Upload Servlet</servlet-name>
  <servlet-class>YourServletName</servlet-class>

  <multipart-config>
   <!-- 10MB of files -->
   <max-file-size>10485760</max-file-size>
   <!-- 10KB of form data -->
   <max-request-size>10240</max-request-size>
   <!-- Buffer to disk over 512KB -->
   <file-size-threshold>524288</file-size-threshold>
 </multipart-config>

</servlet>

or using annotations:

@MultipartConfig(
    maxFileSize=10485760,     // 10Mb max
    fileSizeThreshold=524288, //512 Kb before buffering to disk
    maxRequestSize=10240      // 10Kb of meta data
    location=/tmp             // with permission to write, default uses tomcat tmp
)

With reference to HttpRequest maximum allowable size in tomcat?

byeo
  • 646
  • 7
  • 17
  • Following https://docs.oracle.com/javaee/7/tutorial/servlets011.htm I would say the max-file-size is just for one file and max-request-size is for the whole request (so potentially multiple files and multiparts). – Ondrej Burkert Oct 19 '17 at 15:12
  • 1
    MultipartConfig is not used by JAX-RS/Jersey as it's for Servlets and not JAX-RS resources. There is no equivalent for MultipartConfig in Jersey. Please refer to https://jersey.github.io/documentation/latest/media.html#multipart for details. There's no fail-fast solution. One can't rely on content-length header. We've to inspect file's size & reject it when it's greater than a pre-set threshold. why is that terrible? Imagine a massive file being uploaded. Jersey has to persist entire file on disk first; which is not only time consuming but also unsafe if server's disk space is limited. – Viswanath Mar 28 '18 at 21:21
3

You can check the length, in bytes, of the request body and made available by the input stream with the following code:

public Response uploadFile(@Context final HttpServletRequest request, @FormDataParam("uploadFile") InputStream uploadedInputStream,
      @FormDataParam("uploadFile") FormDataContentDisposition fileDetail, @FormDataParam("uploadFile") FormDataBodyPart body) {

The key part being @Context final HttpServletRequest request. Then in the method body, you can get the length of the inputstream and react to it accordingly with:

int contentLength = request.getContentLength();

if (contentLength == -1 || contentLength > MAX_REQUEST_SIZE) {
  // deal with it
}
herrtim
  • 2,697
  • 1
  • 26
  • 36
  • In that case, let say you wanted to restrict file size above `1gb`, so `your validation comes in picture once we upload whole file`. So it's really slow and not acceptable. Have you think about this case? – Jayesh Dhandha Dec 08 '20 at 12:44
1

You can get the request size by reading a header. In your example:

@POST
@Path("uploadFile/")
@Consumes("multipart/form-data")
@Produces("text/html")
public String handleDocumentUpload(
    @HeaderParam("content-length") long contentLength,
    @FormDataParam("file") InputStream uploadedInputStream,
    @FormDataParam("file") FormDataContentDisposition fileDetail) {

    if(contentLength > MAX_FILE_SIZE) {
      throw new IllegalArgumentException(
            "File is to big! Max size is " + MAX_FILE_SIZE);
    }
  // ...more file handling logic
}
senior_dev
  • 23
  • 1
  • 6
0

You can have your custom class LimitedSizeInputStream extends InputStream with @Override read methods which check for specific size limit as mentioned on https://stackoverflow.com/a/30072143/5962766. Wrap your InputStream with new LimitedSizeInputStream(fileStream, FILE_SIZE_LIMIT) and you'll get exception when read limit is reached.

Community
  • 1
  • 1
Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114