diff --git a/.gitignore b/.gitignore index a82616b..f4ef8da 100644 --- a/.gitignore +++ b/.gitignore @@ -165,5 +165,6 @@ buildNumber.properties # Ignore all local history of files .history .ionide +.vscode # End of https://www.toptal.com/developers/gitignore/api/java,maven,intellij,visualstudiocode \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..760614f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-added-large-files + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/dustinsand/pre-commit-jvm + rev: v0.8.0 + hooks: + - id: google-java-formatter-jdk11 + args: [--replace, --set-exit-if-changed] diff --git a/.travis.yml b/.travis.yml index a79b9b9..c71fcc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,15 +2,14 @@ language: java sudo: false # faster builds jdk: -# Latest -- openjdk16 # LTS versions -- openjdk11 - openjdk8 +- openjdk11 +- openjdk17 addons: sonarcloud: organization: "tomdesair-github" script: - - mvn clean install -P checkstyle + - mvn clean install -P codestyle diff --git a/README.md b/README.md index 3c33d26..a2480c8 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ # tus-java-server This library can be used to enable resumable (and potentially asynchronous) file uploads in any Java web application. This allows the users of your application to upload large files over slow and unreliable internet connections. The ability to pause or resume a file upload (after a connection loss or reset) is achieved by implementing the open file upload protocol tus (https://tus.io/). This library implements the server-side of the tus v1.0.0 protocol with [all optional extensions](#tus-protocol-extensions). -The Javadoc of this library can be found at https://tus.desair.me/. The following Java versions are supported: 8, -9, 10, 11 and 17. +The Javadoc of this library can be found at https://tus.desair.me/. The following Java LTS versions are supported: 8, 11 and 17. ## Quick Start and Examples The tus-java-server library only depends on Jakarta Servlet API 6.0 and some Apache Commons utility libraries. This @@ -79,4 +78,24 @@ This tus protocol implementation has been [tested](https://github.com/tomdesair/ This artifact is versioned as `A.B.C-X.Y` where `A.B.C` is the version of the implemented tus protocol (currently 1.0.0) and `X.Y` is the version of this library. ## Contributing -This library comes without any warranty and is released under a [MIT license](https://github.com/tomdesair/tus-java-server/blob/master/LICENSE). If you encounter any bugs or if you have an idea for a useful improvement you are welcome to [open a new issue](https://github.com/tomdesair/tus-java-server/issues) or to [create a pull request](https://github.com/tomdesair/tus-java-server/pulls) with the proposed implementation. Please note that any contributed code needs to be accompanied by automated unit and/or integration tests and comply with the [defined code-style](https://github.com/tomdesair/tus-java-server/blob/master/checkstyle.xml). +This library comes without any warranty and is released under a [MIT license](https://github.com/tomdesair/tus-java-server/blob/master/LICENSE). If you encounter any bugs or if you have an idea for a useful improvement you are welcome to [open a new issue](https://github.com/tomdesair/tus-java-server/issues) or to [create a pull request](https://github.com/tomdesair/tus-java-server/pulls) with the proposed implementation. Please note that any contributed code needs to be accompanied by automated unit and/or integration tests and comply with the [defined code-style](#code-style). + +### Code Style +All pull requests should have the correct formatting according to [Google Java Style](https://github.com/google/google-java-format) code formatting. To verify if the code style is correct run: + +``` +mvn -P codestyle com.spotify.fmt:fmt-maven-plugin:check +``` + +To reformat your code run: + +``` +mvn -P codestyle com.spotify.fmt:fmt-maven-plugin:format +``` + +See the [Google Java Style Github page](https://github.com/google/google-java-format) on recommendations on how to configure this in your IDE. Or if you have Python 3, you can also use [pre-commit](https://pre-commit.com) to make your live easier: + +``` +pip install pre-commit +pre-commit install +``` diff --git a/checkstyle.xml b/checkstyle.xml index d03e94a..539fe4d 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,239 +1,385 @@ - - - + - + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + - - - - - - + - + + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)" /> + value="Consider using special escape sequence instead of octal value or Unicode escaped value." /> - - - - - - - - - + + + + + + + + + - + + + + + + + + value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF, + INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT, + LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF, + OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF" /> - - - + + value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, + LITERAL_DO" /> - - + + + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF, LITERAL_SWITCH" /> + + + + + + + + - - - - + + + + + + + + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks + may only be represented as '{}' when not part of a multi-block statement (4.1.3)" /> - - - - - - - - + value="WhitespaceAround: ''{0}'' is not preceded with whitespace." /> + + + + + + + + - + + - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - + + + - + + value="Package name ''{0}'' must match pattern ''{1}''." /> + + value="Type name ''{0}'' must match pattern ''{1}''." /> - + + value="Member name ''{0}'' must match pattern ''{1}''." /> - + + + + + + value="Lambda parameter name ''{0}'' must match pattern ''{1}''." /> - + + value="Catch parameter name ''{0}'' must match pattern ''{1}''." /> - - + + + + + + value="Pattern variable name ''{0}'' must match pattern ''{1}''." /> - + + value="Class type name ''{0}'' must match pattern ''{1}''." /> + + + + + + + + - + + value="Method type name ''{0}'' must match pattern ''{1}''." /> - + + value="Interface type name ''{0}'' must match pattern ''{1}''." /> - + + value="GenericWhitespace ''{0}'' is followed by whitespace." /> + value="GenericWhitespace ''{0}'' is preceded with whitespace." /> + value="GenericWhitespace ''{0}'' should followed by whitespace." /> + value="GenericWhitespace ''{0}'' is not preceded with whitespace." /> - - - - - - + + + + + + - - + + + - + + + - - - + + + + + + + - - + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, + LABELED_STAT, METHOD_REF" /> + - - - + + value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF, + EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, + METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA, + RECORD_DEF" /> + + + + - - - + + - - - + + + + + + + + + + + + + - + + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF" /> - + + + + + + + + + + + + + + + + + - + - - - + value="Method name ''{0}'' must match pattern ''{1}''." /> + - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - diff --git a/pom.xml b/pom.xml index ea83294..32df8be 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 me.desair.tus @@ -274,7 +275,8 @@ sonar - ${project.build.directory}/coverage-reports/jacoco-it.exec,${project.build.directory}/coverage-reports/jacoco-ut.exec + + ${project.build.directory}/coverage-reports/jacoco-it.exec,${project.build.directory}/coverage-reports/jacoco-ut.exec @@ -313,8 +315,10 @@ false - ${project.reporting.outputDirectory}/jacoco-ut/jacoco.xml - ${project.reporting.outputDirectory}/jacoco-it/jacoco.xml + + ${project.reporting.outputDirectory}/jacoco-ut/jacoco.xml + + ${project.reporting.outputDirectory}/jacoco-it/jacoco.xml @@ -325,38 +329,27 @@ - checkstyle + codestyle - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.2 + com.spotify.fmt + fmt-maven-plugin + 2.20 + + + verify-style - + verify check - - ${project.basedir}/checkstyle.xml - ${project.build.sourceEncoding} - true - true - - true - - - - com.puppycrawl.tools - checkstyle - 8.44 - - @@ -466,4 +459,4 @@ https://github.com/tomdesair/tus-java-server/tree/master - + \ No newline at end of file diff --git a/src/main/java/me/desair/tus/server/HttpHeader.java b/src/main/java/me/desair/tus/server/HttpHeader.java index 48a8dce..951708b 100644 --- a/src/main/java/me/desair/tus/server/HttpHeader.java +++ b/src/main/java/me/desair/tus/server/HttpHeader.java @@ -1,104 +1,107 @@ package me.desair.tus.server; -/** - * Class that will hold constants for all HTTP headers relevant to the tus v1.0.0 protocol - */ +/** Class that will hold constants for all HTTP headers relevant to the tus v1.0.0 protocol */ public class HttpHeader { - /** - * The X-HTTP-Method-Override request header MUST be a string which MUST be interpreted as the request’s method - * by the Server, if the header is presented. The actual method of the request MUST be ignored. - * The Client SHOULD use this header if its environment does not support the PATCH or DELETE methods. - */ - public static final String METHOD_OVERRIDE = "X-HTTP-Method-Override"; - - public static final String CACHE_CONTROL = "Cache-Control"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_DISPOSITION = "Content-Disposition"; - public static final String LOCATION = "Location"; - - /** - * The Transfer-Encoding header specifies the form of encoding used to safely transfer the entity to the user. - */ - public static final String TRANSFER_ENCODING = "Transfer-Encoding"; - - /** - * The Upload-Offset request and response header indicates a byte offset within a resource. - * The value MUST be a non-negative integer. - */ - public static final String UPLOAD_OFFSET = "Upload-Offset"; - public static final String UPLOAD_METADATA = "Upload-Metadata"; - - /** - * The Upload-Checksum request header contains information about the checksum of the current body payload. - * The header MUST consist of the name of the used checksum algorithm and the Base64 encoded checksum - * separated by a space. - */ - public static final String UPLOAD_CHECKSUM = "Upload-Checksum"; - - /** - * The Upload-Length request and response header indicates the size of the entire upload in bytes. - * The value MUST be a non-negative integer. - */ - public static final String UPLOAD_LENGTH = "Upload-Length"; - - /** - * The Upload-Expires response header indicates the time after which the unfinished upload expires. - * The value of the Upload-Expires header MUST be in RFC 7231 (https://tools.ietf.org/html/rfc7231#section-7.1.1.1) - * datetime format. - */ - public static final String UPLOAD_EXPIRES = "Upload-Expires"; - - /** - * The Upload-Defer-Length request and response header indicates that the size of the upload is not known - * currently and will be transferred later. Its value MUST be 1. If the length of an upload is not deferred, - * this header MUST be omitted. - */ - public static final String UPLOAD_DEFER_LENGTH = "Upload-Defer-Length"; - - /** - * The Upload-Concat request and response header MUST be set in both partial and upload creation requests. - * It indicates whether the upload is either a partial or upload. - */ - public static final String UPLOAD_CONCAT = "Upload-Concat"; - - /** - * The Tus-Version response header MUST be a comma-separated list of protocol versions supported by the Server. - * The list MUST be sorted by Server’s preference where the first one is the most preferred one. - */ - public static final String TUS_VERSION = "Tus-Version"; - - /** - * The Tus-Resumable header MUST be included in every request and response except for OPTIONS requests. - * The value MUST be the version of the protocol used by the Client or the Server. - */ - public static final String TUS_RESUMABLE = "Tus-Resumable"; - - /** - * The Tus-Extension response header MUST be a comma-separated list of the extensions supported by the Server. - * If no extensions are supported, the Tus-Extension header MUST be omitted. - */ - public static final String TUS_EXTENSION = "Tus-Extension"; - - /** - * The Tus-Max-Size response header MUST be a non-negative integer indicating the maximum allowed size of an - * entire upload in bytes. The Server SHOULD set this header if there is a known hard limit. - */ - public static final String TUS_MAX_SIZE = "Tus-Max-Size"; - - /** - * The Tus-Checksum-Algorithm response header MUST be a comma-separated list of the checksum algorithms supported - * by the server. - */ - public static final String TUS_CHECKSUM_ALGORITHM = "Tus-Checksum-Algorithm"; - - /** - * The X-Forwarded-For (XFF) HTTP header field is a common method for identifying the originating IP address of a - * client connecting to a web server through an HTTP proxy or load balancer. - */ - public static final String X_FORWARDED_FOR = "X-Forwarded-For"; - - private HttpHeader() { - //This is an utility class to hold constants - } + /** + * The X-HTTP-Method-Override request header MUST be a string which MUST be interpreted as the + * request’s method by the Server, if the header is presented. The actual method of the request + * MUST be ignored. The Client SHOULD use this header if its environment does not support the + * PATCH or DELETE methods. + */ + public static final String METHOD_OVERRIDE = "X-HTTP-Method-Override"; + + public static final String CACHE_CONTROL = "Cache-Control"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + public static final String LOCATION = "Location"; + + /** + * The Transfer-Encoding header specifies the form of encoding used to safely transfer the entity + * to the user. + */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + + /** + * The Upload-Offset request and response header indicates a byte offset within a resource. The + * value MUST be a non-negative integer. + */ + public static final String UPLOAD_OFFSET = "Upload-Offset"; + + public static final String UPLOAD_METADATA = "Upload-Metadata"; + + /** + * The Upload-Checksum request header contains information about the checksum of the current body + * payload. The header MUST consist of the name of the used checksum algorithm and the Base64 + * encoded checksum separated by a space. + */ + public static final String UPLOAD_CHECKSUM = "Upload-Checksum"; + + /** + * The Upload-Length request and response header indicates the size of the entire upload in bytes. + * The value MUST be a non-negative integer. + */ + public static final String UPLOAD_LENGTH = "Upload-Length"; + + /** + * The Upload-Expires response header indicates the time after which the unfinished upload + * expires. The value of the Upload-Expires header MUST be in RFC 7231 + * (https://tools.ietf.org/html/rfc7231#section-7.1.1.1) datetime format. + */ + public static final String UPLOAD_EXPIRES = "Upload-Expires"; + + /** + * The Upload-Defer-Length request and response header indicates that the size of the upload is + * not known currently and will be transferred later. Its value MUST be 1. If the length of an + * upload is not deferred, this header MUST be omitted. + */ + public static final String UPLOAD_DEFER_LENGTH = "Upload-Defer-Length"; + + /** + * The Upload-Concat request and response header MUST be set in both partial and upload creation + * requests. It indicates whether the upload is either a partial or upload. + */ + public static final String UPLOAD_CONCAT = "Upload-Concat"; + + /** + * The Tus-Version response header MUST be a comma-separated list of protocol versions supported + * by the Server. The list MUST be sorted by Server’s preference where the first one is the most + * preferred one. + */ + public static final String TUS_VERSION = "Tus-Version"; + + /** + * The Tus-Resumable header MUST be included in every request and response except for OPTIONS + * requests. The value MUST be the version of the protocol used by the Client or the Server. + */ + public static final String TUS_RESUMABLE = "Tus-Resumable"; + + /** + * The Tus-Extension response header MUST be a comma-separated list of the extensions supported by + * the Server. If no extensions are supported, the Tus-Extension header MUST be omitted. + */ + public static final String TUS_EXTENSION = "Tus-Extension"; + + /** + * The Tus-Max-Size response header MUST be a non-negative integer indicating the maximum allowed + * size of an entire upload in bytes. The Server SHOULD set this header if there is a known hard + * limit. + */ + public static final String TUS_MAX_SIZE = "Tus-Max-Size"; + + /** + * The Tus-Checksum-Algorithm response header MUST be a comma-separated list of the checksum + * algorithms supported by the server. + */ + public static final String TUS_CHECKSUM_ALGORITHM = "Tus-Checksum-Algorithm"; + + /** + * The X-Forwarded-For (XFF) HTTP header field is a common method for identifying the originating + * IP address of a client connecting to a web server through an HTTP proxy or load balancer. + */ + public static final String X_FORWARDED_FOR = "X-Forwarded-For"; + + private HttpHeader() { + // This is an utility class to hold constants + } } diff --git a/src/main/java/me/desair/tus/server/HttpMethod.java b/src/main/java/me/desair/tus/server/HttpMethod.java index 223696b..911f9d3 100644 --- a/src/main/java/me/desair/tus/server/HttpMethod.java +++ b/src/main/java/me/desair/tus/server/HttpMethod.java @@ -1,51 +1,48 @@ package me.desair.tus.server; -import java.util.Set; import jakarta.servlet.http.HttpServletRequest; - +import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; /** - * Class that represents a HTTP method. - * The X-HTTP-Method-Override request header MUST be a string which MUST be interpreted as the request’s - * method by the Server, if the header is presented. The actual method of the request MUST be ignored. - * The Client SHOULD use this header if its environment does not support the PATCH or DELETE methods. + * Class that represents a HTTP method. The X-HTTP-Method-Override request header MUST be a string + * which MUST be interpreted as the request’s method by the Server, if the header is presented. The + * actual method of the request MUST be ignored. The Client SHOULD use this header if its + * environment does not support the PATCH or DELETE methods. * (https://tus.io/protocols/resumable-upload.html#x-http-method-override) */ public enum HttpMethod { - - GET, - HEAD, - POST, - PUT, - DELETE, - CONNECT, - OPTIONS, - TRACE, - PATCH; - - public static HttpMethod forName(String name) { - for (HttpMethod method : HttpMethod.values()) { - if (StringUtils.equalsIgnoreCase(method.name(), name)) { - return method; - } - } - - return null; + GET, + HEAD, + POST, + PUT, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH; + + public static HttpMethod forName(String name) { + for (HttpMethod method : HttpMethod.values()) { + if (StringUtils.equalsIgnoreCase(method.name(), name)) { + return method; + } } - public static HttpMethod getMethodIfSupported(HttpServletRequest request, - Set supportedHttpMethods) { - Validate.notNull(request, "The HttpServletRequest cannot be null"); + return null; + } - String requestMethod = request.getHeader(HttpHeader.METHOD_OVERRIDE); - if (StringUtils.isBlank(requestMethod) || forName(requestMethod) == null) { - requestMethod = request.getMethod(); - } + public static HttpMethod getMethodIfSupported( + HttpServletRequest request, Set supportedHttpMethods) { + Validate.notNull(request, "The HttpServletRequest cannot be null"); - HttpMethod httpMethod = forName(requestMethod); - return httpMethod != null && supportedHttpMethods.contains(httpMethod) ? httpMethod : null; + String requestMethod = request.getHeader(HttpHeader.METHOD_OVERRIDE); + if (StringUtils.isBlank(requestMethod) || forName(requestMethod) == null) { + requestMethod = request.getMethod(); } + HttpMethod httpMethod = forName(requestMethod); + return httpMethod != null && supportedHttpMethods.contains(httpMethod) ? httpMethod : null; + } } diff --git a/src/main/java/me/desair/tus/server/RequestHandler.java b/src/main/java/me/desair/tus/server/RequestHandler.java index efaa1fd..f089788 100644 --- a/src/main/java/me/desair/tus/server/RequestHandler.java +++ b/src/main/java/me/desair/tus/server/RequestHandler.java @@ -1,7 +1,6 @@ package me.desair.tus.server; import java.io.IOException; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.upload.UploadStorageService; import me.desair.tus.server.util.TusServletRequest; @@ -9,12 +8,15 @@ public interface RequestHandler { - boolean supports(HttpMethod method); - - void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException; + boolean supports(HttpMethod method); - boolean isErrorHandler(); + void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException; + boolean isErrorHandler(); } diff --git a/src/main/java/me/desair/tus/server/RequestValidator.java b/src/main/java/me/desair/tus/server/RequestValidator.java index e4eade7..7aa150e 100644 --- a/src/main/java/me/desair/tus/server/RequestValidator.java +++ b/src/main/java/me/desair/tus/server/RequestValidator.java @@ -1,32 +1,35 @@ package me.desair.tus.server; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.exception.TusException; import me.desair.tus.server.upload.UploadStorageService; -/** - * Interface for request validators - */ +/** Interface for request validators */ public interface RequestValidator { - /** - * Validate if the request should be processed - * @param method The HTTP method of this request (do not use {@link HttpServletRequest#getMethod()}!) - * @param request The {@link HttpServletRequest} to validate - * @param uploadStorageService The current upload storage service - * @param ownerKey A key representing the owner of the upload - * @throws TusException When validation fails and the request should not be processed - */ - void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException, IOException; + /** + * Validate if the request should be processed + * + * @param method The HTTP method of this request (do not use {@link + * HttpServletRequest#getMethod()}!) + * @param request The {@link HttpServletRequest} to validate + * @param uploadStorageService The current upload storage service + * @param ownerKey A key representing the owner of the upload + * @throws TusException When validation fails and the request should not be processed + */ + void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException, IOException; - /** - * Test if this validator supports the given HTTP method - * @param method The current HTTP method - * @return true if supported, false otherwise - */ - boolean supports(HttpMethod method); + /** + * Test if this validator supports the given HTTP method + * + * @param method The current HTTP method + * @return true if supported, false otherwise + */ + boolean supports(HttpMethod method); } diff --git a/src/main/java/me/desair/tus/server/TusExtension.java b/src/main/java/me/desair/tus/server/TusExtension.java index 755af3d..4d42d22 100644 --- a/src/main/java/me/desair/tus/server/TusExtension.java +++ b/src/main/java/me/desair/tus/server/TusExtension.java @@ -1,70 +1,83 @@ package me.desair.tus.server; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Collection; -import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.upload.UploadStorageService; import me.desair.tus.server.util.TusServletRequest; import me.desair.tus.server.util.TusServletResponse; -/** - * Interface that represents an extension in the tus protocol - */ +/** Interface that represents an extension in the tus protocol */ public interface TusExtension { - /** - * The name of the Tus extension that can be used to disable or enable the extension - * @return The name of the extension - */ - String getName(); + /** + * The name of the Tus extension that can be used to disable or enable the extension + * + * @return The name of the extension + */ + String getName(); - /** - * Validate the given request - * @param method The HTTP method of this request (taking into account overrides) - * @param servletRequest The HTTP request - * @param uploadStorageService The current upload storage service - * @param ownerKey Identifier of the owner of this upload - * @throws TusException When the request is invalid - * @throws IOException When unable to read upload information - */ - void validate(HttpMethod method, HttpServletRequest servletRequest, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException, IOException; + /** + * Validate the given request + * + * @param method The HTTP method of this request (taking into account overrides) + * @param servletRequest The HTTP request + * @param uploadStorageService The current upload storage service + * @param ownerKey Identifier of the owner of this upload + * @throws TusException When the request is invalid + * @throws IOException When unable to read upload information + */ + void validate( + HttpMethod method, + HttpServletRequest servletRequest, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException, IOException; - /** - * Process the given request - * @param method The HTTP method of this request (taking into account overrides) - * @param servletRequest The HTTP request - * @param servletResponse The HTTP response - * @param uploadStorageService The current upload storage service - * @param ownerKey Identifier of the owner of this upload - * @throws TusException When processing the request fails - * @throws IOException When unable to read upload information - */ - void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException; + /** + * Process the given request + * + * @param method The HTTP method of this request (taking into account overrides) + * @param servletRequest The HTTP request + * @param servletResponse The HTTP response + * @param uploadStorageService The current upload storage service + * @param ownerKey Identifier of the owner of this upload + * @throws TusException When processing the request fails + * @throws IOException When unable to read upload information + */ + void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException; - /** - * If a request is invalid, or when processing the request fails, it might be necessary to react to this failure. - * This method allows extensions to react to validation or processing failures. - * @param method The HTTP method of this request (taking into account overrides) - * @param servletRequest The HTTP request - * @param servletResponse The HTTP response - * @param uploadStorageService The current upload storage service - * @param ownerKey Identifier of the owner of this upload - * @throws TusException When handling the error fails - * @throws IOException When unable to read upload information - */ - void handleError(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException; + /** + * If a request is invalid, or when processing the request fails, it might be necessary to react + * to this failure. This method allows extensions to react to validation or processing failures. + * + * @param method The HTTP method of this request (taking into account overrides) + * @param servletRequest The HTTP request + * @param servletResponse The HTTP response + * @param uploadStorageService The current upload storage service + * @param ownerKey Identifier of the owner of this upload + * @throws TusException When handling the error fails + * @throws IOException When unable to read upload information + */ + void handleError( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException; - /** - * The minimal list of HTTP methods that this extension needs to function properly - * @return The list of HTTP methods required by this extension - */ - Collection getMinimalSupportedHttpMethods(); + /** + * The minimal list of HTTP methods that this extension needs to function properly + * + * @return The list of HTTP methods required by this extension + */ + Collection getMinimalSupportedHttpMethods(); } diff --git a/src/main/java/me/desair/tus/server/TusFileUploadService.java b/src/main/java/me/desair/tus/server/TusFileUploadService.java index 78be473..e5a33cf 100644 --- a/src/main/java/me/desair/tus/server/TusFileUploadService.java +++ b/src/main/java/me/desair/tus/server/TusFileUploadService.java @@ -1,5 +1,7 @@ package me.desair.tus.server; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -7,9 +9,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Set; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.checksum.ChecksumExtension; import me.desair.tus.server.concatenation.ConcatenationExtension; import me.desair.tus.server.core.CoreProtocol; @@ -35,451 +34,472 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Helper class that implements the server side tus v1.0.0 upload protocol - */ +/** Helper class that implements the server side tus v1.0.0 upload protocol */ public class TusFileUploadService { - public static final String TUS_API_VERSION = "1.0.0"; - - private static final Logger log = LoggerFactory.getLogger(TusFileUploadService.class); - - private UploadStorageService uploadStorageService; - private UploadLockingService uploadLockingService; - private UploadIdFactory idFactory = new UUIDUploadIdFactory(); - private final LinkedHashMap enabledFeatures = new LinkedHashMap<>(); - private final Set supportedHttpMethods = EnumSet.noneOf(HttpMethod.class); - private boolean isThreadLocalCacheEnabled = false; - private boolean isChunkedTransferDecodingEnabled = false; - - public TusFileUploadService() { - String storagePath = FileUtils.getTempDirectoryPath() + File.separator + "tus"; - this.uploadStorageService = new DiskStorageService(idFactory, storagePath); - this.uploadLockingService = new DiskLockingService(idFactory, storagePath); - initFeatures(); - } - - protected void initFeatures() { - //The order of the features is important - addTusExtension(new CoreProtocol()); - addTusExtension(new CreationExtension()); - addTusExtension(new ChecksumExtension()); - addTusExtension(new TerminationExtension()); - addTusExtension(new ExpirationExtension()); - addTusExtension(new ConcatenationExtension()); - } - - /** - * Set the URI under which the main tus upload endpoint is hosted. - * Optionally, this URI may contain regex parameters in order to support endpoints that contain - * URL parameters, for example /users/[0-9]+/files/upload - * - * @param uploadURI The URI of the main tus upload endpoint - * @return The current service - */ - public TusFileUploadService withUploadURI(String uploadURI) { - this.idFactory.setUploadURI(uploadURI); - return this; - } - - /** - * Specify the maximum number of bytes that can be uploaded per upload. - * If you don't call this method, the maximum number of bytes is Long.MAX_VALUE. - * - * @param maxUploadSize The maximum upload length that is allowed - * @return The current service - */ - public TusFileUploadService withMaxUploadSize(Long maxUploadSize) { - Validate.exclusiveBetween(0, Long.MAX_VALUE, maxUploadSize, "The max upload size must be bigger than 0"); - this.uploadStorageService.setMaxUploadSize(maxUploadSize); - return this; - } - - /** - * Provide a custom {@link UploadIdFactory} implementation that should be used to generate identifiers for - * the different uploads. Example implementation are {@link me.desair.tus.server.upload.UUIDUploadIdFactory} and - * {@link me.desair.tus.server.upload.TimeBasedUploadIdFactory}. - * - * @param uploadIdFactory The custom {@link UploadIdFactory} implementation - * @return The current service - */ - public TusFileUploadService withUploadIdFactory(UploadIdFactory uploadIdFactory) { - Validate.notNull(uploadIdFactory, "The UploadIdFactory cannot be null"); - String previousUploadURI = this.idFactory.getUploadURI(); - this.idFactory = uploadIdFactory; - this.idFactory.setUploadURI(previousUploadURI); - this.uploadStorageService.setIdFactory(this.idFactory); - this.uploadLockingService.setIdFactory(this.idFactory); - return this; - } - - /** - * Provide a custom {@link UploadStorageService} implementation that should be used to store uploaded bytes and - * metadata ({@link UploadInfo}). - * - * @param uploadStorageService The custom {@link UploadStorageService} implementation - * @return The current service - */ - public TusFileUploadService withUploadStorageService(UploadStorageService uploadStorageService) { - Validate.notNull(uploadStorageService, "The UploadStorageService cannot be null"); - //Copy over any previous configuration - uploadStorageService.setMaxUploadSize(this.uploadStorageService.getMaxUploadSize()); - uploadStorageService.setUploadExpirationPeriod(this.uploadStorageService.getUploadExpirationPeriod()); - uploadStorageService.setIdFactory(this.idFactory); - //Update the upload storage service - this.uploadStorageService = uploadStorageService; - prepareCacheIfEnabled(); - return this; - } - - /** - * Provide a custom {@link UploadLockingService} implementation that should be used when processing uploads. - * The upload locking service is responsible for locking an upload that is being processed so that it cannot - * be corrupted by simultaneous or delayed requests. - * - * @param uploadLockingService The {@link UploadLockingService} implementation to use - * @return The current service - */ - public TusFileUploadService withUploadLockingService(UploadLockingService uploadLockingService) { - Validate.notNull(uploadLockingService, "The UploadStorageService cannot be null"); - uploadLockingService.setIdFactory(this.idFactory); - //Update the upload storage service - this.uploadLockingService = uploadLockingService; - prepareCacheIfEnabled(); - return this; - } - - /** - * If you're using the default file system-based storage service, you can use this method to - * specify the path where to store the uploaded bytes and upload information. - * - * @param storagePath The file system path where uploads can be stored (temporarily) - * @return The current service - */ - public TusFileUploadService withStoragePath(String storagePath) { - Validate.notBlank(storagePath, "The storage path cannot be blank"); - withUploadStorageService(new DiskStorageService(storagePath)); - withUploadLockingService(new DiskLockingService(storagePath)); - prepareCacheIfEnabled(); - return this; - } - - /** - * Enable or disable a thread-local based cache of upload data. This can reduce the load - * on the storage backends. By default this cache is disabled. - * @param isEnabled True if the cache should be enabled, false otherwise - * @return The current service - */ - public TusFileUploadService withThreadLocalCache(boolean isEnabled) { - this.isThreadLocalCacheEnabled = isEnabled; - prepareCacheIfEnabled(); - return this; - } - - /** - * Instruct this service to (not) decode any requests with Transfer-Encoding value "chunked". - * Use this method in case the web container in which this service is running does not decode - * chunked transfers itself. By default, chunked decoding is disabled. - * - * @param isEnabled True if chunked requests should be decoded, false otherwise. - * @return The current service - */ - public TusFileUploadService withChunkedTransferDecoding(boolean isEnabled) { - isChunkedTransferDecodingEnabled = isEnabled; - return this; - } - - /** - * You can set the number of milliseconds after which an upload is considered as expired and available for cleanup. - * - * @param expirationPeriod The number of milliseconds after which an upload expires and can be removed - * @return The current service - */ - public TusFileUploadService withUploadExpirationPeriod(Long expirationPeriod) { - uploadStorageService.setUploadExpirationPeriod(expirationPeriod); - return this; - } - - /** - * Enable the unofficial `download` extension that also allows you to download uploaded bytes. - * By default this feature is disabled. - * - * @return The current service - */ - public TusFileUploadService withDownloadFeature() { - addTusExtension(new DownloadExtension()); - return this; - } - - /** - * Add a custom (application-specific) extension that implements the {@link me.desair.tus.server.TusExtension} - * interface. For example you can add your own extension that checks authentication and authorization policies - * within your application for the user doing the upload. - * - * @param feature The custom extension implementation - * @return The current service - */ - public TusFileUploadService addTusExtension(TusExtension feature) { - Validate.notNull(feature, "A custom feature cannot be null"); - enabledFeatures.put(feature.getName(), feature); - updateSupportedHttpMethods(); - return this; - } - - /** - * Disable the TusExtension for which the getName() method matches the provided string. The default extensions - * have names "creation", "checksum", "expiration", "concatenation", "termination" and "download". - * You cannot disable the "core" feature. - * - * @param extensionName The name of the extension to disable - * @return The current service - */ - public TusFileUploadService disableTusExtension(String extensionName) { - Validate.notNull(extensionName, "The extension name cannot be null"); - Validate.isTrue(!StringUtils.equals("core", extensionName), "The core protocol cannot be disabled"); - - enabledFeatures.remove(extensionName); - updateSupportedHttpMethods(); - return this; - } - - /** - * Get all HTTP methods that are supported by this TusUploadService based on the enabled and/or disabled - * tus extensions - * - * @return The set of enabled HTTP methods - */ - public Set getSupportedHttpMethods() { - return EnumSet.copyOf(supportedHttpMethods); - } - - /** - * Get the set of enabled Tus extensions - * @return The set of active extensions - */ - public Set getEnabledFeatures() { - return new LinkedHashSet<>(enabledFeatures.keySet()); - } - - /** - * Process a tus upload request. - * Use this method to process any request made to the main and sub tus upload endpoints. This corresponds to - * the path specified in the withUploadURI() method and any sub-path of that URI. - * - * @param servletRequest The {@link HttpServletRequest} of the request - * @param servletResponse The {@link HttpServletResponse} of the request - * @throws IOException When saving bytes or information of this requests fails - */ - public void process(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws IOException { - process(servletRequest, servletResponse, null); + public static final String TUS_API_VERSION = "1.0.0"; + + private static final Logger log = LoggerFactory.getLogger(TusFileUploadService.class); + + private UploadStorageService uploadStorageService; + private UploadLockingService uploadLockingService; + private UploadIdFactory idFactory = new UUIDUploadIdFactory(); + private final LinkedHashMap enabledFeatures = new LinkedHashMap<>(); + private final Set supportedHttpMethods = EnumSet.noneOf(HttpMethod.class); + private boolean isThreadLocalCacheEnabled = false; + private boolean isChunkedTransferDecodingEnabled = false; + + public TusFileUploadService() { + String storagePath = FileUtils.getTempDirectoryPath() + File.separator + "tus"; + this.uploadStorageService = new DiskStorageService(idFactory, storagePath); + this.uploadLockingService = new DiskLockingService(idFactory, storagePath); + initFeatures(); + } + + protected void initFeatures() { + // The order of the features is important + addTusExtension(new CoreProtocol()); + addTusExtension(new CreationExtension()); + addTusExtension(new ChecksumExtension()); + addTusExtension(new TerminationExtension()); + addTusExtension(new ExpirationExtension()); + addTusExtension(new ConcatenationExtension()); + } + + /** + * Set the URI under which the main tus upload endpoint is hosted. Optionally, this URI may + * contain regex parameters in order to support endpoints that contain URL parameters, for example + * /users/[0-9]+/files/upload + * + * @param uploadURI The URI of the main tus upload endpoint + * @return The current service + */ + public TusFileUploadService withUploadURI(String uploadURI) { + this.idFactory.setUploadURI(uploadURI); + return this; + } + + /** + * Specify the maximum number of bytes that can be uploaded per upload. If you don't call this + * method, the maximum number of bytes is Long.MAX_VALUE. + * + * @param maxUploadSize The maximum upload length that is allowed + * @return The current service + */ + public TusFileUploadService withMaxUploadSize(Long maxUploadSize) { + Validate.exclusiveBetween( + 0, Long.MAX_VALUE, maxUploadSize, "The max upload size must be bigger than 0"); + this.uploadStorageService.setMaxUploadSize(maxUploadSize); + return this; + } + + /** + * Provide a custom {@link UploadIdFactory} implementation that should be used to generate + * identifiers for the different uploads. Example implementation are {@link + * me.desair.tus.server.upload.UUIDUploadIdFactory} and {@link + * me.desair.tus.server.upload.TimeBasedUploadIdFactory}. + * + * @param uploadIdFactory The custom {@link UploadIdFactory} implementation + * @return The current service + */ + public TusFileUploadService withUploadIdFactory(UploadIdFactory uploadIdFactory) { + Validate.notNull(uploadIdFactory, "The UploadIdFactory cannot be null"); + String previousUploadURI = this.idFactory.getUploadURI(); + this.idFactory = uploadIdFactory; + this.idFactory.setUploadURI(previousUploadURI); + this.uploadStorageService.setIdFactory(this.idFactory); + this.uploadLockingService.setIdFactory(this.idFactory); + return this; + } + + /** + * Provide a custom {@link UploadStorageService} implementation that should be used to store + * uploaded bytes and metadata ({@link UploadInfo}). + * + * @param uploadStorageService The custom {@link UploadStorageService} implementation + * @return The current service + */ + public TusFileUploadService withUploadStorageService(UploadStorageService uploadStorageService) { + Validate.notNull(uploadStorageService, "The UploadStorageService cannot be null"); + // Copy over any previous configuration + uploadStorageService.setMaxUploadSize(this.uploadStorageService.getMaxUploadSize()); + uploadStorageService.setUploadExpirationPeriod( + this.uploadStorageService.getUploadExpirationPeriod()); + uploadStorageService.setIdFactory(this.idFactory); + // Update the upload storage service + this.uploadStorageService = uploadStorageService; + prepareCacheIfEnabled(); + return this; + } + + /** + * Provide a custom {@link UploadLockingService} implementation that should be used when + * processing uploads. The upload locking service is responsible for locking an upload that is + * being processed so that it cannot be corrupted by simultaneous or delayed requests. + * + * @param uploadLockingService The {@link UploadLockingService} implementation to use + * @return The current service + */ + public TusFileUploadService withUploadLockingService(UploadLockingService uploadLockingService) { + Validate.notNull(uploadLockingService, "The UploadStorageService cannot be null"); + uploadLockingService.setIdFactory(this.idFactory); + // Update the upload storage service + this.uploadLockingService = uploadLockingService; + prepareCacheIfEnabled(); + return this; + } + + /** + * If you're using the default file system-based storage service, you can use this method to + * specify the path where to store the uploaded bytes and upload information. + * + * @param storagePath The file system path where uploads can be stored (temporarily) + * @return The current service + */ + public TusFileUploadService withStoragePath(String storagePath) { + Validate.notBlank(storagePath, "The storage path cannot be blank"); + withUploadStorageService(new DiskStorageService(storagePath)); + withUploadLockingService(new DiskLockingService(storagePath)); + prepareCacheIfEnabled(); + return this; + } + + /** + * Enable or disable a thread-local based cache of upload data. This can reduce the load on the + * storage backends. By default this cache is disabled. + * + * @param isEnabled True if the cache should be enabled, false otherwise + * @return The current service + */ + public TusFileUploadService withThreadLocalCache(boolean isEnabled) { + this.isThreadLocalCacheEnabled = isEnabled; + prepareCacheIfEnabled(); + return this; + } + + /** + * Instruct this service to (not) decode any requests with Transfer-Encoding value "chunked". Use + * this method in case the web container in which this service is running does not decode chunked + * transfers itself. By default, chunked decoding is disabled. + * + * @param isEnabled True if chunked requests should be decoded, false otherwise. + * @return The current service + */ + public TusFileUploadService withChunkedTransferDecoding(boolean isEnabled) { + isChunkedTransferDecodingEnabled = isEnabled; + return this; + } + + /** + * You can set the number of milliseconds after which an upload is considered as expired and + * available for cleanup. + * + * @param expirationPeriod The number of milliseconds after which an upload expires and can be + * removed + * @return The current service + */ + public TusFileUploadService withUploadExpirationPeriod(Long expirationPeriod) { + uploadStorageService.setUploadExpirationPeriod(expirationPeriod); + return this; + } + + /** + * Enable the unofficial `download` extension that also allows you to download uploaded bytes. By + * default this feature is disabled. + * + * @return The current service + */ + public TusFileUploadService withDownloadFeature() { + addTusExtension(new DownloadExtension()); + return this; + } + + /** + * Add a custom (application-specific) extension that implements the {@link + * me.desair.tus.server.TusExtension} interface. For example you can add your own extension that + * checks authentication and authorization policies within your application for the user doing the + * upload. + * + * @param feature The custom extension implementation + * @return The current service + */ + public TusFileUploadService addTusExtension(TusExtension feature) { + Validate.notNull(feature, "A custom feature cannot be null"); + enabledFeatures.put(feature.getName(), feature); + updateSupportedHttpMethods(); + return this; + } + + /** + * Disable the TusExtension for which the getName() method matches the provided string. The + * default extensions have names "creation", "checksum", "expiration", "concatenation", + * "termination" and "download". You cannot disable the "core" feature. + * + * @param extensionName The name of the extension to disable + * @return The current service + */ + public TusFileUploadService disableTusExtension(String extensionName) { + Validate.notNull(extensionName, "The extension name cannot be null"); + Validate.isTrue( + !StringUtils.equals("core", extensionName), "The core protocol cannot be disabled"); + + enabledFeatures.remove(extensionName); + updateSupportedHttpMethods(); + return this; + } + + /** + * Get all HTTP methods that are supported by this TusUploadService based on the enabled and/or + * disabled tus extensions + * + * @return The set of enabled HTTP methods + */ + public Set getSupportedHttpMethods() { + return EnumSet.copyOf(supportedHttpMethods); + } + + /** + * Get the set of enabled Tus extensions + * + * @return The set of active extensions + */ + public Set getEnabledFeatures() { + return new LinkedHashSet<>(enabledFeatures.keySet()); + } + + /** + * Process a tus upload request. Use this method to process any request made to the main and sub + * tus upload endpoints. This corresponds to the path specified in the withUploadURI() method and + * any sub-path of that URI. + * + * @param servletRequest The {@link HttpServletRequest} of the request + * @param servletResponse The {@link HttpServletResponse} of the request + * @throws IOException When saving bytes or information of this requests fails + */ + public void process(HttpServletRequest servletRequest, HttpServletResponse servletResponse) + throws IOException { + process(servletRequest, servletResponse, null); + } + + /** + * Process a tus upload request that belongs to a specific owner. Use this method to process any + * request made to the main and sub tus upload endpoints. This corresponds to the path specified + * in the withUploadURI() method and any sub-path of that URI. + * + * @param servletRequest The {@link HttpServletRequest} of the request + * @param servletResponse The {@link HttpServletResponse} of the request + * @param ownerKey A unique identifier of the owner (group) of this upload + * @throws IOException When saving bytes or information of this requests fails + */ + public void process( + HttpServletRequest servletRequest, HttpServletResponse servletResponse, String ownerKey) + throws IOException { + Validate.notNull(servletRequest, "The HTTP Servlet request cannot be null"); + Validate.notNull(servletResponse, "The HTTP Servlet response cannot be null"); + + HttpMethod method = HttpMethod.getMethodIfSupported(servletRequest, supportedHttpMethods); + + log.debug( + "Processing request with method {} and URL {}", method, servletRequest.getRequestURL()); + + TusServletRequest request = + new TusServletRequest(servletRequest, isChunkedTransferDecodingEnabled); + TusServletResponse response = new TusServletResponse(servletResponse); + + try (UploadLock lock = uploadLockingService.lockUploadByUri(request.getRequestURI())) { + + processLockedRequest(method, request, response, ownerKey); + + } catch (TusException e) { + log.error("Unable to lock upload for request URI " + request.getRequestURI(), e); } - - /** - * Process a tus upload request that belongs to a specific owner. - * Use this method to process any request made to the main and sub tus upload endpoints. This corresponds to - * the path specified in the withUploadURI() method and any sub-path of that URI. - * - * @param servletRequest The {@link HttpServletRequest} of the request - * @param servletResponse The {@link HttpServletResponse} of the request - * @param ownerKey A unique identifier of the owner (group) of this upload - * @throws IOException When saving bytes or information of this requests fails - */ - public void process(HttpServletRequest servletRequest, HttpServletResponse servletResponse, - String ownerKey) throws IOException { - Validate.notNull(servletRequest, "The HTTP Servlet request cannot be null"); - Validate.notNull(servletResponse, "The HTTP Servlet response cannot be null"); - - HttpMethod method = HttpMethod.getMethodIfSupported(servletRequest, supportedHttpMethods); - - log.debug("Processing request with method {} and URL {}", method, servletRequest.getRequestURL()); - - TusServletRequest request = new TusServletRequest(servletRequest, isChunkedTransferDecodingEnabled); - TusServletResponse response = new TusServletResponse(servletResponse); - - try (UploadLock lock = uploadLockingService.lockUploadByUri(request.getRequestURI())) { - - processLockedRequest(method, request, response, ownerKey); - - } catch (TusException e) { - log.error("Unable to lock upload for request URI " + request.getRequestURI(), e); - } - } - - /** - * Method to retrieve the bytes that were uploaded to a specific upload URI - * - * @param uploadURI The URI of the upload - * @return An {@link InputStream} that will stream the uploaded bytes - * @throws IOException When the retreiving the uploaded bytes fails - * @throws TusException When the upload is still in progress or cannot be found - */ - public InputStream getUploadedBytes(String uploadURI) throws IOException, TusException { - return getUploadedBytes(uploadURI, null); + } + + /** + * Method to retrieve the bytes that were uploaded to a specific upload URI + * + * @param uploadURI The URI of the upload + * @return An {@link InputStream} that will stream the uploaded bytes + * @throws IOException When the retreiving the uploaded bytes fails + * @throws TusException When the upload is still in progress or cannot be found + */ + public InputStream getUploadedBytes(String uploadURI) throws IOException, TusException { + return getUploadedBytes(uploadURI, null); + } + + /** + * Method to retrieve the bytes that were uploaded to a specific upload URI + * + * @param uploadURI The URI of the upload + * @param ownerKey The key of the owner of this upload + * @return An {@link InputStream} that will stream the uploaded bytes + * @throws IOException When the retreiving the uploaded bytes fails + * @throws TusException When the upload is still in progress or cannot be found + */ + public InputStream getUploadedBytes(String uploadURI, String ownerKey) + throws IOException, TusException { + + try (UploadLock lock = uploadLockingService.lockUploadByUri(uploadURI)) { + + return uploadStorageService.getUploadedBytes(uploadURI, ownerKey); } - - /** - * Method to retrieve the bytes that were uploaded to a specific upload URI - * - * @param uploadURI The URI of the upload - * @param ownerKey The key of the owner of this upload - * @return An {@link InputStream} that will stream the uploaded bytes - * @throws IOException When the retreiving the uploaded bytes fails - * @throws TusException When the upload is still in progress or cannot be found - */ - public InputStream getUploadedBytes(String uploadURI, String ownerKey) - throws IOException, TusException { - - try (UploadLock lock = uploadLockingService.lockUploadByUri(uploadURI)) { - - return uploadStorageService.getUploadedBytes(uploadURI, ownerKey); - } - } - - /** - * Get the information on the upload corresponding to the given upload URI - * - * @param uploadURI The URI of the upload - * @return Information on the upload - * @throws IOException When retrieving the upload information fails - * @throws TusException When the upload is still in progress or cannot be found - */ - public UploadInfo getUploadInfo(String uploadURI) throws IOException, TusException { - return getUploadInfo(uploadURI, null); - } - - /** - * Get the information on the upload corresponding to the given upload URI - * - * @param uploadURI The URI of the upload - * @param ownerKey The key of the owner of this upload - * @return Information on the upload - * @throws IOException When retrieving the upload information fails - * @throws TusException When the upload is still in progress or cannot be found - */ - public UploadInfo getUploadInfo(String uploadURI, String ownerKey) throws IOException, TusException { - try (UploadLock lock = uploadLockingService.lockUploadByUri(uploadURI)) { - - return uploadStorageService.getUploadInfo(uploadURI, ownerKey); - } + } + + /** + * Get the information on the upload corresponding to the given upload URI + * + * @param uploadURI The URI of the upload + * @return Information on the upload + * @throws IOException When retrieving the upload information fails + * @throws TusException When the upload is still in progress or cannot be found + */ + public UploadInfo getUploadInfo(String uploadURI) throws IOException, TusException { + return getUploadInfo(uploadURI, null); + } + + /** + * Get the information on the upload corresponding to the given upload URI + * + * @param uploadURI The URI of the upload + * @param ownerKey The key of the owner of this upload + * @return Information on the upload + * @throws IOException When retrieving the upload information fails + * @throws TusException When the upload is still in progress or cannot be found + */ + public UploadInfo getUploadInfo(String uploadURI, String ownerKey) + throws IOException, TusException { + try (UploadLock lock = uploadLockingService.lockUploadByUri(uploadURI)) { + + return uploadStorageService.getUploadInfo(uploadURI, ownerKey); } - - /** - * Method to delete an upload associated with the given upload URL. Invoke this method if you no longer need - * the upload. - * - * @param uploadURI The upload URI - */ - public void deleteUpload(String uploadURI) throws IOException, TusException { - deleteUpload(uploadURI, null); + } + + /** + * Method to delete an upload associated with the given upload URL. Invoke this method if you no + * longer need the upload. + * + * @param uploadURI The upload URI + */ + public void deleteUpload(String uploadURI) throws IOException, TusException { + deleteUpload(uploadURI, null); + } + + /** + * Method to delete an upload associated with the given upload URL. Invoke this method if you no + * longer need the upload. + * + * @param uploadURI The upload URI + * @param ownerKey The key of the owner of this upload + */ + public void deleteUpload(String uploadURI, String ownerKey) throws IOException, TusException { + try (UploadLock lock = uploadLockingService.lockUploadByUri(uploadURI)) { + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadURI, ownerKey); + if (uploadInfo != null) { + uploadStorageService.terminateUpload(uploadInfo); + } } - - /** - * Method to delete an upload associated with the given upload URL. Invoke this method if you no longer need - * the upload. - * - * @param uploadURI The upload URI - * @param ownerKey The key of the owner of this upload - */ - public void deleteUpload(String uploadURI, String ownerKey) throws IOException, TusException { - try (UploadLock lock = uploadLockingService.lockUploadByUri(uploadURI)) { - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadURI, ownerKey); - if (uploadInfo != null) { - uploadStorageService.terminateUpload(uploadInfo); - } - } + } + + /** + * This method should be invoked periodically. It will cleanup any expired uploads and stale locks + * + * @throws IOException When cleaning fails + */ + public void cleanup() throws IOException { + uploadLockingService.cleanupStaleLocks(); + uploadStorageService.cleanupExpiredUploads(uploadLockingService); + } + + protected void processLockedRequest( + HttpMethod method, TusServletRequest request, TusServletResponse response, String ownerKey) + throws IOException { + try { + validateRequest(method, request, ownerKey); + + executeProcessingByFeatures(method, request, response, ownerKey); + + } catch (TusException e) { + processTusException(method, request, response, ownerKey, e); } - - /** - * This method should be invoked periodically. It will cleanup any expired uploads - * and stale locks - * - * @throws IOException When cleaning fails - */ - public void cleanup() throws IOException { - uploadLockingService.cleanupStaleLocks(); - uploadStorageService.cleanupExpiredUploads(uploadLockingService); + } + + protected void executeProcessingByFeatures( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + String ownerKey) + throws IOException, TusException { + + for (TusExtension feature : enabledFeatures.values()) { + if (!servletRequest.isProcessedBy(feature)) { + servletRequest.addProcessor(feature); + feature.process(method, servletRequest, servletResponse, uploadStorageService, ownerKey); + } } + } - protected void processLockedRequest(HttpMethod method, TusServletRequest request, - TusServletResponse response, String ownerKey) throws IOException { - try { - validateRequest(method, request, ownerKey); - - executeProcessingByFeatures(method, request, response, ownerKey); + protected void validateRequest( + HttpMethod method, HttpServletRequest servletRequest, String ownerKey) + throws TusException, IOException { - } catch (TusException e) { - processTusException(method, request, response, ownerKey, e); - } + for (TusExtension feature : enabledFeatures.values()) { + feature.validate(method, servletRequest, uploadStorageService, ownerKey); } - - protected void executeProcessingByFeatures(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, String ownerKey) - throws IOException, TusException { - - for (TusExtension feature : enabledFeatures.values()) { - if (!servletRequest.isProcessedBy(feature)) { - servletRequest.addProcessor(feature); - feature.process(method, servletRequest, servletResponse, uploadStorageService, ownerKey); - } + } + + protected void processTusException( + HttpMethod method, + TusServletRequest request, + TusServletResponse response, + String ownerKey, + TusException exception) + throws IOException { + + int status = exception.getStatus(); + String message = exception.getMessage(); + + log.warn( + "Unable to process request {} {}. Sent response status {} with message \"{}\"", + method, + request.getRequestURL(), + status, + message); + + try { + for (TusExtension feature : enabledFeatures.values()) { + + if (!request.isProcessedBy(feature)) { + request.addProcessor(feature); + feature.handleError(method, request, response, uploadStorageService, ownerKey); } - } + } - protected void validateRequest(HttpMethod method, HttpServletRequest servletRequest, - String ownerKey) throws TusException, IOException { + // Since an error occurred, the bytes we have written are probably not valid. So remove + // them. + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); + uploadStorageService.removeLastNumberOfBytes(uploadInfo, request.getBytesRead()); - for (TusExtension feature : enabledFeatures.values()) { - feature.validate(method, servletRequest, uploadStorageService, ownerKey); - } + } catch (TusException ex) { + log.warn("An exception occurred while handling another exception", ex); } - protected void processTusException(HttpMethod method, TusServletRequest request, - TusServletResponse response, String ownerKey, - TusException exception) throws IOException { - - int status = exception.getStatus(); - String message = exception.getMessage(); - - log.warn("Unable to process request {} {}. Sent response status {} with message \"{}\"", - method, request.getRequestURL(), status, message); - - try { - for (TusExtension feature : enabledFeatures.values()) { + response.sendError(status, message); + } - if (!request.isProcessedBy(feature)) { - request.addProcessor(feature); - feature.handleError(method, request, response, uploadStorageService, ownerKey); - } - } - - //Since an error occurred, the bytes we have written are probably not valid. So remove them. - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); - uploadStorageService.removeLastNumberOfBytes(uploadInfo, request.getBytesRead()); - - } catch (TusException ex) { - log.warn("An exception occurred while handling another exception", ex); - } - - response.sendError(status, message); - } - - private void updateSupportedHttpMethods() { - supportedHttpMethods.clear(); - for (TusExtension tusFeature : enabledFeatures.values()) { - supportedHttpMethods.addAll(tusFeature.getMinimalSupportedHttpMethods()); - } + private void updateSupportedHttpMethods() { + supportedHttpMethods.clear(); + for (TusExtension tusFeature : enabledFeatures.values()) { + supportedHttpMethods.addAll(tusFeature.getMinimalSupportedHttpMethods()); } - - private void prepareCacheIfEnabled() { - if (isThreadLocalCacheEnabled && uploadStorageService != null && uploadLockingService != null) { - ThreadLocalCachedStorageAndLockingService service = - new ThreadLocalCachedStorageAndLockingService( - uploadStorageService, - uploadLockingService); - service.setIdFactory(this.idFactory); - this.uploadStorageService = service; - this.uploadLockingService = service; - } + } + + private void prepareCacheIfEnabled() { + if (isThreadLocalCacheEnabled && uploadStorageService != null && uploadLockingService != null) { + ThreadLocalCachedStorageAndLockingService service = + new ThreadLocalCachedStorageAndLockingService(uploadStorageService, uploadLockingService); + service.setIdFactory(this.idFactory); + this.uploadStorageService = service; + this.uploadLockingService = service; } - + } } diff --git a/src/main/java/me/desair/tus/server/checksum/ChecksumAlgorithm.java b/src/main/java/me/desair/tus/server/checksum/ChecksumAlgorithm.java index 93fc48f..ea6ddf1 100644 --- a/src/main/java/me/desair/tus/server/checksum/ChecksumAlgorithm.java +++ b/src/main/java/me/desair/tus/server/checksum/ChecksumAlgorithm.java @@ -2,68 +2,66 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; - import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Enum that contains all supported checksum algorithms - * The names of the checksum algorithms MUST only consist of ASCII characters - * with the modification that uppercase characters are excluded. +/** + * Enum that contains all supported checksum algorithms The names of the checksum algorithms MUST + * only consist of ASCII characters with the modification that uppercase characters are excluded. */ public enum ChecksumAlgorithm { + MD5("MD5", "md5"), + SHA1("SHA-1", "sha1"), + SHA256("SHA-256", "sha256"), + SHA384("SHA-384", "sha384"), + SHA512("SHA-512", "sha512"); - MD5("MD5", "md5"), - SHA1("SHA-1", "sha1"), - SHA256("SHA-256", "sha256"), - SHA384("SHA-384", "sha384"), - SHA512("SHA-512", "sha512"); + public static final String CHECKSUM_VALUE_SEPARATOR = " "; - public static final String CHECKSUM_VALUE_SEPARATOR = " "; + private static final Logger log = LoggerFactory.getLogger(ChecksumAlgorithm.class); - private static final Logger log = LoggerFactory.getLogger(ChecksumAlgorithm.class); + private String javaName; + private String tusName; - private String javaName; - private String tusName; + ChecksumAlgorithm(String javaName, String tusName) { + this.javaName = javaName; + this.tusName = tusName; + } - ChecksumAlgorithm(String javaName, String tusName) { - this.javaName = javaName; - this.tusName = tusName; - } + public String getJavaName() { + return javaName; + } - public String getJavaName() { - return javaName; - } + public String getTusName() { + return tusName; + } - public String getTusName() { - return tusName; - } + @Override + public String toString() { + return getTusName(); + } - @Override - public String toString() { - return getTusName(); + public MessageDigest getMessageDigest() { + try { + return MessageDigest.getInstance(getJavaName()); + } catch (NoSuchAlgorithmException e) { + log.error("We are trying the use an algorithm that is not supported by this JVM", e); + return null; } + } - public MessageDigest getMessageDigest() { - try { - return MessageDigest.getInstance(getJavaName()); - } catch (NoSuchAlgorithmException e) { - log.error("We are trying the use an algorithm that is not supported by this JVM", e); - return null; - } + public static ChecksumAlgorithm forTusName(String name) { + for (ChecksumAlgorithm alg : ChecksumAlgorithm.values()) { + if (alg.getTusName().equals(name)) { + return alg; + } } + return null; + } - public static ChecksumAlgorithm forTusName(String name) { - for (ChecksumAlgorithm alg : ChecksumAlgorithm.values()) { - if (alg.getTusName().equals(name)) { - return alg; - } - } - return null; - } - - public static ChecksumAlgorithm forUploadChecksumHeader(String uploadChecksumHeader) { - String algorithm = StringUtils.substringBefore(uploadChecksumHeader, CHECKSUM_VALUE_SEPARATOR); - return forTusName(algorithm); - } + public static ChecksumAlgorithm forUploadChecksumHeader(String uploadChecksumHeader) { + String algorithm = StringUtils.substringBefore(uploadChecksumHeader, CHECKSUM_VALUE_SEPARATOR); + return forTusName(algorithm); + } } diff --git a/src/main/java/me/desair/tus/server/checksum/ChecksumExtension.java b/src/main/java/me/desair/tus/server/checksum/ChecksumExtension.java index bb89d92..699a4a8 100644 --- a/src/main/java/me/desair/tus/server/checksum/ChecksumExtension.java +++ b/src/main/java/me/desair/tus/server/checksum/ChecksumExtension.java @@ -3,7 +3,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; @@ -11,29 +10,29 @@ import me.desair.tus.server.util.AbstractTusExtension; /** - * The Client and the Server MAY implement and use this extension to verify data integrity of each PATCH request. - * If supported, the Server MUST add checksum to the Tus-Extension header. + * The Client and the Server MAY implement and use this extension to verify data integrity of each + * PATCH request. If supported, the Server MUST add checksum to the Tus-Extension header. */ public class ChecksumExtension extends AbstractTusExtension { - @Override - public String getName() { - return "checksum"; - } + @Override + public String getName() { + return "checksum"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.PATCH); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.PATCH); + } - @Override - protected void initValidators(List requestValidators) { - requestValidators.add(new ChecksumAlgorithmValidator()); - } + @Override + protected void initValidators(List requestValidators) { + requestValidators.add(new ChecksumAlgorithmValidator()); + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new ChecksumOptionsRequestHandler()); - requestHandlers.add(new ChecksumPatchRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new ChecksumOptionsRequestHandler()); + requestHandlers.add(new ChecksumPatchRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandler.java index 5f2405d..da435b4 100644 --- a/src/main/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandler.java @@ -9,27 +9,29 @@ import org.apache.commons.lang3.StringUtils; /** - * The Tus-Checksum-Algorithm header MUST be included in the response to an OPTIONS request. - * The Tus-Checksum-Algorithm response header MUST be a comma-separated list of the checksum - * algorithms supported by the server. + * The Tus-Checksum-Algorithm header MUST be included in the response to an OPTIONS request. The + * Tus-Checksum-Algorithm response header MUST be a comma-separated list of the checksum algorithms + * supported by the server. */ public class ChecksumOptionsRequestHandler extends AbstractExtensionRequestHandler { - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) { - super.process(method, servletRequest, servletResponse, uploadStorageService, ownerKey); + super.process(method, servletRequest, servletResponse, uploadStorageService, ownerKey); - servletResponse.setHeader(HttpHeader.TUS_CHECKSUM_ALGORITHM, - StringUtils.join(ChecksumAlgorithm.values(), ",")); - } - - @Override - protected void appendExtensions(StringBuilder extensionBuilder) { - addExtension(extensionBuilder, "checksum"); - addExtension(extensionBuilder, "checksum-trailer"); - } + servletResponse.setHeader( + HttpHeader.TUS_CHECKSUM_ALGORITHM, StringUtils.join(ChecksumAlgorithm.values(), ",")); + } + @Override + protected void appendExtensions(StringBuilder extensionBuilder) { + addExtension(extensionBuilder, "checksum"); + addExtension(extensionBuilder, "checksum-trailer"); + } } diff --git a/src/main/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandler.java b/src/main/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandler.java index fec8552..e7ddf38 100644 --- a/src/main/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandler.java +++ b/src/main/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandler.java @@ -3,7 +3,6 @@ import static me.desair.tus.server.checksum.ChecksumAlgorithm.CHECKSUM_VALUE_SEPARATOR; import java.io.IOException; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.checksum.validation.ChecksumAlgorithmValidator; @@ -17,38 +16,51 @@ public class ChecksumPatchRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - String uploadChecksumHeader = servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM); + String uploadChecksumHeader = servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM); - if (servletRequest.hasCalculatedChecksum() && StringUtils.isNotBlank(uploadChecksumHeader)) { + if (servletRequest.hasCalculatedChecksum() && StringUtils.isNotBlank(uploadChecksumHeader)) { - //The Upload-Checksum header can be a trailing header which is only present after reading the full content. - //Therefor we need to revalidate that header here - new ChecksumAlgorithmValidator().validate(method, servletRequest, uploadStorageService, ownerKey); + // The Upload-Checksum header can be a trailing header which is only present after + // reading the + // full content. + // Therefor we need to revalidate that header here + new ChecksumAlgorithmValidator() + .validate(method, servletRequest, uploadStorageService, ownerKey); - //Everything is valid, check if the checksum matches - String expectedValue = StringUtils.substringAfter(uploadChecksumHeader, CHECKSUM_VALUE_SEPARATOR); + // Everything is valid, check if the checksum matches + String expectedValue = + StringUtils.substringAfter(uploadChecksumHeader, CHECKSUM_VALUE_SEPARATOR); - ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.forUploadChecksumHeader(uploadChecksumHeader); - String calculatedValue = servletRequest.getCalculatedChecksum(checksumAlgorithm); + ChecksumAlgorithm checksumAlgorithm = + ChecksumAlgorithm.forUploadChecksumHeader(uploadChecksumHeader); + String calculatedValue = servletRequest.getCalculatedChecksum(checksumAlgorithm); - if (!StringUtils.equals(expectedValue, calculatedValue)) { - //throw an exception if the checksum is invalid. This will also trigger the removal of any - //bytes that were already saved - throw new UploadChecksumMismatchException("Expected checksum " + expectedValue - + " but was " + calculatedValue - + " with algorithm " + checksumAlgorithm); - } - } + if (!StringUtils.equals(expectedValue, calculatedValue)) { + // throw an exception if the checksum is invalid. This will also trigger the removal + // of any + // bytes that were already saved + throw new UploadChecksumMismatchException( + "Expected checksum " + + expectedValue + + " but was " + + calculatedValue + + " with algorithm " + + checksumAlgorithm); + } } - + } } diff --git a/src/main/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidator.java b/src/main/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidator.java index 7632ac3..d1f93cb 100644 --- a/src/main/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidator.java +++ b/src/main/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidator.java @@ -1,8 +1,7 @@ package me.desair.tus.server.checksum.validation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -13,30 +12,36 @@ import org.apache.commons.lang3.StringUtils; /** - * The Server MAY respond with one of the following status code: 400 Bad Request - * if the checksum algorithm is not supported by the server + * The Server MAY respond with one of the following status code: 400 Bad Request if the checksum + * algorithm is not supported by the server */ public class ChecksumAlgorithmValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException, IOException { - - String uploadChecksum = request.getHeader(HttpHeader.UPLOAD_CHECKSUM); - - //If the client provided a checksum header, check that we support the algorithm - if (StringUtils.isNotBlank(uploadChecksum) - && ChecksumAlgorithm.forUploadChecksumHeader(uploadChecksum) == null) { - - throw new ChecksumAlgorithmNotSupportedException("The " + HttpHeader.UPLOAD_CHECKSUM + " header value " - + uploadChecksum + " is not supported"); - - } + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException, IOException { + + String uploadChecksum = request.getHeader(HttpHeader.UPLOAD_CHECKSUM); + + // If the client provided a checksum header, check that we support the algorithm + if (StringUtils.isNotBlank(uploadChecksum) + && ChecksumAlgorithm.forUploadChecksumHeader(uploadChecksum) == null) { + + throw new ChecksumAlgorithmNotSupportedException( + "The " + + HttpHeader.UPLOAD_CHECKSUM + + " header value " + + uploadChecksum + + " is not supported"); } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/ConcatenationExtension.java b/src/main/java/me/desair/tus/server/concatenation/ConcatenationExtension.java index e91495e..69c1a72 100644 --- a/src/main/java/me/desair/tus/server/concatenation/ConcatenationExtension.java +++ b/src/main/java/me/desair/tus/server/concatenation/ConcatenationExtension.java @@ -3,7 +3,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; @@ -13,33 +12,33 @@ import me.desair.tus.server.util.AbstractTusExtension; /** - * This extension can be used to concatenate multiple uploads into a single one enabling Clients - * to perform parallel uploads and to upload non-contiguous chunks. - * If the Server supports this extension, it MUST add concatenation to the Tus-Extension header. + * This extension can be used to concatenate multiple uploads into a single one enabling Clients to + * perform parallel uploads and to upload non-contiguous chunks. If the Server supports this + * extension, it MUST add concatenation to the Tus-Extension header. */ public class ConcatenationExtension extends AbstractTusExtension { - @Override - public String getName() { - return "concatenation"; - } + @Override + public String getName() { + return "concatenation"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.POST, HttpMethod.PATCH, HttpMethod.HEAD); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.POST, HttpMethod.PATCH, HttpMethod.HEAD); + } - @Override - protected void initValidators(List requestValidators) { - requestValidators.add(new PatchFinalUploadValidator()); - requestValidators.add(new NoUploadLengthOnFinalValidator()); - requestValidators.add(new PartialUploadsExistValidator()); - } + @Override + protected void initValidators(List requestValidators) { + requestValidators.add(new PatchFinalUploadValidator()); + requestValidators.add(new NoUploadLengthOnFinalValidator()); + requestValidators.add(new PartialUploadsExistValidator()); + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new ConcatenationOptionsRequestHandler()); - requestHandlers.add(new ConcatenationPostRequestHandler()); - requestHandlers.add(new ConcatenationHeadRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new ConcatenationOptionsRequestHandler()); + requestHandlers.add(new ConcatenationPostRequestHandler()); + requestHandlers.add(new ConcatenationHeadRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandler.java b/src/main/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandler.java index ab1bb9c..5f7cabc 100644 --- a/src/main/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandler.java +++ b/src/main/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandler.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.util.Objects; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.TusException; @@ -14,46 +13,53 @@ import me.desair.tus.server.util.TusServletResponse; /** - * The response to a HEAD request for a upload SHOULD NOT contain the Upload-Offset header unless the - * concatenation has been successfully finished. After successful concatenation, the Upload-Offset and - * Upload-Length MUST be set and their values MUST be equal. The value of the Upload-Offset header before - * concatenation is not defined for a upload. - *

- * The response to a HEAD request for a partial upload MUST contain the Upload-Offset header. Response to HEAD - * request against partial or upload MUST include the Upload-Concat header and its value as received in - * the upload creation request. + * The response to a HEAD request for a upload SHOULD NOT contain the Upload-Offset header unless + * the concatenation has been successfully finished. After successful concatenation, the + * Upload-Offset and Upload-Length MUST be set and their values MUST be equal. The value of the + * Upload-Offset header before concatenation is not defined for a upload. + * + *

The response to a HEAD request for a partial upload MUST contain the Upload-Offset header. + * Response to HEAD request against partial or upload MUST include the Upload-Concat header and its + * value as received in the upload creation request. */ public class ConcatenationHeadRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.HEAD.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.HEAD.equals(method); + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + UploadInfo uploadInfo = + uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); - if (!UploadType.REGULAR.equals(uploadInfo.getUploadType())) { - servletResponse.setHeader(HttpHeader.UPLOAD_CONCAT, uploadInfo.getUploadConcatHeaderValue()); - } + if (!UploadType.REGULAR.equals(uploadInfo.getUploadType())) { + servletResponse.setHeader(HttpHeader.UPLOAD_CONCAT, uploadInfo.getUploadConcatHeaderValue()); + } - if (UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { - if (uploadInfo.isUploadInProgress()) { - //Execute the merge function again to update our upload data - uploadStorageService.getUploadConcatenationService().merge(uploadInfo); - } + if (UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { + if (uploadInfo.isUploadInProgress()) { + // Execute the merge function again to update our upload data + uploadStorageService.getUploadConcatenationService().merge(uploadInfo); + } - if (uploadInfo.hasLength()) { - servletResponse.setHeader(HttpHeader.UPLOAD_LENGTH, Objects.toString(uploadInfo.getLength())); - } + if (uploadInfo.hasLength()) { + servletResponse.setHeader( + HttpHeader.UPLOAD_LENGTH, Objects.toString(uploadInfo.getLength())); + } - if (!uploadInfo.isUploadInProgress()) { - servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, Objects.toString(uploadInfo.getOffset())); - } - } + if (!uploadInfo.isUploadInProgress()) { + servletResponse.setHeader( + HttpHeader.UPLOAD_OFFSET, Objects.toString(uploadInfo.getOffset())); + } } + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandler.java index 515d463..979c39e 100644 --- a/src/main/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandler.java @@ -3,17 +3,16 @@ import me.desair.tus.server.util.AbstractExtensionRequestHandler; /** - * If the Server supports this extension, it MUST add concatenation to the Tus-Extension header. - * The Client MAY send the concatenation request while the partial uploads are still in progress. - * This feature MUST be explicitly announced by the Server by adding concatenation-unfinished to - * the Tus-Extension header. + * If the Server supports this extension, it MUST add concatenation to the Tus-Extension header. The + * Client MAY send the concatenation request while the partial uploads are still in progress. This + * feature MUST be explicitly announced by the Server by adding concatenation-unfinished to the + * Tus-Extension header. */ public class ConcatenationOptionsRequestHandler extends AbstractExtensionRequestHandler { - @Override - protected void appendExtensions(StringBuilder extensionBuilder) { - addExtension(extensionBuilder, "concatenation"); - addExtension(extensionBuilder, "concatenation-unfinished"); - } - + @Override + protected void appendExtensions(StringBuilder extensionBuilder) { + addExtension(extensionBuilder, "concatenation"); + addExtension(extensionBuilder, "concatenation-unfinished"); + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandler.java b/src/main/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandler.java index cc32eb1..9b96a6e 100644 --- a/src/main/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandler.java +++ b/src/main/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandler.java @@ -1,7 +1,6 @@ package me.desair.tus.server.concatenation; import java.io.IOException; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.TusException; @@ -15,46 +14,52 @@ import org.apache.commons.lang3.StringUtils; /** - * The Server MUST acknowledge a successful upload creation with the 201 Created status. - * The Server MUST set the Location header to the URL of the created resource. This URL MAY be absolute or relative. + * The Server MUST acknowledge a successful upload creation with the 201 Created status. The Server + * MUST set the Location header to the URL of the created resource. This URL MAY be absolute or + * relative. */ public class ConcatenationPostRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - //For post requests, the upload URI is part of the response - String uploadUri = servletResponse.getHeader(HttpHeader.LOCATION); - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadUri, ownerKey); + // For post requests, the upload URI is part of the response + String uploadUri = servletResponse.getHeader(HttpHeader.LOCATION); + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadUri, ownerKey); - if (uploadInfo != null) { + if (uploadInfo != null) { - String uploadConcatValue = servletRequest.getHeader(HttpHeader.UPLOAD_CONCAT); - if (StringUtils.equalsIgnoreCase(uploadConcatValue, "partial")) { - uploadInfo.setUploadType(UploadType.PARTIAL); + String uploadConcatValue = servletRequest.getHeader(HttpHeader.UPLOAD_CONCAT); + if (StringUtils.equalsIgnoreCase(uploadConcatValue, "partial")) { + uploadInfo.setUploadType(UploadType.PARTIAL); - } else if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final")) { - //reset the length, just to be sure - uploadInfo.setLength(null); - uploadInfo.setUploadType(UploadType.CONCATENATED); - uploadInfo.setConcatenationPartIds(Utils.parseConcatenationIDsFromHeader(uploadConcatValue)); + } else if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final")) { + // reset the length, just to be sure + uploadInfo.setLength(null); + uploadInfo.setUploadType(UploadType.CONCATENATED); + uploadInfo.setConcatenationPartIds( + Utils.parseConcatenationIDsFromHeader(uploadConcatValue)); - uploadStorageService.getUploadConcatenationService().merge(uploadInfo); + uploadStorageService.getUploadConcatenationService().merge(uploadInfo); - } else { - uploadInfo.setUploadType(UploadType.REGULAR); - } + } else { + uploadInfo.setUploadType(UploadType.REGULAR); + } - uploadInfo.setUploadConcatHeaderValue(uploadConcatValue); + uploadInfo.setUploadConcatHeaderValue(uploadConcatValue); - uploadStorageService.update(uploadInfo); - } + uploadStorageService.update(uploadInfo); } + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidator.java b/src/main/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidator.java index 16dee6b..69e10ab 100644 --- a/src/main/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidator.java +++ b/src/main/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidator.java @@ -1,8 +1,7 @@ package me.desair.tus.server.concatenation.validation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -11,28 +10,29 @@ import me.desair.tus.server.upload.UploadStorageService; import org.apache.commons.lang3.StringUtils; -/** - * The Client MUST NOT include the Upload-Length header in the upload creation. - */ +/** The Client MUST NOT include the Upload-Length header in the upload creation. */ public class NoUploadLengthOnFinalValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws IOException, TusException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - String uploadConcatValue = request.getHeader(HttpHeader.UPLOAD_CONCAT); + String uploadConcatValue = request.getHeader(HttpHeader.UPLOAD_CONCAT); - if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final") - && StringUtils.isNotBlank(request.getHeader(HttpHeader.UPLOAD_LENGTH))) { + if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final") + && StringUtils.isNotBlank(request.getHeader(HttpHeader.UPLOAD_LENGTH))) { - throw new UploadLengthNotAllowedOnConcatenationException( - "The upload length of a concatenated upload cannot be set"); - } + throw new UploadLengthNotAllowedOnConcatenationException( + "The upload length of a concatenated upload cannot be set"); } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidator.java b/src/main/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidator.java index dada748..a0f40df 100644 --- a/src/main/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidator.java +++ b/src/main/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidator.java @@ -1,8 +1,7 @@ package me.desair.tus.server.concatenation.validation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -13,34 +12,36 @@ import me.desair.tus.server.util.Utils; import org.apache.commons.lang3.StringUtils; -/** - * Validate that the IDs specified in the Upload-Concat header map to an existing upload - */ +/** Validate that the IDs specified in the Upload-Concat header map to an existing upload */ public class PartialUploadsExistValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws IOException, TusException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - String uploadConcatValue = request.getHeader(HttpHeader.UPLOAD_CONCAT); + String uploadConcatValue = request.getHeader(HttpHeader.UPLOAD_CONCAT); - if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final")) { + if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final")) { - for (String uploadUri : Utils.parseConcatenationIDsFromHeader(uploadConcatValue)) { + for (String uploadUri : Utils.parseConcatenationIDsFromHeader(uploadConcatValue)) { - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadUri, ownerKey); - if (uploadInfo == null) { - throw new InvalidPartialUploadIdException("The URI " + uploadUri - + " in Upload-Concat header does not match an existing upload"); - } - } + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadUri, ownerKey); + if (uploadInfo == null) { + throw new InvalidPartialUploadIdException( + "The URI " + + uploadUri + + " in Upload-Concat header does not match an existing upload"); } + } } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } - + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidator.java b/src/main/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidator.java index 3ea1bd4..a55328b 100644 --- a/src/main/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidator.java +++ b/src/main/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidator.java @@ -1,8 +1,7 @@ package me.desair.tus.server.concatenation.validation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.exception.PatchOnFinalUploadNotAllowedException; @@ -12,26 +11,29 @@ import me.desair.tus.server.upload.UploadType; /** - * The Server MUST respond with the 403 Forbidden status to PATCH requests against a upload URL - * and MUST NOT modify the or its partial uploads. + * The Server MUST respond with the 403 Forbidden status to PATCH requests against a upload URL and + * MUST NOT modify the or its partial uploads. */ public class PatchFinalUploadValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws IOException, TusException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); - if (uploadInfo != null && UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { - throw new PatchOnFinalUploadNotAllowedException("You cannot send a PATCH request for a " - + "concatenated upload URI"); - } + if (uploadInfo != null && UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { + throw new PatchOnFinalUploadNotAllowedException( + "You cannot send a PATCH request for a " + "concatenated upload URI"); } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandler.java b/src/main/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandler.java index 27a79bc..1e19a73 100644 --- a/src/main/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandler.java +++ b/src/main/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandler.java @@ -9,29 +9,32 @@ import me.desair.tus.server.util.TusServletResponse; /** - * The Tus-Resumable header MUST be included in every request and response except for OPTIONS requests. - * The value MUST be the version of the protocol used by the Client or the Server. + * The Tus-Resumable header MUST be included in every request and response except for OPTIONS + * requests. The value MUST be the version of the protocol used by the Client or the Server. */ public class CoreDefaultResponseHeadersHandler implements RequestHandler { - @Override - public boolean supports(HttpMethod method) { - return true; - } + @Override + public boolean supports(HttpMethod method) { + return true; + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) { - //Always set Tus-Resumable header - servletResponse.setHeader(HttpHeader.TUS_RESUMABLE, TusFileUploadService.TUS_API_VERSION); - //By default, set the Content-Length to 0 - servletResponse.setHeader(HttpHeader.CONTENT_LENGTH, "0"); - } + // Always set Tus-Resumable header + servletResponse.setHeader(HttpHeader.TUS_RESUMABLE, TusFileUploadService.TUS_API_VERSION); + // By default, set the Content-Length to 0 + servletResponse.setHeader(HttpHeader.CONTENT_LENGTH, "0"); + } - @Override - public boolean isErrorHandler() { - return true; - } + @Override + public boolean isErrorHandler() { + return true; + } } diff --git a/src/main/java/me/desair/tus/server/core/CoreHeadRequestHandler.java b/src/main/java/me/desair/tus/server/core/CoreHeadRequestHandler.java index 33bbd48..416cbef 100644 --- a/src/main/java/me/desair/tus/server/core/CoreHeadRequestHandler.java +++ b/src/main/java/me/desair/tus/server/core/CoreHeadRequestHandler.java @@ -1,9 +1,8 @@ package me.desair.tus.server.core; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Objects; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadInfo; @@ -13,39 +12,46 @@ import me.desair.tus.server.util.TusServletRequest; import me.desair.tus.server.util.TusServletResponse; -/** A HEAD request is used to determine the offset at which the upload should be continued. - *

- * The Server MUST always include the Upload-Offset header in the response for a HEAD request, - * even if the offset is 0, or the upload is already considered completed. If the size of the upload is known, - * the Server MUST include the Upload-Length header in the response. - *

- * The Server MUST prevent the client and/or proxies from caching the response by adding - * the Cache-Control: no-store header to the response. +/** + * A HEAD request is used to determine the offset at which the upload should be continued. + * + *

The Server MUST always include the Upload-Offset header in the response for a HEAD request, + * even if the offset is 0, or the upload is already considered completed. If the size of the upload + * is known, the Server MUST include the Upload-Length header in the response. + * + *

The Server MUST prevent the client and/or proxies from caching the response by adding the + * Cache-Control: no-store header to the response. */ public class CoreHeadRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.HEAD.equals(method); + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.HEAD.equals(method); + } + + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException { + + UploadInfo uploadInfo = + uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + + if (!UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { + + if (uploadInfo.hasLength()) { + servletResponse.setHeader( + HttpHeader.UPLOAD_LENGTH, Objects.toString(uploadInfo.getLength())); + } + servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, Objects.toString(uploadInfo.getOffset())); } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException { - - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + servletResponse.setHeader(HttpHeader.CACHE_CONTROL, "no-store"); - if (!UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { - - if (uploadInfo.hasLength()) { - servletResponse.setHeader(HttpHeader.UPLOAD_LENGTH, Objects.toString(uploadInfo.getLength())); - } - servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, Objects.toString(uploadInfo.getOffset())); - } - - servletResponse.setHeader(HttpHeader.CACHE_CONTROL, "no-store"); - - servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); - } + servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + } } diff --git a/src/main/java/me/desair/tus/server/core/CoreOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/core/CoreOptionsRequestHandler.java index 150b12e..d31069f 100644 --- a/src/main/java/me/desair/tus/server/core/CoreOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/core/CoreOptionsRequestHandler.java @@ -1,8 +1,7 @@ package me.desair.tus.server.core; -import java.util.Objects; import jakarta.servlet.http.HttpServletResponse; - +import java.util.Objects; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.TusFileUploadService; @@ -12,29 +11,32 @@ import me.desair.tus.server.util.TusServletResponse; /** - * An OPTIONS request MAY be used to gather information about the Server’s current configuration. A successful - * response indicated by the 204 No Content or 200 OK status MUST contain the Tus-Version header. It MAY include - * the Tus-Extension and Tus-Max-Size headers. + * An OPTIONS request MAY be used to gather information about the Server’s current configuration. A + * successful response indicated by the 204 No Content or 200 OK status MUST contain the Tus-Version + * header. It MAY include the Tus-Extension and Tus-Max-Size headers. */ public class CoreOptionsRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.OPTIONS.equals(method); + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.OPTIONS.equals(method); + } + + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) { + + if (uploadStorageService.getMaxUploadSize() > 0) { + servletResponse.setHeader( + HttpHeader.TUS_MAX_SIZE, Objects.toString(uploadStorageService.getMaxUploadSize())); } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) { + servletResponse.setHeader(HttpHeader.TUS_VERSION, TusFileUploadService.TUS_API_VERSION); - if (uploadStorageService.getMaxUploadSize() > 0) { - servletResponse.setHeader(HttpHeader.TUS_MAX_SIZE, - Objects.toString(uploadStorageService.getMaxUploadSize())); - } - - servletResponse.setHeader(HttpHeader.TUS_VERSION, TusFileUploadService.TUS_API_VERSION); - - servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); - } + servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + } } diff --git a/src/main/java/me/desair/tus/server/core/CorePatchRequestHandler.java b/src/main/java/me/desair/tus/server/core/CorePatchRequestHandler.java index 882dc13..85894a7 100644 --- a/src/main/java/me/desair/tus/server/core/CorePatchRequestHandler.java +++ b/src/main/java/me/desair/tus/server/core/CorePatchRequestHandler.java @@ -1,9 +1,8 @@ package me.desair.tus.server.core; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Objects; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.TusException; @@ -17,52 +16,63 @@ import org.slf4j.LoggerFactory; /** - * The Server SHOULD accept PATCH requests against any upload URL and apply the bytes contained in the message at - * the given offset specified by the Upload-Offset header. - *

- * The Server MUST acknowledge successful PATCH requests with the 204 No Content status. It MUST include the - * Upload-Offset header containing the new offset. The new offset MUST be the sum of the offset before the PATCH - * request and the number of bytes received and processed or stored during the current PATCH request. + * The Server SHOULD accept PATCH requests against any upload URL and apply the bytes contained in + * the message at the given offset specified by the Upload-Offset header. + * + *

The Server MUST acknowledge successful PATCH requests with the 204 No Content status. It MUST + * include the Upload-Offset header containing the new offset. The new offset MUST be the sum of the + * offset before the PATCH request and the number of bytes received and processed or stored during + * the current PATCH request. */ public class CorePatchRequestHandler extends AbstractRequestHandler { - private static final Logger log = LoggerFactory.getLogger(CorePatchRequestHandler.class); + private static final Logger log = LoggerFactory.getLogger(CorePatchRequestHandler.class); - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - boolean found = true; - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + boolean found = true; + UploadInfo uploadInfo = + uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); - if (uploadInfo == null) { - found = false; - } else if (uploadInfo.isUploadInProgress()) { - try { - uploadInfo = uploadStorageService.append(uploadInfo, servletRequest.getContentInputStream()); - } catch (UploadNotFoundException e) { - found = false; - } - } + if (uploadInfo == null) { + found = false; + } else if (uploadInfo.isUploadInProgress()) { + try { + uploadInfo = + uploadStorageService.append(uploadInfo, servletRequest.getContentInputStream()); + } catch (UploadNotFoundException e) { + found = false; + } + } - if (found) { - servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, Objects.toString(uploadInfo.getOffset())); - servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + if (found) { + servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, Objects.toString(uploadInfo.getOffset())); + servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); - if (!uploadInfo.isUploadInProgress()) { - log.info("Upload with ID {} at location {} finished successfully", - uploadInfo.getId(), servletRequest.getRequestURI()); - } - } else { - log.error("The patch request handler could not find the upload for URL " + servletRequest.getRequestURI() - + ". This means something is really wrong the request validators!"); - servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } + if (!uploadInfo.isUploadInProgress()) { + log.info( + "Upload with ID {} at location {} finished successfully", + uploadInfo.getId(), + servletRequest.getRequestURI()); + } + } else { + log.error( + "The patch request handler could not find the upload for URL " + + servletRequest.getRequestURI() + + ". This means something is really wrong the request validators!"); + servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } + } } diff --git a/src/main/java/me/desair/tus/server/core/CoreProtocol.java b/src/main/java/me/desair/tus/server/core/CoreProtocol.java index 0a45c78..10ecfe0 100644 --- a/src/main/java/me/desair/tus/server/core/CoreProtocol.java +++ b/src/main/java/me/desair/tus/server/core/CoreProtocol.java @@ -3,7 +3,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; @@ -16,37 +15,37 @@ import me.desair.tus.server.util.AbstractTusExtension; /** - * The core protocol describes how to resume an interrupted upload. - * It assumes that you already have a URL for the upload, usually created via the Creation extension. - * All Clients and Servers MUST implement the core protocol. + * The core protocol describes how to resume an interrupted upload. It assumes that you already have + * a URL for the upload, usually created via the Creation extension. All Clients and Servers MUST + * implement the core protocol. */ public class CoreProtocol extends AbstractTusExtension { - @Override - public String getName() { - return "core"; - } + @Override + public String getName() { + return "core"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.PATCH); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.PATCH); + } - @Override - protected void initValidators(List validators) { - validators.add(new HttpMethodValidator()); - validators.add(new TusResumableValidator()); - validators.add(new IdExistsValidator()); - validators.add(new ContentTypeValidator()); - validators.add(new UploadOffsetValidator()); - validators.add(new ContentLengthValidator()); - } + @Override + protected void initValidators(List validators) { + validators.add(new HttpMethodValidator()); + validators.add(new TusResumableValidator()); + validators.add(new IdExistsValidator()); + validators.add(new ContentTypeValidator()); + validators.add(new UploadOffsetValidator()); + validators.add(new ContentLengthValidator()); + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new CoreDefaultResponseHeadersHandler()); - requestHandlers.add(new CoreHeadRequestHandler()); - requestHandlers.add(new CorePatchRequestHandler()); - requestHandlers.add(new CoreOptionsRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new CoreDefaultResponseHeadersHandler()); + requestHandlers.add(new CoreHeadRequestHandler()); + requestHandlers.add(new CorePatchRequestHandler()); + requestHandlers.add(new CoreOptionsRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/core/validation/ContentLengthValidator.java b/src/main/java/me/desair/tus/server/core/validation/ContentLengthValidator.java index c7c0f16..e787289 100644 --- a/src/main/java/me/desair/tus/server/core/validation/ContentLengthValidator.java +++ b/src/main/java/me/desair/tus/server/core/validation/ContentLengthValidator.java @@ -1,8 +1,7 @@ package me.desair.tus.server.core.validation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -13,34 +12,42 @@ import me.desair.tus.server.util.Utils; /** - * Validate that the given upload length in combination with the bytes we already received, - * does not exceed the declared initial length on upload creation. + * Validate that the given upload length in combination with the bytes we already received, does not + * exceed the declared initial length on upload creation. */ public class ContentLengthValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException, IOException { - - Long contentLength = Utils.getLongHeader(request, HttpHeader.CONTENT_LENGTH); - - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); - - if (contentLength != null - && uploadInfo != null - && uploadInfo.hasLength() - && (uploadInfo.getOffset() + contentLength > uploadInfo.getLength())) { - - throw new InvalidContentLengthException("The " + HttpHeader.CONTENT_LENGTH + " value " + contentLength - + " in combination with the current offset " + uploadInfo.getOffset() - + " exceeds the declared upload length " + uploadInfo.getLength()); - } - } - - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException, IOException { + + Long contentLength = Utils.getLongHeader(request, HttpHeader.CONTENT_LENGTH); + + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); + + if (contentLength != null + && uploadInfo != null + && uploadInfo.hasLength() + && (uploadInfo.getOffset() + contentLength > uploadInfo.getLength())) { + + throw new InvalidContentLengthException( + "The " + + HttpHeader.CONTENT_LENGTH + + " value " + + contentLength + + " in combination with the current offset " + + uploadInfo.getOffset() + + " exceeds the declared upload length " + + uploadInfo.getLength()); } + } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/core/validation/ContentTypeValidator.java b/src/main/java/me/desair/tus/server/core/validation/ContentTypeValidator.java index b90cd66..939006e 100644 --- a/src/main/java/me/desair/tus/server/core/validation/ContentTypeValidator.java +++ b/src/main/java/me/desair/tus/server/core/validation/ContentTypeValidator.java @@ -1,7 +1,6 @@ package me.desair.tus.server.core.validation; import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -10,27 +9,31 @@ import me.desair.tus.server.upload.UploadStorageService; import me.desair.tus.server.util.Utils; -/** - * All PATCH requests MUST use Content-Type: application/offset+octet-stream. - */ +/** All PATCH requests MUST use Content-Type: application/offset+octet-stream. */ public class ContentTypeValidator implements RequestValidator { - static final String APPLICATION_OFFSET_OCTET_STREAM = "application/offset+octet-stream"; - - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) throws TusException { - - String contentType = Utils.getHeader(request, HttpHeader.CONTENT_TYPE); - if (!APPLICATION_OFFSET_OCTET_STREAM.equals(contentType)) { - throw new InvalidContentTypeException("The " + HttpHeader.CONTENT_TYPE + " header must contain value " - + APPLICATION_OFFSET_OCTET_STREAM); - } - } - - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); + static final String APPLICATION_OFFSET_OCTET_STREAM = "application/offset+octet-stream"; + + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { + + String contentType = Utils.getHeader(request, HttpHeader.CONTENT_TYPE); + if (!APPLICATION_OFFSET_OCTET_STREAM.equals(contentType)) { + throw new InvalidContentTypeException( + "The " + + HttpHeader.CONTENT_TYPE + + " header must contain value " + + APPLICATION_OFFSET_OCTET_STREAM); } + } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/core/validation/HttpMethodValidator.java b/src/main/java/me/desair/tus/server/core/validation/HttpMethodValidator.java index 7238893..e12d7f5 100644 --- a/src/main/java/me/desair/tus/server/core/validation/HttpMethodValidator.java +++ b/src/main/java/me/desair/tus/server/core/validation/HttpMethodValidator.java @@ -1,30 +1,31 @@ package me.desair.tus.server.core.validation; import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.exception.TusException; import me.desair.tus.server.exception.UnsupportedMethodException; import me.desair.tus.server.upload.UploadStorageService; -/** - * Class to validate if the current HTTP method is valid - */ +/** Class to validate if the current HTTP method is valid */ public class HttpMethodValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) throws TusException { - - if (method == null) { - throw new UnsupportedMethodException("The HTTP method " + request.getMethod() + " is not supported"); - } - } + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { - @Override - public boolean supports(HttpMethod method) { - return true; + if (method == null) { + throw new UnsupportedMethodException( + "The HTTP method " + request.getMethod() + " is not supported"); } + } + @Override + public boolean supports(HttpMethod method) { + return true; + } } diff --git a/src/main/java/me/desair/tus/server/core/validation/IdExistsValidator.java b/src/main/java/me/desair/tus/server/core/validation/IdExistsValidator.java index 7dfcb09..b5190e1 100644 --- a/src/main/java/me/desair/tus/server/core/validation/IdExistsValidator.java +++ b/src/main/java/me/desair/tus/server/core/validation/IdExistsValidator.java @@ -1,8 +1,7 @@ package me.desair.tus.server.core.validation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; - +import java.io.IOException; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.exception.TusException; @@ -10,30 +9,35 @@ import me.desair.tus.server.upload.UploadStorageService; /** - * If the resource is not found, the Server SHOULD return either the - * 404 Not Found, 410 Gone or 403 Forbidden status without the Upload-Offset header. + * If the resource is not found, the Server SHOULD return either the 404 Not Found, 410 Gone or 403 + * Forbidden status without the Upload-Offset header. */ public class IdExistsValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException, IOException { - - if (uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey) == null) { - throw new UploadNotFoundException("The upload for path " + request.getRequestURI() - + " and owner " + ownerKey + " was not found."); - } - } + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException, IOException { - @Override - public boolean supports(HttpMethod method) { - return method != null && ( - HttpMethod.HEAD.equals(method) - || HttpMethod.PATCH.equals(method) - || HttpMethod.DELETE.equals(method) - || HttpMethod.GET.equals(method) - ); + if (uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey) == null) { + throw new UploadNotFoundException( + "The upload for path " + + request.getRequestURI() + + " and owner " + + ownerKey + + " was not found."); } + } + @Override + public boolean supports(HttpMethod method) { + return method != null + && (HttpMethod.HEAD.equals(method) + || HttpMethod.PATCH.equals(method) + || HttpMethod.DELETE.equals(method) + || HttpMethod.GET.equals(method)); + } } diff --git a/src/main/java/me/desair/tus/server/core/validation/TusResumableValidator.java b/src/main/java/me/desair/tus/server/core/validation/TusResumableValidator.java index 095b98d..b818d82 100644 --- a/src/main/java/me/desair/tus/server/core/validation/TusResumableValidator.java +++ b/src/main/java/me/desair/tus/server/core/validation/TusResumableValidator.java @@ -1,7 +1,6 @@ package me.desair.tus.server.core.validation; import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -12,31 +11,36 @@ import me.desair.tus.server.util.Utils; import org.apache.commons.lang3.StringUtils; -/** Class that will validate if the tus version in the request corresponds to our implementation version - *

- * The Tus-Resumable header MUST be included in every request and response except for OPTIONS requests. - * The value MUST be the version of the protocol used by the Client or the Server. - * If the the version specified by the Client is not supported by the Server, it MUST respond with the - * 412 Precondition Failed status and MUST include the Tus-Version header into the response. - * In addition, the Server MUST NOT process the request. - *

- * (https://tus.io/protocols/resumable-upload.html#tus-resumable) +/** + * Class that will validate if the tus version in the request corresponds to our implementation + * version + * + *

The Tus-Resumable header MUST be included in every request and response except for OPTIONS + * requests. The value MUST be the version of the protocol used by the Client or the Server. If the + * the version specified by the Client is not supported by the Server, it MUST respond with the 412 + * Precondition Failed status and MUST include the Tus-Version header into the response. In + * addition, the Server MUST NOT process the request. + * + *

(https://tus.io/protocols/resumable-upload.html#tus-resumable) */ public class TusResumableValidator implements RequestValidator { - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException { + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { - String requestVersion = Utils.getHeader(request, HttpHeader.TUS_RESUMABLE); - if (!StringUtils.equals(requestVersion, TusFileUploadService.TUS_API_VERSION)) { - throw new InvalidTusResumableException("This server does not support tus protocol version " - + requestVersion); - } + String requestVersion = Utils.getHeader(request, HttpHeader.TUS_RESUMABLE); + if (!StringUtils.equals(requestVersion, TusFileUploadService.TUS_API_VERSION)) { + throw new InvalidTusResumableException( + "This server does not support tus protocol version " + requestVersion); } + } - @Override - public boolean supports(HttpMethod method) { - return !HttpMethod.OPTIONS.equals(method) && !HttpMethod.GET.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return !HttpMethod.OPTIONS.equals(method) && !HttpMethod.GET.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/core/validation/UploadOffsetValidator.java b/src/main/java/me/desair/tus/server/core/validation/UploadOffsetValidator.java index 195ac30..61ae3e9 100644 --- a/src/main/java/me/desair/tus/server/core/validation/UploadOffsetValidator.java +++ b/src/main/java/me/desair/tus/server/core/validation/UploadOffsetValidator.java @@ -1,9 +1,8 @@ package me.desair.tus.server.core.validation; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Objects; -import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -15,34 +14,38 @@ import org.apache.commons.lang3.StringUtils; /** - * The Upload-Offset header’s value MUST be equal to the current offset of the resource. - * If the offsets do not match, the Server MUST respond with the - * 409 Conflict status without modifying the upload resource. + * The Upload-Offset header’s value MUST be equal to the current offset of the resource. If the + * offsets do not match, the Server MUST respond with the 409 Conflict status without modifying the + * upload resource. */ public class UploadOffsetValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws IOException, TusException { - - String uploadOffset = Utils.getHeader(request, HttpHeader.UPLOAD_OFFSET); - - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); - - if (uploadInfo != null) { - String expectedOffset = Objects.toString(uploadInfo.getOffset()); - if (!StringUtils.equals(expectedOffset, uploadOffset)) { - throw new UploadOffsetMismatchException("The Upload-Offset was " - + StringUtils.trimToNull(uploadOffset) + " but expected " + expectedOffset); - } - } - - } - - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { + + String uploadOffset = Utils.getHeader(request, HttpHeader.UPLOAD_OFFSET); + + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(request.getRequestURI(), ownerKey); + + if (uploadInfo != null) { + String expectedOffset = Objects.toString(uploadInfo.getOffset()); + if (!StringUtils.equals(expectedOffset, uploadOffset)) { + throw new UploadOffsetMismatchException( + "The Upload-Offset was " + + StringUtils.trimToNull(uploadOffset) + + " but expected " + + expectedOffset); + } } + } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/creation/CreationExtension.java b/src/main/java/me/desair/tus/server/creation/CreationExtension.java index 204b473..770b132 100644 --- a/src/main/java/me/desair/tus/server/creation/CreationExtension.java +++ b/src/main/java/me/desair/tus/server/creation/CreationExtension.java @@ -3,7 +3,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; @@ -14,33 +13,34 @@ import me.desair.tus.server.util.AbstractTusExtension; /** - * The Client and the Server SHOULD implement the upload creation extension. If the Server supports this extension. + * The Client and the Server SHOULD implement the upload creation extension. If the Server supports + * this extension. */ public class CreationExtension extends AbstractTusExtension { - @Override - public String getName() { - return "creation"; - } + @Override + public String getName() { + return "creation"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.PATCH, HttpMethod.POST); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.PATCH, HttpMethod.POST); + } - @Override - protected void initValidators(List requestValidators) { - requestValidators.add(new PostURIValidator()); - requestValidators.add(new PostEmptyRequestValidator()); - requestValidators.add(new UploadDeferLengthValidator()); - requestValidators.add(new UploadLengthValidator()); - } + @Override + protected void initValidators(List requestValidators) { + requestValidators.add(new PostURIValidator()); + requestValidators.add(new PostEmptyRequestValidator()); + requestValidators.add(new UploadDeferLengthValidator()); + requestValidators.add(new UploadLengthValidator()); + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new CreationHeadRequestHandler()); - requestHandlers.add(new CreationPatchRequestHandler()); - requestHandlers.add(new CreationPostRequestHandler()); - requestHandlers.add(new CreationOptionsRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new CreationHeadRequestHandler()); + requestHandlers.add(new CreationPatchRequestHandler()); + requestHandlers.add(new CreationPostRequestHandler()); + requestHandlers.add(new CreationOptionsRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/creation/CreationHeadRequestHandler.java b/src/main/java/me/desair/tus/server/creation/CreationHeadRequestHandler.java index c3417cb..1f5a375 100644 --- a/src/main/java/me/desair/tus/server/creation/CreationHeadRequestHandler.java +++ b/src/main/java/me/desair/tus/server/creation/CreationHeadRequestHandler.java @@ -1,7 +1,6 @@ package me.desair.tus.server.creation; import java.io.IOException; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadInfo; @@ -11,34 +10,40 @@ import me.desair.tus.server.util.TusServletRequest; import me.desair.tus.server.util.TusServletResponse; -/** A HEAD request can be used to retrieve the metadata that was supplied at creation. - *

- * If an upload contains additional metadata, responses to HEAD requests MUST include the Upload-Metadata - * header and its value as specified by the Client during the creation. - *

- * As long as the length of the upload is not known, the Server MUST set Upload-Defer-Length: 1 in - * all responses to HEAD requests. +/** + * A HEAD request can be used to retrieve the metadata that was supplied at creation. + * + *

If an upload contains additional metadata, responses to HEAD requests MUST include the + * Upload-Metadata header and its value as specified by the Client during the creation. + * + *

As long as the length of the upload is not known, the Server MUST set Upload-Defer-Length: 1 + * in all responses to HEAD requests. */ public class CreationHeadRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.HEAD.equals(method); + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.HEAD.equals(method); + } + + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException { + + UploadInfo uploadInfo = + uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + + if (uploadInfo.hasMetadata()) { + servletResponse.setHeader(HttpHeader.UPLOAD_METADATA, uploadInfo.getEncodedMetadata()); } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException { - - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); - - if (uploadInfo.hasMetadata()) { - servletResponse.setHeader(HttpHeader.UPLOAD_METADATA, uploadInfo.getEncodedMetadata()); - } - - if (!uploadInfo.hasLength() && !UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { - servletResponse.setHeader(HttpHeader.UPLOAD_DEFER_LENGTH, "1"); - } + if (!uploadInfo.hasLength() && !UploadType.CONCATENATED.equals(uploadInfo.getUploadType())) { + servletResponse.setHeader(HttpHeader.UPLOAD_DEFER_LENGTH, "1"); } + } } diff --git a/src/main/java/me/desair/tus/server/creation/CreationOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/creation/CreationOptionsRequestHandler.java index b6f53bd..30657a2 100644 --- a/src/main/java/me/desair/tus/server/creation/CreationOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/creation/CreationOptionsRequestHandler.java @@ -3,17 +3,17 @@ import me.desair.tus.server.util.AbstractExtensionRequestHandler; /** - * The Client and the Server SHOULD implement the upload creation extension. - * If the Server supports this extension, it MUST add creation to the Tus-Extension header. - *

- * If the Server supports deferring length, it MUST add creation-defer-length to the Tus-Extension header. + * The Client and the Server SHOULD implement the upload creation extension. If the Server supports + * this extension, it MUST add creation to the Tus-Extension header. + * + *

If the Server supports deferring length, it MUST add creation-defer-length to the + * Tus-Extension header. */ public class CreationOptionsRequestHandler extends AbstractExtensionRequestHandler { - @Override - protected void appendExtensions(StringBuilder extensionBuilder) { - addExtension(extensionBuilder, "creation"); - addExtension(extensionBuilder, "creation-defer-length"); - } - + @Override + protected void appendExtensions(StringBuilder extensionBuilder) { + addExtension(extensionBuilder, "creation"); + addExtension(extensionBuilder, "creation-defer-length"); + } } diff --git a/src/main/java/me/desair/tus/server/creation/CreationPatchRequestHandler.java b/src/main/java/me/desair/tus/server/creation/CreationPatchRequestHandler.java index fd07b9c..c6baf32 100644 --- a/src/main/java/me/desair/tus/server/creation/CreationPatchRequestHandler.java +++ b/src/main/java/me/desair/tus/server/creation/CreationPatchRequestHandler.java @@ -1,8 +1,7 @@ package me.desair.tus.server.creation; -import java.io.IOException; import jakarta.servlet.http.HttpServletResponse; - +import java.io.IOException; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.UploadNotFoundException; @@ -16,38 +15,45 @@ import org.slf4j.LoggerFactory; /** - * Upload-Defer-Length: 1 if upload size is not known at the time. Once it is known the Client MUST set - * the Upload-Length header in the next PATCH request. Once set the length MUST NOT be changed. + * Upload-Defer-Length: 1 if upload size is not known at the time. Once it is known the Client MUST + * set the Upload-Length header in the next PATCH request. Once set the length MUST NOT be changed. */ public class CreationPatchRequestHandler extends AbstractRequestHandler { - private static final Logger log = LoggerFactory.getLogger(CreationPatchRequestHandler.class); - - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method); - } - - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException { - - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); - - if (uploadInfo != null && !uploadInfo.hasLength()) { - Long uploadLength = Utils.getLongHeader(servletRequest, HttpHeader.UPLOAD_LENGTH); - if (uploadLength != null) { - uploadInfo.setLength(uploadLength); - try { - uploadStorageService.update(uploadInfo); - } catch (UploadNotFoundException e) { - log.error("The patch request handler could not find the upload for URL " - + servletRequest.getRequestURI() - + ". This means something is really wrong the request validators!", e); - servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } + private static final Logger log = LoggerFactory.getLogger(CreationPatchRequestHandler.class); + + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method); + } + + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException { + + UploadInfo uploadInfo = + uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + + if (uploadInfo != null && !uploadInfo.hasLength()) { + Long uploadLength = Utils.getLongHeader(servletRequest, HttpHeader.UPLOAD_LENGTH); + if (uploadLength != null) { + uploadInfo.setLength(uploadLength); + try { + uploadStorageService.update(uploadInfo); + } catch (UploadNotFoundException e) { + log.error( + "The patch request handler could not find the upload for URL " + + servletRequest.getRequestURI() + + ". This means something is really wrong the request validators!", + e); + servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } + } } + } } diff --git a/src/main/java/me/desair/tus/server/creation/CreationPostRequestHandler.java b/src/main/java/me/desair/tus/server/creation/CreationPostRequestHandler.java index 1bbf9ea..e115bf8 100644 --- a/src/main/java/me/desair/tus/server/creation/CreationPostRequestHandler.java +++ b/src/main/java/me/desair/tus/server/creation/CreationPostRequestHandler.java @@ -1,9 +1,8 @@ package me.desair.tus.server.creation; -import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - +import java.io.IOException; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadInfo; @@ -17,52 +16,64 @@ import org.slf4j.LoggerFactory; /** - * The Server MUST acknowledge a successful upload creation with the 201 Created status. - * The Server MUST set the Location header to the URL of the created resource. This URL MAY be absolute or relative. + * The Server MUST acknowledge a successful upload creation with the 201 Created status. The Server + * MUST set the Location header to the URL of the created resource. This URL MAY be absolute or + * relative. */ public class CreationPostRequestHandler extends AbstractRequestHandler { - private static final Logger log = LoggerFactory.getLogger(CreationPostRequestHandler.class); - - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } + private static final Logger log = LoggerFactory.getLogger(CreationPostRequestHandler.class); - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException { + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } - UploadInfo info = buildUploadInfo(servletRequest); - info = uploadStorageService.create(info, ownerKey); + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException { - //We've already validated that the current request URL matches our upload URL so we can safely use it. - String uploadURI = servletRequest.getRequestURI(); + UploadInfo info = buildUploadInfo(servletRequest); + info = uploadStorageService.create(info, ownerKey); - //It's important to return relative UPLOAD URLs in the Location header in order to support HTTPS proxies - //that sit in front of the web app - String url = uploadURI + (StringUtils.endsWith(uploadURI, "/") ? "" : "/") + info.getId(); - servletResponse.setHeader(HttpHeader.LOCATION, url); - servletResponse.setStatus(HttpServletResponse.SC_CREATED); + // We've already validated that the current request URL matches our upload URL so we can + // safely + // use it. + String uploadURI = servletRequest.getRequestURI(); - log.info("Created upload with ID {} at {} for ip address {} with location {}", - info.getId(), info.getCreationTimestamp(), info.getCreatorIpAddresses(), url); - } + // It's important to return relative UPLOAD URLs in the Location header in order to support + // HTTPS proxies + // that sit in front of the web app + String url = uploadURI + (StringUtils.endsWith(uploadURI, "/") ? "" : "/") + info.getId(); + servletResponse.setHeader(HttpHeader.LOCATION, url); + servletResponse.setStatus(HttpServletResponse.SC_CREATED); - private UploadInfo buildUploadInfo(HttpServletRequest servletRequest) { - UploadInfo info = new UploadInfo(servletRequest); + log.info( + "Created upload with ID {} at {} for ip address {} with location {}", + info.getId(), + info.getCreationTimestamp(), + info.getCreatorIpAddresses(), + url); + } - Long length = Utils.getLongHeader(servletRequest, HttpHeader.UPLOAD_LENGTH); - if (length != null) { - info.setLength(length); - } + private UploadInfo buildUploadInfo(HttpServletRequest servletRequest) { + UploadInfo info = new UploadInfo(servletRequest); - String metadata = Utils.getHeader(servletRequest, HttpHeader.UPLOAD_METADATA); - if (StringUtils.isNotBlank(metadata)) { - info.setEncodedMetadata(metadata); - } + Long length = Utils.getLongHeader(servletRequest, HttpHeader.UPLOAD_LENGTH); + if (length != null) { + info.setLength(length); + } - return info; + String metadata = Utils.getHeader(servletRequest, HttpHeader.UPLOAD_METADATA); + if (StringUtils.isNotBlank(metadata)) { + info.setEncodedMetadata(metadata); } + + return info; + } } diff --git a/src/main/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidator.java b/src/main/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidator.java index acff53b..12c860a 100644 --- a/src/main/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidator.java +++ b/src/main/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidator.java @@ -1,7 +1,6 @@ package me.desair.tus.server.creation.validation; import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -10,25 +9,26 @@ import me.desair.tus.server.upload.UploadStorageService; import me.desair.tus.server.util.Utils; -/** - * An empty POST request is used to create a new upload resource. - */ +/** An empty POST request is used to create a new upload resource. */ public class PostEmptyRequestValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { - Long contentLength = Utils.getLongHeader(request, HttpHeader.CONTENT_LENGTH); - if (contentLength != null && contentLength > 0) { - throw new InvalidContentLengthException("A POST request should have a Content-Length header with value " - + "0 and no content"); - } + Long contentLength = Utils.getLongHeader(request, HttpHeader.CONTENT_LENGTH); + if (contentLength != null && contentLength > 0) { + throw new InvalidContentLengthException( + "A POST request should have a Content-Length header with value " + "0 and no content"); } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/creation/validation/PostURIValidator.java b/src/main/java/me/desair/tus/server/creation/validation/PostURIValidator.java index b3f522f..3b5ed1b 100644 --- a/src/main/java/me/desair/tus/server/creation/validation/PostURIValidator.java +++ b/src/main/java/me/desair/tus/server/creation/validation/PostURIValidator.java @@ -1,9 +1,8 @@ package me.desair.tus.server.creation.validation; +import jakarta.servlet.http.HttpServletRequest; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.exception.PostOnInvalidRequestURIException; @@ -11,36 +10,40 @@ import me.desair.tus.server.upload.UploadStorageService; /** - * The Client MUST send a POST request against a known upload creation URL to request a new upload resource. + * The Client MUST send a POST request against a known upload creation URL to request a new upload + * resource. */ public class PostURIValidator implements RequestValidator { - private Pattern uploadUriPattern = null; + private Pattern uploadUriPattern = null; - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { - Matcher uploadUriMatcher = getUploadUriPattern(uploadStorageService).matcher(request.getRequestURI()); + Matcher uploadUriMatcher = + getUploadUriPattern(uploadStorageService).matcher(request.getRequestURI()); - if (!uploadUriMatcher.matches()) { - throw new PostOnInvalidRequestURIException("POST requests have to be sent to '" - + uploadStorageService.getUploadURI() + "'. "); - } + if (!uploadUriMatcher.matches()) { + throw new PostOnInvalidRequestURIException( + "POST requests have to be sent to '" + uploadStorageService.getUploadURI() + "'. "); } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } - private Pattern getUploadUriPattern(UploadStorageService uploadStorageService) { - if (uploadUriPattern == null) { - //A POST request should match the full URI - uploadUriPattern = Pattern.compile("^" + uploadStorageService.getUploadURI() + "$"); - } - return uploadUriPattern; + private Pattern getUploadUriPattern(UploadStorageService uploadStorageService) { + if (uploadUriPattern == null) { + // A POST request should match the full URI + uploadUriPattern = Pattern.compile("^" + uploadStorageService.getUploadURI() + "$"); } - + return uploadUriPattern; + } } diff --git a/src/main/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidator.java b/src/main/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidator.java index 1a3f406..88cc7d4 100644 --- a/src/main/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidator.java +++ b/src/main/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidator.java @@ -1,7 +1,6 @@ package me.desair.tus.server.creation.validation; import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -12,45 +11,54 @@ import org.apache.commons.lang3.StringUtils; /** - * The request MUST include one of the following headers: - * a) Upload-Length to indicate the size of an entire upload in bytes. - * b) Upload-Defer-Length: 1 if upload size is not known at the time. + * The request MUST include one of the following headers: a) Upload-Length to indicate the size of + * an entire upload in bytes. b) Upload-Defer-Length: 1 if upload size is not known at the time. */ public class UploadDeferLengthValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException { - - boolean uploadLength = false; - boolean deferredLength = false; - boolean concatenatedUpload = false; - - if (StringUtils.isNumeric(Utils.getHeader(request, HttpHeader.UPLOAD_LENGTH))) { - uploadLength = true; - } - - if (Utils.getHeader(request, HttpHeader.UPLOAD_DEFER_LENGTH).equals("1")) { - deferredLength = true; - } - - String uploadConcatValue = request.getHeader(HttpHeader.UPLOAD_CONCAT); - if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final")) { - concatenatedUpload = true; - } - - if (!concatenatedUpload && !uploadLength && !deferredLength) { - throw new InvalidUploadLengthException("No valid value was found in headers " + HttpHeader.UPLOAD_LENGTH - + " and " + HttpHeader.UPLOAD_DEFER_LENGTH); - } else if (uploadLength && deferredLength) { - throw new InvalidUploadLengthException("A POST request cannot contain both " + HttpHeader.UPLOAD_LENGTH - + " and " + HttpHeader.UPLOAD_DEFER_LENGTH + " headers."); - } + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { + + boolean uploadLength = false; + boolean deferredLength = false; + boolean concatenatedUpload = false; + + if (StringUtils.isNumeric(Utils.getHeader(request, HttpHeader.UPLOAD_LENGTH))) { + uploadLength = true; + } + + if (Utils.getHeader(request, HttpHeader.UPLOAD_DEFER_LENGTH).equals("1")) { + deferredLength = true; + } + + String uploadConcatValue = request.getHeader(HttpHeader.UPLOAD_CONCAT); + if (StringUtils.startsWithIgnoreCase(uploadConcatValue, "final")) { + concatenatedUpload = true; } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); + if (!concatenatedUpload && !uploadLength && !deferredLength) { + throw new InvalidUploadLengthException( + "No valid value was found in headers " + + HttpHeader.UPLOAD_LENGTH + + " and " + + HttpHeader.UPLOAD_DEFER_LENGTH); + } else if (uploadLength && deferredLength) { + throw new InvalidUploadLengthException( + "A POST request cannot contain both " + + HttpHeader.UPLOAD_LENGTH + + " and " + + HttpHeader.UPLOAD_DEFER_LENGTH + + " headers."); } + } + + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/creation/validation/UploadLengthValidator.java b/src/main/java/me/desair/tus/server/creation/validation/UploadLengthValidator.java index 58882a6..3baf6fc 100644 --- a/src/main/java/me/desair/tus/server/creation/validation/UploadLengthValidator.java +++ b/src/main/java/me/desair/tus/server/creation/validation/UploadLengthValidator.java @@ -1,7 +1,6 @@ package me.desair.tus.server.creation.validation; import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestValidator; @@ -11,28 +10,31 @@ import me.desair.tus.server.util.Utils; /** - * If the length of the upload exceeds the maximum, which MAY be specified using the Tus-Max-Size header, - * the Server MUST respond with the 413 Request Entity Too Large status. + * If the length of the upload exceeds the maximum, which MAY be specified using the Tus-Max-Size + * header, the Server MUST respond with the 413 Request Entity Too Large status. */ public class UploadLengthValidator implements RequestValidator { - @Override - public void validate(HttpMethod method, HttpServletRequest request, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest request, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException { - Long uploadLength = Utils.getLongHeader(request, HttpHeader.UPLOAD_LENGTH); - if (uploadLength != null - && uploadStorageService.getMaxUploadSize() > 0 - && uploadLength > uploadStorageService.getMaxUploadSize()) { + Long uploadLength = Utils.getLongHeader(request, HttpHeader.UPLOAD_LENGTH); + if (uploadLength != null + && uploadStorageService.getMaxUploadSize() > 0 + && uploadLength > uploadStorageService.getMaxUploadSize()) { - throw new MaxUploadLengthExceededException("Upload requests can have a maximum size of " - + uploadStorageService.getMaxUploadSize()); - } + throw new MaxUploadLengthExceededException( + "Upload requests can have a maximum size of " + uploadStorageService.getMaxUploadSize()); } + } - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.POST.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.POST.equals(method); + } } diff --git a/src/main/java/me/desair/tus/server/download/DownloadExtension.java b/src/main/java/me/desair/tus/server/download/DownloadExtension.java index c3c6eda..c6d6ceb 100644 --- a/src/main/java/me/desair/tus/server/download/DownloadExtension.java +++ b/src/main/java/me/desair/tus/server/download/DownloadExtension.java @@ -3,36 +3,35 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.util.AbstractTusExtension; /** - * Some Tus clients also send GET request to retrieve the uploaded content. We consider this - * as an unofficial extension. + * Some Tus clients also send GET request to retrieve the uploaded content. We consider this as an + * unofficial extension. */ public class DownloadExtension extends AbstractTusExtension { - @Override - public String getName() { - return "download"; - } + @Override + public String getName() { + return "download"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET); + } - @Override - protected void initValidators(List requestValidators) { - //All validation is all read done by the Core protocol - } + @Override + protected void initValidators(List requestValidators) { + // All validation is all read done by the Core protocol + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new DownloadGetRequestHandler()); - requestHandlers.add(new DownloadOptionsRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new DownloadGetRequestHandler()); + requestHandlers.add(new DownloadOptionsRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/download/DownloadGetRequestHandler.java b/src/main/java/me/desair/tus/server/download/DownloadGetRequestHandler.java index 82f5590..809a0e7 100644 --- a/src/main/java/me/desair/tus/server/download/DownloadGetRequestHandler.java +++ b/src/main/java/me/desair/tus/server/download/DownloadGetRequestHandler.java @@ -1,11 +1,10 @@ package me.desair.tus.server.download; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Objects; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.TusException; @@ -16,45 +15,54 @@ import me.desair.tus.server.util.TusServletRequest; import me.desair.tus.server.util.TusServletResponse; -/** - * Send the uploaded bytes of finished uploads - */ +/** Send the uploaded bytes of finished uploads */ public class DownloadGetRequestHandler extends AbstractRequestHandler { - private static final String CONTENT_DISPOSITION_FORMAT = "attachment; filename=\"%s\"; filename*=UTF-8''%s"; + private static final String CONTENT_DISPOSITION_FORMAT = + "attachment; filename=\"%s\"; filename*=UTF-8''%s"; - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.GET.equals(method); - } - - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.GET.equals(method); + } - UploadInfo info = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); - if (info == null || info.isUploadInProgress()) { - throw new UploadInProgressException("Upload " + servletRequest.getRequestURI() + " is still in progress " - + "and cannot be downloaded yet"); - } else { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - servletResponse.setHeader(HttpHeader.CONTENT_LENGTH, Objects.toString(info.getLength())); + UploadInfo info = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + if (info == null || info.isUploadInProgress()) { + throw new UploadInProgressException( + "Upload " + + servletRequest.getRequestURI() + + " is still in progress " + + "and cannot be downloaded yet"); + } else { - servletResponse.setHeader(HttpHeader.CONTENT_DISPOSITION, - String.format(CONTENT_DISPOSITION_FORMAT, info.getFileName(), - URLEncoder.encode(info.getFileName(), - StandardCharsets.UTF_8.toString()).replace("+","%20"))); + servletResponse.setHeader(HttpHeader.CONTENT_LENGTH, Objects.toString(info.getLength())); - servletResponse.setHeader(HttpHeader.CONTENT_TYPE, info.getFileMimeType()); + servletResponse.setHeader( + HttpHeader.CONTENT_DISPOSITION, + String.format( + CONTENT_DISPOSITION_FORMAT, + info.getFileName(), + URLEncoder.encode(info.getFileName(), StandardCharsets.UTF_8.toString()) + .replace("+", "%20"))); - if (info.hasMetadata()) { - servletResponse.setHeader(HttpHeader.UPLOAD_METADATA, info.getEncodedMetadata()); - } + servletResponse.setHeader(HttpHeader.CONTENT_TYPE, info.getFileMimeType()); - uploadStorageService.copyUploadTo(info, servletResponse.getOutputStream()); - } + if (info.hasMetadata()) { + servletResponse.setHeader(HttpHeader.UPLOAD_METADATA, info.getEncodedMetadata()); + } - servletResponse.setStatus(HttpServletResponse.SC_OK); + uploadStorageService.copyUploadTo(info, servletResponse.getOutputStream()); } + + servletResponse.setStatus(HttpServletResponse.SC_OK); + } } diff --git a/src/main/java/me/desair/tus/server/download/DownloadOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/download/DownloadOptionsRequestHandler.java index d810141..29a410f 100644 --- a/src/main/java/me/desair/tus/server/download/DownloadOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/download/DownloadOptionsRequestHandler.java @@ -2,14 +2,11 @@ import me.desair.tus.server.util.AbstractExtensionRequestHandler; -/** - * Add our download extension the Tus-Extension header - */ +/** Add our download extension the Tus-Extension header */ public class DownloadOptionsRequestHandler extends AbstractExtensionRequestHandler { - @Override - protected void appendExtensions(StringBuilder extensionBuilder) { - addExtension(extensionBuilder, "download"); - } - + @Override + protected void appendExtensions(StringBuilder extensionBuilder) { + addExtension(extensionBuilder, "download"); + } } diff --git a/src/main/java/me/desair/tus/server/exception/ChecksumAlgorithmNotSupportedException.java b/src/main/java/me/desair/tus/server/exception/ChecksumAlgorithmNotSupportedException.java index 9e0890c..1bdd419 100644 --- a/src/main/java/me/desair/tus/server/exception/ChecksumAlgorithmNotSupportedException.java +++ b/src/main/java/me/desair/tus/server/exception/ChecksumAlgorithmNotSupportedException.java @@ -2,11 +2,9 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when the client sends a request for a checksum algorithm we do not support - */ +/** Exception thrown when the client sends a request for a checksum algorithm we do not support */ public class ChecksumAlgorithmNotSupportedException extends TusException { - public ChecksumAlgorithmNotSupportedException(String message) { - super(HttpServletResponse.SC_BAD_REQUEST, message); - } + public ChecksumAlgorithmNotSupportedException(String message) { + super(HttpServletResponse.SC_BAD_REQUEST, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/InvalidContentLengthException.java b/src/main/java/me/desair/tus/server/exception/InvalidContentLengthException.java index 84d3469..1255108 100644 --- a/src/main/java/me/desair/tus/server/exception/InvalidContentLengthException.java +++ b/src/main/java/me/desair/tus/server/exception/InvalidContentLengthException.java @@ -3,7 +3,7 @@ import jakarta.servlet.http.HttpServletResponse; public class InvalidContentLengthException extends TusException { - public InvalidContentLengthException(String message) { - super(HttpServletResponse.SC_BAD_REQUEST, message); - } + public InvalidContentLengthException(String message) { + super(HttpServletResponse.SC_BAD_REQUEST, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/InvalidContentTypeException.java b/src/main/java/me/desair/tus/server/exception/InvalidContentTypeException.java index de55671..378474b 100644 --- a/src/main/java/me/desair/tus/server/exception/InvalidContentTypeException.java +++ b/src/main/java/me/desair/tus/server/exception/InvalidContentTypeException.java @@ -2,11 +2,9 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when the request has an invalid content type. - */ +/** Exception thrown when the request has an invalid content type. */ public class InvalidContentTypeException extends TusException { - public InvalidContentTypeException(String message) { - super(HttpServletResponse.SC_NOT_ACCEPTABLE, message); - } + public InvalidContentTypeException(String message) { + super(HttpServletResponse.SC_NOT_ACCEPTABLE, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/InvalidPartialUploadIdException.java b/src/main/java/me/desair/tus/server/exception/InvalidPartialUploadIdException.java index cb53027..3314a04 100644 --- a/src/main/java/me/desair/tus/server/exception/InvalidPartialUploadIdException.java +++ b/src/main/java/me/desair/tus/server/exception/InvalidPartialUploadIdException.java @@ -2,11 +2,9 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when the Upload-Concat header contains an ID which is not valid - */ +/** Exception thrown when the Upload-Concat header contains an ID which is not valid */ public class InvalidPartialUploadIdException extends TusException { - public InvalidPartialUploadIdException(String message) { - super(HttpServletResponse.SC_PRECONDITION_FAILED, message); - } + public InvalidPartialUploadIdException(String message) { + super(HttpServletResponse.SC_PRECONDITION_FAILED, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/InvalidTusResumableException.java b/src/main/java/me/desair/tus/server/exception/InvalidTusResumableException.java index 94a5c04..53d1d44 100644 --- a/src/main/java/me/desair/tus/server/exception/InvalidTusResumableException.java +++ b/src/main/java/me/desair/tus/server/exception/InvalidTusResumableException.java @@ -2,19 +2,20 @@ import jakarta.servlet.http.HttpServletResponse; -/** Exception thrown when receiving a request with a tus protocol version we do not support - *

- * The Tus-Resumable header MUST be included in every request and response except for OPTIONS requests. - * The value MUST be the version of the protocol used by the Client or the Server. - * If the the version specified by the Client is not supported by the Server, it MUST respond with the - * 412 Precondition Failed status and MUST include the Tus-Version header into the response. - * In addition, the Server MUST NOT process the request. - *

- * (https://tus.io/protocols/resumable-upload.html#tus-resumable) +/** + * Exception thrown when receiving a request with a tus protocol version we do not support + * + *

The Tus-Resumable header MUST be included in every request and response except for OPTIONS + * requests. The value MUST be the version of the protocol used by the Client or the Server. If the + * the version specified by the Client is not supported by the Server, it MUST respond with the 412 + * Precondition Failed status and MUST include the Tus-Version header into the response. In + * addition, the Server MUST NOT process the request. + * + *

(https://tus.io/protocols/resumable-upload.html#tus-resumable) */ public class InvalidTusResumableException extends TusException { - public InvalidTusResumableException(String message) { - super(HttpServletResponse.SC_PRECONDITION_FAILED, message); - } + public InvalidTusResumableException(String message) { + super(HttpServletResponse.SC_PRECONDITION_FAILED, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/InvalidUploadLengthException.java b/src/main/java/me/desair/tus/server/exception/InvalidUploadLengthException.java index 46a9f30..22f04e2 100644 --- a/src/main/java/me/desair/tus/server/exception/InvalidUploadLengthException.java +++ b/src/main/java/me/desair/tus/server/exception/InvalidUploadLengthException.java @@ -2,12 +2,10 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when no valid Upload-Length or Upload-Defer-Length header is found - */ +/** Exception thrown when no valid Upload-Length or Upload-Defer-Length header is found */ public class InvalidUploadLengthException extends TusException { - public InvalidUploadLengthException(String message) { - super(HttpServletResponse.SC_BAD_REQUEST, message); - } + public InvalidUploadLengthException(String message) { + super(HttpServletResponse.SC_BAD_REQUEST, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/InvalidUploadOffsetException.java b/src/main/java/me/desair/tus/server/exception/InvalidUploadOffsetException.java index 5bfc74b..7bd7294 100644 --- a/src/main/java/me/desair/tus/server/exception/InvalidUploadOffsetException.java +++ b/src/main/java/me/desair/tus/server/exception/InvalidUploadOffsetException.java @@ -3,7 +3,7 @@ import jakarta.servlet.http.HttpServletResponse; public class InvalidUploadOffsetException extends TusException { - public InvalidUploadOffsetException(String message) { - super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message); - } + public InvalidUploadOffsetException(String message) { + super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/MaxUploadLengthExceededException.java b/src/main/java/me/desair/tus/server/exception/MaxUploadLengthExceededException.java index 15d8947..136f858 100644 --- a/src/main/java/me/desair/tus/server/exception/MaxUploadLengthExceededException.java +++ b/src/main/java/me/desair/tus/server/exception/MaxUploadLengthExceededException.java @@ -2,11 +2,9 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when the given upload length exceeds or internally defined maximum - */ +/** Exception thrown when the given upload length exceeds or internally defined maximum */ public class MaxUploadLengthExceededException extends TusException { - public MaxUploadLengthExceededException(String message) { - super(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, message); - } + public MaxUploadLengthExceededException(String message) { + super(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/PatchOnFinalUploadNotAllowedException.java b/src/main/java/me/desair/tus/server/exception/PatchOnFinalUploadNotAllowedException.java index b693e76..e74ac05 100644 --- a/src/main/java/me/desair/tus/server/exception/PatchOnFinalUploadNotAllowedException.java +++ b/src/main/java/me/desair/tus/server/exception/PatchOnFinalUploadNotAllowedException.java @@ -2,12 +2,10 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * The Server MUST respond with the 403 Forbidden status to PATCH requests against a upload URL - */ +/** The Server MUST respond with the 403 Forbidden status to PATCH requests against a upload URL */ public class PatchOnFinalUploadNotAllowedException extends TusException { - public PatchOnFinalUploadNotAllowedException(String message) { - super(HttpServletResponse.SC_FORBIDDEN, message); - } + public PatchOnFinalUploadNotAllowedException(String message) { + super(HttpServletResponse.SC_FORBIDDEN, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/PostOnInvalidRequestURIException.java b/src/main/java/me/desair/tus/server/exception/PostOnInvalidRequestURIException.java index 1ef29d6..3e4d16a 100644 --- a/src/main/java/me/desair/tus/server/exception/PostOnInvalidRequestURIException.java +++ b/src/main/java/me/desair/tus/server/exception/PostOnInvalidRequestURIException.java @@ -2,12 +2,10 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when a POST request was received on an invalid URI - */ +/** Exception thrown when a POST request was received on an invalid URI */ public class PostOnInvalidRequestURIException extends TusException { - public PostOnInvalidRequestURIException(String message) { - super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message); - } + public PostOnInvalidRequestURIException(String message) { + super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/TusException.java b/src/main/java/me/desair/tus/server/exception/TusException.java index d0bc66f..7ba43b9 100644 --- a/src/main/java/me/desair/tus/server/exception/TusException.java +++ b/src/main/java/me/desair/tus/server/exception/TusException.java @@ -1,22 +1,20 @@ package me.desair.tus.server.exception; -/** - * Super class for exception in the tus protocol - */ +/** Super class for exception in the tus protocol */ public class TusException extends Exception { - private int status; + private int status; - public TusException(int status, String message) { - this(status, message, null); - } + public TusException(int status, String message) { + this(status, message, null); + } - public TusException(int status, String message, Throwable e) { - super(message, e); - this.status = status; - } + public TusException(int status, String message, Throwable e) { + super(message, e); + this.status = status; + } - public int getStatus() { - return status; - } + public int getStatus() { + return status; + } } diff --git a/src/main/java/me/desair/tus/server/exception/UnsupportedMethodException.java b/src/main/java/me/desair/tus/server/exception/UnsupportedMethodException.java index eb9b8a0..5a9780b 100644 --- a/src/main/java/me/desair/tus/server/exception/UnsupportedMethodException.java +++ b/src/main/java/me/desair/tus/server/exception/UnsupportedMethodException.java @@ -2,11 +2,9 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when we receive a HTTP request with a method name that we do not support - */ +/** Exception thrown when we receive a HTTP request with a method name that we do not support */ public class UnsupportedMethodException extends TusException { - public UnsupportedMethodException(String message) { - super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message); - } + public UnsupportedMethodException(String message) { + super(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/UploadAlreadyLockedException.java b/src/main/java/me/desair/tus/server/exception/UploadAlreadyLockedException.java index 784c88f..74c9900 100644 --- a/src/main/java/me/desair/tus/server/exception/UploadAlreadyLockedException.java +++ b/src/main/java/me/desair/tus/server/exception/UploadAlreadyLockedException.java @@ -1,8 +1,8 @@ package me.desair.tus.server.exception; public class UploadAlreadyLockedException extends TusException { - public UploadAlreadyLockedException(String message) { - // 423 is LOCKED (WebDAV rfc 4918) - super(423, message); - } + public UploadAlreadyLockedException(String message) { + // 423 is LOCKED (WebDAV rfc 4918) + super(423, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/UploadChecksumMismatchException.java b/src/main/java/me/desair/tus/server/exception/UploadChecksumMismatchException.java index b8a65b5..cd67529 100644 --- a/src/main/java/me/desair/tus/server/exception/UploadChecksumMismatchException.java +++ b/src/main/java/me/desair/tus/server/exception/UploadChecksumMismatchException.java @@ -1,10 +1,11 @@ package me.desair.tus.server.exception; /** - * Exception thrown when the client provided checksum does not match the checksum calculated by the server + * Exception thrown when the client provided checksum does not match the checksum calculated by the + * server */ public class UploadChecksumMismatchException extends TusException { - public UploadChecksumMismatchException(String message) { - super(460, message); - } + public UploadChecksumMismatchException(String message) { + super(460, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/UploadInProgressException.java b/src/main/java/me/desair/tus/server/exception/UploadInProgressException.java index db43f0f..eee5565 100644 --- a/src/main/java/me/desair/tus/server/exception/UploadInProgressException.java +++ b/src/main/java/me/desair/tus/server/exception/UploadInProgressException.java @@ -1,13 +1,13 @@ package me.desair.tus.server.exception; /** - * Exception thrown when accessing an upload that is still in progress and this - * is not supported by the operation + * Exception thrown when accessing an upload that is still in progress and this is not supported by + * the operation */ public class UploadInProgressException extends TusException { - public UploadInProgressException(String message) { - //422 Unprocessable Entity - //The request was well-formed but was unable to be followed due to semantic errors. - super(422, message); - } + public UploadInProgressException(String message) { + // 422 Unprocessable Entity + // The request was well-formed but was unable to be followed due to semantic errors. + super(422, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/UploadLengthNotAllowedOnConcatenationException.java b/src/main/java/me/desair/tus/server/exception/UploadLengthNotAllowedOnConcatenationException.java index 3e97b67..13d974f 100644 --- a/src/main/java/me/desair/tus/server/exception/UploadLengthNotAllowedOnConcatenationException.java +++ b/src/main/java/me/desair/tus/server/exception/UploadLengthNotAllowedOnConcatenationException.java @@ -2,11 +2,9 @@ import jakarta.servlet.http.HttpServletResponse; -/** - * Exception thrown when the Client includes the Upload-Length header in the upload creation. - */ +/** Exception thrown when the Client includes the Upload-Length header in the upload creation. */ public class UploadLengthNotAllowedOnConcatenationException extends TusException { - public UploadLengthNotAllowedOnConcatenationException(String message) { - super(HttpServletResponse.SC_BAD_REQUEST, message); - } + public UploadLengthNotAllowedOnConcatenationException(String message) { + super(HttpServletResponse.SC_BAD_REQUEST, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/UploadNotFoundException.java b/src/main/java/me/desair/tus/server/exception/UploadNotFoundException.java index bd85c4e..bbc270a 100644 --- a/src/main/java/me/desair/tus/server/exception/UploadNotFoundException.java +++ b/src/main/java/me/desair/tus/server/exception/UploadNotFoundException.java @@ -2,13 +2,14 @@ import jakarta.servlet.http.HttpServletResponse; -/** Exception thrown when the given upload ID was not found - *

- * If the resource is not found, the Server SHOULD return either the - * 404 Not Found, 410 Gone or 403 Forbidden status without the Upload-Offset header. +/** + * Exception thrown when the given upload ID was not found + * + *

If the resource is not found, the Server SHOULD return either the 404 Not Found, 410 Gone or + * 403 Forbidden status without the Upload-Offset header. */ public class UploadNotFoundException extends TusException { - public UploadNotFoundException(String message) { - super(HttpServletResponse.SC_NOT_FOUND, message); - } + public UploadNotFoundException(String message) { + super(HttpServletResponse.SC_NOT_FOUND, message); + } } diff --git a/src/main/java/me/desair/tus/server/exception/UploadOffsetMismatchException.java b/src/main/java/me/desair/tus/server/exception/UploadOffsetMismatchException.java index 3bb541b..bde3057 100644 --- a/src/main/java/me/desair/tus/server/exception/UploadOffsetMismatchException.java +++ b/src/main/java/me/desair/tus/server/exception/UploadOffsetMismatchException.java @@ -3,11 +3,11 @@ import jakarta.servlet.http.HttpServletResponse; /** - * If the offsets do not match, the Server MUST respond with the - * 409 Conflict status without modifying the upload resource. + * If the offsets do not match, the Server MUST respond with the 409 Conflict status without + * modifying the upload resource. */ public class UploadOffsetMismatchException extends TusException { - public UploadOffsetMismatchException(String message) { - super(HttpServletResponse.SC_CONFLICT, message); - } + public UploadOffsetMismatchException(String message) { + super(HttpServletResponse.SC_CONFLICT, message); + } } diff --git a/src/main/java/me/desair/tus/server/expiration/ExpirationExtension.java b/src/main/java/me/desair/tus/server/expiration/ExpirationExtension.java index 3be2bbe..2723c9f 100644 --- a/src/main/java/me/desair/tus/server/expiration/ExpirationExtension.java +++ b/src/main/java/me/desair/tus/server/expiration/ExpirationExtension.java @@ -3,35 +3,32 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.util.AbstractTusExtension; -/** - * The Server MAY remove unfinished uploads once they expire. - */ +/** The Server MAY remove unfinished uploads once they expire. */ public class ExpirationExtension extends AbstractTusExtension { - @Override - public String getName() { - return "expiration"; - } + @Override + public String getName() { + return "expiration"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.PATCH, HttpMethod.POST); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.PATCH, HttpMethod.POST); + } - @Override - protected void initValidators(List requestValidators) { - //No validators - } + @Override + protected void initValidators(List requestValidators) { + // No validators + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new ExpirationOptionsRequestHandler()); - requestHandlers.add(new ExpirationRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new ExpirationOptionsRequestHandler()); + requestHandlers.add(new ExpirationRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandler.java index 3225165..e126ef1 100644 --- a/src/main/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandler.java @@ -3,14 +3,13 @@ import me.desair.tus.server.util.AbstractExtensionRequestHandler; /** - * The Server MAY remove unfinished uploads once they expire. In order to indicate this behavior to the Client, - * the Server MUST add expiration to the Tus-Extension header. + * The Server MAY remove unfinished uploads once they expire. In order to indicate this behavior to + * the Client, the Server MUST add expiration to the Tus-Extension header. */ public class ExpirationOptionsRequestHandler extends AbstractExtensionRequestHandler { - @Override - protected void appendExtensions(StringBuilder extensionBuilder) { - addExtension(extensionBuilder, "expiration"); - } - + @Override + protected void appendExtensions(StringBuilder extensionBuilder) { + addExtension(extensionBuilder, "expiration"); + } } diff --git a/src/main/java/me/desair/tus/server/expiration/ExpirationRequestHandler.java b/src/main/java/me/desair/tus/server/expiration/ExpirationRequestHandler.java index 0326f58..c010313 100644 --- a/src/main/java/me/desair/tus/server/expiration/ExpirationRequestHandler.java +++ b/src/main/java/me/desair/tus/server/expiration/ExpirationRequestHandler.java @@ -1,7 +1,6 @@ package me.desair.tus.server.expiration; import java.io.IOException; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.TusException; @@ -13,50 +12,58 @@ import org.apache.commons.lang3.StringUtils; /** - * The Upload-Expires response header indicates the time after which the unfinished upload expires. This header MUST - * be included in every PATCH response if the upload is going to expire. Its value MAY change over time. - * If the expiration is known at the creation, the Upload-Expires header MUST be included in the response to - * the initial POST request. Its value MAY change over time. The value of the Upload-Expires header MUST be in - * RFC 7231 (https://tools.ietf.org/html/rfc7231#section-7.1.1.1) datetime format. + * The Upload-Expires response header indicates the time after which the unfinished upload expires. + * This header MUST be included in every PATCH response if the upload is going to expire. Its value + * MAY change over time. If the expiration is known at the creation, the Upload-Expires header MUST + * be included in the response to the initial POST request. Its value MAY change over time. The + * value of the Upload-Expires header MUST be in RFC 7231 + * (https://tools.ietf.org/html/rfc7231#section-7.1.1.1) datetime format. */ public class ExpirationRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.PATCH.equals(method) - || HttpMethod.POST.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.PATCH.equals(method) || HttpMethod.POST.equals(method); + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - //For post requests, the upload URI is part of the response - String uploadUri = servletResponse.getHeader(HttpHeader.LOCATION); - if (StringUtils.isBlank(uploadUri)) { - //For patch request, our upload URI is the URI of the request - uploadUri = servletRequest.getRequestURI(); - } + // For post requests, the upload URI is part of the response + String uploadUri = servletResponse.getHeader(HttpHeader.LOCATION); + if (StringUtils.isBlank(uploadUri)) { + // For patch request, our upload URI is the URI of the request + uploadUri = servletRequest.getRequestURI(); + } - Long expirationPeriod = uploadStorageService.getUploadExpirationPeriod(); - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadUri, ownerKey); + Long expirationPeriod = uploadStorageService.getUploadExpirationPeriod(); + UploadInfo uploadInfo = uploadStorageService.getUploadInfo(uploadUri, ownerKey); - // The Upload-Expires response header MUST be included in every PATCH response if the upload is going to expire. - // If the expiration is known at the creation, the Upload-Expires header MUST be included in the response to - // the initial POST request. Its value MAY change over time. + // The Upload-Expires response header MUST be included in every PATCH response if the upload + // is + // going to expire. + // If the expiration is known at the creation, the Upload-Expires header MUST be included in + // the + // response to + // the initial POST request. Its value MAY change over time. - if (expirationPeriod != null && expirationPeriod > 0 && uploadInfo != null) { + if (expirationPeriod != null && expirationPeriod > 0 && uploadInfo != null) { - uploadInfo.updateExpiration(expirationPeriod); - uploadStorageService.update(uploadInfo); + uploadInfo.updateExpiration(expirationPeriod); + uploadStorageService.update(uploadInfo); - servletResponse.setDateHeader(HttpHeader.UPLOAD_EXPIRES, uploadInfo.getExpirationTimestamp()); - } + servletResponse.setDateHeader(HttpHeader.UPLOAD_EXPIRES, uploadInfo.getExpirationTimestamp()); } + } - @Override - public boolean isErrorHandler() { - return true; - } + @Override + public boolean isErrorHandler() { + return true; + } } diff --git a/src/main/java/me/desair/tus/server/termination/TerminationDeleteRequestHandler.java b/src/main/java/me/desair/tus/server/termination/TerminationDeleteRequestHandler.java index aaee961..7b61176 100644 --- a/src/main/java/me/desair/tus/server/termination/TerminationDeleteRequestHandler.java +++ b/src/main/java/me/desair/tus/server/termination/TerminationDeleteRequestHandler.java @@ -1,8 +1,7 @@ package me.desair.tus.server.termination; -import java.io.IOException; import jakarta.servlet.http.HttpServletResponse; - +import java.io.IOException; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.TusException; import me.desair.tus.server.upload.UploadInfo; @@ -12,29 +11,34 @@ import me.desair.tus.server.util.TusServletResponse; /** - * When receiving a DELETE request for an existing upload the Server SHOULD free associated resources - * and MUST respond with the 204 No Content status confirming that the upload was terminated. For all future requests - * to this URL the Server SHOULD respond with the 404 Not Found or 410 Gone status. + * When receiving a DELETE request for an existing upload the Server SHOULD free associated + * resources and MUST respond with the 204 No Content status confirming that the upload was + * terminated. For all future requests to this URL the Server SHOULD respond with the 404 Not Found + * or 410 Gone status. */ public class TerminationDeleteRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.DELETE.equals(method); - } - - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { - - UploadInfo uploadInfo = uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); - - if (uploadInfo != null) { - uploadStorageService.terminateUpload(uploadInfo); - } - - servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.DELETE.equals(method); + } + + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { + + UploadInfo uploadInfo = + uploadStorageService.getUploadInfo(servletRequest.getRequestURI(), ownerKey); + + if (uploadInfo != null) { + uploadStorageService.terminateUpload(uploadInfo); } + servletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + } } diff --git a/src/main/java/me/desair/tus/server/termination/TerminationExtension.java b/src/main/java/me/desair/tus/server/termination/TerminationExtension.java index c9ebffb..2a8f85f 100644 --- a/src/main/java/me/desair/tus/server/termination/TerminationExtension.java +++ b/src/main/java/me/desair/tus/server/termination/TerminationExtension.java @@ -3,39 +3,38 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; import me.desair.tus.server.util.AbstractTusExtension; /** - * This extension defines a way for the Client to terminate completed and unfinished - * uploads allowing the Server to free up used resources. - *

- * If this extension is supported by the Server, it MUST be announced by adding "termination" - * to the Tus-Extension header. + * This extension defines a way for the Client to terminate completed and unfinished uploads + * allowing the Server to free up used resources. + * + *

If this extension is supported by the Server, it MUST be announced by adding "termination" to + * the Tus-Extension header. */ public class TerminationExtension extends AbstractTusExtension { - @Override - public String getName() { - return "termination"; - } + @Override + public String getName() { + return "termination"; + } - @Override - public Collection getMinimalSupportedHttpMethods() { - return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.DELETE); - } + @Override + public Collection getMinimalSupportedHttpMethods() { + return Arrays.asList(HttpMethod.OPTIONS, HttpMethod.DELETE); + } - @Override - protected void initValidators(List requestValidators) { - //All validation is all read done by the Core protocol - } + @Override + protected void initValidators(List requestValidators) { + // All validation is all read done by the Core protocol + } - @Override - protected void initRequestHandlers(List requestHandlers) { - requestHandlers.add(new TerminationDeleteRequestHandler()); - requestHandlers.add(new TerminationOptionsRequestHandler()); - } + @Override + protected void initRequestHandlers(List requestHandlers) { + requestHandlers.add(new TerminationDeleteRequestHandler()); + requestHandlers.add(new TerminationOptionsRequestHandler()); + } } diff --git a/src/main/java/me/desair/tus/server/termination/TerminationOptionsRequestHandler.java b/src/main/java/me/desair/tus/server/termination/TerminationOptionsRequestHandler.java index 03c3422..6c0f1d3 100644 --- a/src/main/java/me/desair/tus/server/termination/TerminationOptionsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/termination/TerminationOptionsRequestHandler.java @@ -2,14 +2,11 @@ import me.desair.tus.server.util.AbstractExtensionRequestHandler; -/** - * Add our download extension the Tus-Extension header - */ +/** Add our download extension the Tus-Extension header */ public class TerminationOptionsRequestHandler extends AbstractExtensionRequestHandler { - @Override - protected void appendExtensions(StringBuilder extensionBuilder) { - addExtension(extensionBuilder, "termination"); - } - + @Override + protected void appendExtensions(StringBuilder extensionBuilder) { + addExtension(extensionBuilder, "termination"); + } } diff --git a/src/main/java/me/desair/tus/server/upload/TimeBasedUploadIdFactory.java b/src/main/java/me/desair/tus/server/upload/TimeBasedUploadIdFactory.java index ea56dad..8b98ad2 100644 --- a/src/main/java/me/desair/tus/server/upload/TimeBasedUploadIdFactory.java +++ b/src/main/java/me/desair/tus/server/upload/TimeBasedUploadIdFactory.java @@ -1,33 +1,32 @@ package me.desair.tus.server.upload; import java.io.Serializable; - import org.apache.commons.lang3.StringUtils; /** - * Alternative {@link UploadIdFactory} implementation that uses the current system time to generate ID's. - * Since time is not unique, this upload ID factory should not be used in busy, clustered production systems. + * Alternative {@link UploadIdFactory} implementation that uses the current system time to generate + * ID's. Since time is not unique, this upload ID factory should not be used in busy, clustered + * production systems. */ public class TimeBasedUploadIdFactory extends UploadIdFactory { - @Override - protected Serializable getIdValueIfValid(String extractedUrlId) { - Long id = null; - - if (StringUtils.isNotBlank(extractedUrlId)) { - try { - id = Long.parseLong(extractedUrlId); - } catch (NumberFormatException ex) { - id = null; - } - } + @Override + protected Serializable getIdValueIfValid(String extractedUrlId) { + Long id = null; - return id; + if (StringUtils.isNotBlank(extractedUrlId)) { + try { + id = Long.parseLong(extractedUrlId); + } catch (NumberFormatException ex) { + id = null; + } } - @Override - public synchronized UploadId createId() { - return new UploadId(System.currentTimeMillis()); - } + return id; + } + @Override + public synchronized UploadId createId() { + return new UploadId(System.currentTimeMillis()); + } } diff --git a/src/main/java/me/desair/tus/server/upload/UUIDUploadIdFactory.java b/src/main/java/me/desair/tus/server/upload/UUIDUploadIdFactory.java index cce50a6..674a222 100644 --- a/src/main/java/me/desair/tus/server/upload/UUIDUploadIdFactory.java +++ b/src/main/java/me/desair/tus/server/upload/UUIDUploadIdFactory.java @@ -4,26 +4,25 @@ import java.util.UUID; /** - * Factory to create unique upload IDs. This factory can also parse the upload identifier - * from a given upload URL. + * Factory to create unique upload IDs. This factory can also parse the upload identifier from a + * given upload URL. */ public class UUIDUploadIdFactory extends UploadIdFactory { - @Override - protected Serializable getIdValueIfValid(String extractedUrlId) { - UUID id = null; - try { - id = UUID.fromString(extractedUrlId); - } catch (IllegalArgumentException ex) { - id = null; - } - - return id; + @Override + protected Serializable getIdValueIfValid(String extractedUrlId) { + UUID id = null; + try { + id = UUID.fromString(extractedUrlId); + } catch (IllegalArgumentException ex) { + id = null; } - @Override - public synchronized UploadId createId() { - return new UploadId(UUID.randomUUID()); - } + return id; + } + @Override + public synchronized UploadId createId() { + return new UploadId(UUID.randomUUID()); + } } diff --git a/src/main/java/me/desair/tus/server/upload/UploadId.java b/src/main/java/me/desair/tus/server/upload/UploadId.java index 83bd392..b93015e 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadId.java +++ b/src/main/java/me/desair/tus/server/upload/UploadId.java @@ -3,78 +3,77 @@ import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.Objects; - import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * The unique identifier of an upload process in the tus protocol - */ +/** The unique identifier of an upload process in the tus protocol */ public class UploadId implements Serializable { - private static final String UPLOAD_ID_CHARSET = "UTF-8"; - private static final Logger log = LoggerFactory.getLogger(UploadId.class); - - private String urlSafeValue; - private Serializable originalObject; + private static final String UPLOAD_ID_CHARSET = "UTF-8"; + private static final Logger log = LoggerFactory.getLogger(UploadId.class); - /** - * Create a new {@link UploadId} instance based on the provided object using it's toString method. - * @param inputObject The object to use for constructing the ID - */ - public UploadId(Serializable inputObject) { - String inputValue = (inputObject == null ? null : inputObject.toString()); - Validate.notBlank(inputValue, "The upload ID value cannot be blank"); + private String urlSafeValue; + private Serializable originalObject; - this.originalObject = inputObject; - URLCodec codec = new URLCodec(); - //Check if value is not encoded already - try { - if (inputValue != null && inputValue.equals(codec.decode(inputValue, UPLOAD_ID_CHARSET))) { - this.urlSafeValue = codec.encode(inputValue, UPLOAD_ID_CHARSET); - } else { - //value is already encoded, use as is - this.urlSafeValue = inputValue; - } - } catch (DecoderException | UnsupportedEncodingException e) { - log.warn("Unable to URL encode upload ID value", e); - this.urlSafeValue = inputValue; - } - } + /** + * Create a new {@link UploadId} instance based on the provided object using it's toString method. + * + * @param inputObject The object to use for constructing the ID + */ + public UploadId(Serializable inputObject) { + String inputValue = (inputObject == null ? null : inputObject.toString()); + Validate.notBlank(inputValue, "The upload ID value cannot be blank"); - /** - * The original input object that was provided when constructing this upload ID - * @return The original object used to create this ID - */ - public Serializable getOriginalObject() { - return this.originalObject; + this.originalObject = inputObject; + URLCodec codec = new URLCodec(); + // Check if value is not encoded already + try { + if (inputValue != null && inputValue.equals(codec.decode(inputValue, UPLOAD_ID_CHARSET))) { + this.urlSafeValue = codec.encode(inputValue, UPLOAD_ID_CHARSET); + } else { + // value is already encoded, use as is + this.urlSafeValue = inputValue; + } + } catch (DecoderException | UnsupportedEncodingException e) { + log.warn("Unable to URL encode upload ID value", e); + this.urlSafeValue = inputValue; } + } - @Override - public String toString() { - return urlSafeValue; - } + /** + * The original input object that was provided when constructing this upload ID + * + * @return The original object used to create this ID + */ + public Serializable getOriginalObject() { + return this.originalObject; + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof UploadId)) { - return false; - } + @Override + public String toString() { + return urlSafeValue; + } - UploadId uploadId = (UploadId) o; - //Upload IDs with the same URL-safe value should be considered equal - return Objects.equals(urlSafeValue, uploadId.urlSafeValue); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - @Override - public int hashCode() { - //Upload IDs with the same URL-safe value should be considered equal - return Objects.hash(urlSafeValue); + if (!(o instanceof UploadId)) { + return false; } + + UploadId uploadId = (UploadId) o; + // Upload IDs with the same URL-safe value should be considered equal + return Objects.equals(urlSafeValue, uploadId.urlSafeValue); + } + + @Override + public int hashCode() { + // Upload IDs with the same URL-safe value should be considered equal + return Objects.hash(urlSafeValue); + } } diff --git a/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java b/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java index 791f00c..1207b25 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java +++ b/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java @@ -3,88 +3,92 @@ import java.io.Serializable; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; /** - * Interface for a factory that can create unique upload IDs. This factory can also parse the upload identifier - * from a given upload URL. + * Interface for a factory that can create unique upload IDs. This factory can also parse the upload + * identifier from a given upload URL. */ public abstract class UploadIdFactory { - private String uploadURI = "/"; - private Pattern uploadUriPattern = null; + private String uploadURI = "/"; + private Pattern uploadUriPattern = null; - /** - * Set the URI under which the main tus upload endpoint is hosted. - * Optionally, this URI may contain regex parameters in order to support endpoints that contain - * URL parameters, for example /users/[0-9]+/files/upload - * - * @param uploadURI The URI of the main tus upload endpoint - */ - public void setUploadURI(String uploadURI) { - Validate.notBlank(uploadURI, "The upload URI pattern cannot be blank"); - Validate.isTrue(StringUtils.startsWith(uploadURI, "/"), "The upload URI should start with /"); - Validate.isTrue(!StringUtils.endsWith(uploadURI, "$"), "The upload URI should not end with $"); - this.uploadURI = uploadURI; - this.uploadUriPattern = null; - } + /** + * Set the URI under which the main tus upload endpoint is hosted. Optionally, this URI may + * contain regex parameters in order to support endpoints that contain URL parameters, for example + * /users/[0-9]+/files/upload + * + * @param uploadURI The URI of the main tus upload endpoint + */ + public void setUploadURI(String uploadURI) { + Validate.notBlank(uploadURI, "The upload URI pattern cannot be blank"); + Validate.isTrue(StringUtils.startsWith(uploadURI, "/"), "The upload URI should start with /"); + Validate.isTrue(!StringUtils.endsWith(uploadURI, "$"), "The upload URI should not end with $"); + this.uploadURI = uploadURI; + this.uploadUriPattern = null; + } - /** - * Return the URI of the main tus upload endpoint. Note that this value possibly contains regex parameters. - * @return The URI of the main tus upload endpoint. - */ - public String getUploadURI() { - return uploadURI; - } + /** + * Return the URI of the main tus upload endpoint. Note that this value possibly contains regex + * parameters. + * + * @return The URI of the main tus upload endpoint. + */ + public String getUploadURI() { + return uploadURI; + } - /** - * Read the upload identifier from the given URL. - *

- * Clients will send requests to upload URLs or provided URLs of completed uploads. This method is able to - * parse those URLs and provide the user with the corresponding upload ID. - * - * @param url The URL provided by the client - * @return The corresponding Upload identifier - */ - public UploadId readUploadId(String url) { - Matcher uploadUriMatcher = getUploadUriPattern().matcher(StringUtils.trimToEmpty(url)); - String pathId = uploadUriMatcher.replaceFirst(""); + /** + * Read the upload identifier from the given URL. + * + *

Clients will send requests to upload URLs or provided URLs of completed uploads. This method + * is able to parse those URLs and provide the user with the corresponding upload ID. + * + * @param url The URL provided by the client + * @return The corresponding Upload identifier + */ + public UploadId readUploadId(String url) { + Matcher uploadUriMatcher = getUploadUriPattern().matcher(StringUtils.trimToEmpty(url)); + String pathId = uploadUriMatcher.replaceFirst(""); - Serializable idValue = null; - if (StringUtils.isNotBlank(pathId)) { - idValue = getIdValueIfValid(pathId); - } - - return idValue == null ? null : new UploadId(idValue); + Serializable idValue = null; + if (StringUtils.isNotBlank(pathId)) { + idValue = getIdValueIfValid(pathId); } - /** - * Create a new unique upload ID - * @return A new unique upload ID - */ - public abstract UploadId createId(); + return idValue == null ? null : new UploadId(idValue); + } + + /** + * Create a new unique upload ID + * + * @return A new unique upload ID + */ + public abstract UploadId createId(); - /** - * Transform the extracted path ID value to a value to use for the upload ID object. - * If the extracted value is not valid, null is returned - * @param extractedUrlId The ID extracted from the URL - * @return Value to use in the UploadId object, null if the extracted URL value was not valid - */ - protected abstract Serializable getIdValueIfValid(String extractedUrlId); + /** + * Transform the extracted path ID value to a value to use for the upload ID object. If the + * extracted value is not valid, null is returned + * + * @param extractedUrlId The ID extracted from the URL + * @return Value to use in the UploadId object, null if the extracted URL value was not valid + */ + protected abstract Serializable getIdValueIfValid(String extractedUrlId); - /** - * Build and retrieve the Upload URI regex pattern - * @return A (cached) Pattern to match upload URI's - */ - protected Pattern getUploadUriPattern() { - if (uploadUriPattern == null) { - //We will extract the upload ID's by removing the upload URI from the start of the request URI - uploadUriPattern = Pattern.compile("^.*" - + uploadURI - + (StringUtils.endsWith(uploadURI, "/") ? "" : "/?")); - } - return uploadUriPattern; + /** + * Build and retrieve the Upload URI regex pattern + * + * @return A (cached) Pattern to match upload URI's + */ + protected Pattern getUploadUriPattern() { + if (uploadUriPattern == null) { + // We will extract the upload ID's by removing the upload URI from the start of the + // request URI + uploadUriPattern = + Pattern.compile("^.*" + uploadURI + (StringUtils.endsWith(uploadURI, "/") ? "" : "/?")); } + return uploadUriPattern; + } } diff --git a/src/main/java/me/desair/tus/server/upload/UploadInfo.java b/src/main/java/me/desair/tus/server/upload/UploadInfo.java index c91f83b..4f1307f 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadInfo.java +++ b/src/main/java/me/desair/tus/server/upload/UploadInfo.java @@ -1,13 +1,12 @@ package me.desair.tus.server.upload; +import jakarta.servlet.http.HttpServletRequest; import java.io.Serializable; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TreeMap; -import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.util.Utils; import org.apache.commons.codec.Charsets; import org.apache.commons.codec.binary.Base64; @@ -16,390 +15,417 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; /** - * Class that contains all metadata on an upload process. This class also holds the metadata provided by the client - * when creating the upload. + * Class that contains all metadata on an upload process. This class also holds the metadata + * provided by the client when creating the upload. */ public class UploadInfo implements Serializable { - private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - private static List fileNameKeys = Arrays.asList("filename", "name"); - private static List mimeTypeKeys = Arrays.asList("mimetype", "filetype", "type"); - - private UploadType uploadType; - private Long offset; - private String encodedMetadata; - private Long length; - private UploadId id; - private String ownerKey; - private Long creationTimestamp; - private String creatorIpAddresses; - private Long expirationTimestamp; - private List concatenationPartIds; - private String uploadConcatHeaderValue; - - /** - * Default constructor to use if an upload is created without HTTP request - */ - public UploadInfo() { - creationTimestamp = getCurrentTime(); - offset = 0L; - encodedMetadata = null; - length = null; - } - - /** - * Constructor to use if the upload is created using an HTTP request (which is usually the case) - * @param servletRequest The HTTP request that creates the new upload - */ - public UploadInfo(HttpServletRequest servletRequest) { - this(); - creatorIpAddresses = Utils.buildRemoteIpList(servletRequest); - } - - /** - * The current byte offset of the bytes that already have been stored for this upload on the server. - * The offset is the position where the next newly received byte should be stored. This index is zero-based. - * @return The offset where the next new byte will be written - */ - public Long getOffset() { - return offset; - } - - /** - * Set the position where the next newly received byte should be stored. This index is zero-based. - * @param offset The offset where the next new byte should be written - */ - public void setOffset(Long offset) { - this.offset = offset; - } - - /** - * Get the encoded Tus metadata string as it was provided by the Tus client at creation of the upload. - * The encoded metadata string consists of one or more comma-separated key-value pairs where the key is - * ASCII encoded and the value Base64 encoded. See https://tus.io/protocols/resumable-upload.html#upload-metadata - * @return The encoded metadata string as received from the client - */ - public String getEncodedMetadata() { - return encodedMetadata; - } - - /** - * Set the encoded Tus metadata string as it was provided by the Tus client at creation of the upload. - * The encoded metadata string consists of one or more comma-separated key-value pairs where the key is - * ASCII encoded and the value Base64 encoded. See https://tus.io/protocols/resumable-upload.html#upload-metadata - * @return The encoded metadata string as received from the client - */ - public void setEncodedMetadata(String encodedMetadata) { - this.encodedMetadata = encodedMetadata; - } - - /** - * Get the decoded metadata map provided by the client based on the encoded Tus metadata string received on - * creation of the upload. The encoded metadata string consists of one or more comma-separated key-value pairs - * where the key is ASCII encoded and the value Base64 encoded. The key and value MUST be separated by a space. - * See https://tus.io/protocols/resumable-upload.html#upload-metadata - * @return The encoded metadata string as received from the client - */ - public Map getMetadata() { - Map metadata = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (String valuePair : splitToArray(encodedMetadata, ",")) { - String[] keyValue = splitToArray(valuePair, "\\s"); - String key = null; - String value = null; - if (keyValue.length > 0) { - key = StringUtils.trimToEmpty(keyValue[0]); - - //Skip any blank values - int i = 1; - while (keyValue.length > i && StringUtils.isBlank(keyValue[i])) { - i++; - } - - if (keyValue.length > i) { - value = decode(keyValue[i]); - } - - metadata.put(key, value); - } + private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + private static List fileNameKeys = Arrays.asList("filename", "name"); + private static List mimeTypeKeys = Arrays.asList("mimetype", "filetype", "type"); + + private UploadType uploadType; + private Long offset; + private String encodedMetadata; + private Long length; + private UploadId id; + private String ownerKey; + private Long creationTimestamp; + private String creatorIpAddresses; + private Long expirationTimestamp; + private List concatenationPartIds; + private String uploadConcatHeaderValue; + + /** Default constructor to use if an upload is created without HTTP request */ + public UploadInfo() { + creationTimestamp = getCurrentTime(); + offset = 0L; + encodedMetadata = null; + length = null; + } + + /** + * Constructor to use if the upload is created using an HTTP request (which is usually the case) + * + * @param servletRequest The HTTP request that creates the new upload + */ + public UploadInfo(HttpServletRequest servletRequest) { + this(); + creatorIpAddresses = Utils.buildRemoteIpList(servletRequest); + } + + /** + * The current byte offset of the bytes that already have been stored for this upload on the + * server. The offset is the position where the next newly received byte should be stored. This + * index is zero-based. + * + * @return The offset where the next new byte will be written + */ + public Long getOffset() { + return offset; + } + + /** + * Set the position where the next newly received byte should be stored. This index is zero-based. + * + * @param offset The offset where the next new byte should be written + */ + public void setOffset(Long offset) { + this.offset = offset; + } + + /** + * Get the encoded Tus metadata string as it was provided by the Tus client at creation of the + * upload. The encoded metadata string consists of one or more comma-separated key-value pairs + * where the key is ASCII encoded and the value Base64 encoded. See + * https://tus.io/protocols/resumable-upload.html#upload-metadata + * + * @return The encoded metadata string as received from the client + */ + public String getEncodedMetadata() { + return encodedMetadata; + } + + /** + * Set the encoded Tus metadata string as it was provided by the Tus client at creation of the + * upload. The encoded metadata string consists of one or more comma-separated key-value pairs + * where the key is ASCII encoded and the value Base64 encoded. See + * https://tus.io/protocols/resumable-upload.html#upload-metadata + * + * @return The encoded metadata string as received from the client + */ + public void setEncodedMetadata(String encodedMetadata) { + this.encodedMetadata = encodedMetadata; + } + + /** + * Get the decoded metadata map provided by the client based on the encoded Tus metadata string + * received on creation of the upload. The encoded metadata string consists of one or more + * comma-separated key-value pairs where the key is ASCII encoded and the value Base64 encoded. + * The key and value MUST be separated by a space. See + * https://tus.io/protocols/resumable-upload.html#upload-metadata + * + * @return The encoded metadata string as received from the client + */ + public Map getMetadata() { + Map metadata = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (String valuePair : splitToArray(encodedMetadata, ",")) { + String[] keyValue = splitToArray(valuePair, "\\s"); + String key = null; + String value = null; + if (keyValue.length > 0) { + key = StringUtils.trimToEmpty(keyValue[0]); + + // Skip any blank values + int i = 1; + while (keyValue.length > i && StringUtils.isBlank(keyValue[i])) { + i++; } - return metadata; - } - - /** - * Did the client provide any metadata when creating this upload? - * @return True if metadata is present, false otherwise - */ - public boolean hasMetadata() { - return StringUtils.isNotBlank(encodedMetadata); - } - - /** - * Get the total length of the byte array that the client wants to upload. This value is provided by the client - * when creating the upload (POST) or when uploading a new set of bytes (PATCH). - * @return The number of bytes that the client specified he will upload - */ - public Long getLength() { - return length; - } - /** - * Set the total length of the byte array that the client wants to upload. The client can provided this value - * when creating the upload (POST) or when uploading a new set of bytes (PATCH). - * @param length The number of bytes that the client specified he will upload - */ - public void setLength(Long length) { - this.length = (length != null && length > 0 ? length : null); - } - - /** - * Did the client already provide a total upload length? - * @return True if the total upload length is known, false otherwise - */ - public boolean hasLength() { - return length != null; - } - - /** - * An upload is still in progress: - * - as long as we did not receive information on the total length (see {@link UploadInfo#getLength()}) - * - the total length does not match the current offset - * @return true if the upload is still in progress, false otherwise - */ - public boolean isUploadInProgress() { - return length == null || !offset.equals(length); - } - - /** - * Set the unique identifier of this upload process - * The unique identifier is represented by a {@link UploadId} instance - * @param id The unique identifier to use - */ - public void setId(UploadId id) { - this.id = id; - } - - /** - * Get the unique identifier of this upload process - * The unique identifier is represented by a {@link UploadId} instance - * @return The unique upload identifier of this upload - */ - public UploadId getId() { - return id; - } - - /** - * Set the owner key for this upload. - * This key uniquely identifies the owner of the uploaded bytes. The user of this library is free to interpret the - * meaning of "owner". This can be a user ID, a company division, a group of users, a tenant... - * @param ownerKey The owner key to assign to this upload - */ - public void setOwnerKey(String ownerKey) { - this.ownerKey = ownerKey; - } - - /** - * Get the owner key for this upload. - * This key uniquely identifies the owner of the uploaded bytes. The user of this library is free to interpret the - * meaning of "owner". This can be a user ID, a company division, a group of users, a tenant... - * @return The unique identifying key of the owner of this upload - */ - public String getOwnerKey() { - return ownerKey; - } - - /** - * Indicates the timestamp after which the upload expires in milliseconds since January 1, 1970, 00:00:00 GMT - * @return The expiration timestamp in milliseconds - */ - public Long getExpirationTimestamp() { - return expirationTimestamp; - } - - /** - * Calculate the expiration timestamp based on the provided expiration period. - * @param expirationPeriod The period the upload should remain valid - */ - public void updateExpiration(long expirationPeriod) { - expirationTimestamp = getCurrentTime() + expirationPeriod; - } - - /** - * The timestamp this upload was created in number of milliseconds since January 1, 1970, 00:00:00 GMT - * @return Creation timestamp of this upload object - */ - public Long getCreationTimestamp() { - return creationTimestamp; - } - - /** - * Get the ip-addresses that were involved when this upload was created. - * The returned value is a comma-separated list based on the remote address of the request and the - * X-Forwareded-For header. The list is constructed as "client, proxy1, proxy2". - * @return A comma-separated list of ip-addresses - */ - public String getCreatorIpAddresses() { - return creatorIpAddresses; - } - - /** - * Return the type of this upload. An upload can have types specified in {@link UploadType}. - * The type of an upload depends on the Tus concatenation extension: - * https://tus.io/protocols/resumable-upload.html#concatenation - * @return The type of this upload as specified in {@link UploadType} - */ - public UploadType getUploadType() { - return uploadType; - } - - /** - * Set the type of this upload. An upload can have types specified in {@link UploadType}. - * The type of an upload depends on the Tus concatenation extension: - * https://tus.io/protocols/resumable-upload.html#concatenation - * @param uploadType The type to set on this upload - */ - public void setUploadType(UploadType uploadType) { - this.uploadType = uploadType; - } - - /** - * Set the list of upload identifiers of which this upload is composed of. - * @param concatenationPartIds The list of child upload identifiers - */ - public void setConcatenationPartIds(List concatenationPartIds) { - this.concatenationPartIds = concatenationPartIds; - } - - /** - * Get the list of upload identifiers of which this upload is composed of. - * @return The list of child upload identifiers - */ - public List getConcatenationPartIds() { - return concatenationPartIds; - } - - /** - * Set the original value of the "Upload-Concat" HTTP header that was provided by the client - * @param uploadConcatHeaderValue The original value of the "Upload-Concat" HTTP header - */ - public void setUploadConcatHeaderValue(String uploadConcatHeaderValue) { - this.uploadConcatHeaderValue = uploadConcatHeaderValue; - } - - /** - * Get the original value of the "Upload-Concat" HTTP header that was provided by the client - * @return The original value of the "Upload-Concat" HTTP header - */ - public String getUploadConcatHeaderValue() { - return uploadConcatHeaderValue; - } - - /** - * Try to guess the filename of the uploaded data. If we cannot guess the name - * we fall back to the ID. - *

- * NOTE: This is only a guess, there are no guarantees that the return value is correct - * - * @return A potential file name - */ - public String getFileName() { - Map metadata = getMetadata(); - for (String fileNameKey : fileNameKeys) { - if (metadata.containsKey(fileNameKey)) { - return metadata.get(fileNameKey); - } - } - - return getId().toString(); - } - - /** - * Try to guess the mime-type of the uploaded data. - *

- * NOTE: This is only a guess, there are no guarantees that the return value is correct - * - * @return A potential file name - */ - public String getFileMimeType() { - Map metadata = getMetadata(); - for (String fileNameKey : mimeTypeKeys) { - if (metadata.containsKey(fileNameKey)) { - return metadata.get(fileNameKey); - } + if (keyValue.length > i) { + value = decode(keyValue[i]); } - return APPLICATION_OCTET_STREAM; - } - - /** - * Check if this upload is expired - * @return True if the upload is expired, false otherwise - */ - public boolean isExpired() { - return expirationTimestamp != null && expirationTimestamp < getCurrentTime(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (!(o instanceof UploadInfo)) { - return false; - } - - UploadInfo that = (UploadInfo) o; - - return new EqualsBuilder() - .append(getUploadType(), that.getUploadType()) - .append(getOffset(), that.getOffset()) - .append(getEncodedMetadata(), that.getEncodedMetadata()) - .append(getLength(), that.getLength()) - .append(getId(), that.getId()) - .append(getOwnerKey(), that.getOwnerKey()) - .append(getCreatorIpAddresses(), that.getCreatorIpAddresses()) - .append(getExpirationTimestamp(), that.getExpirationTimestamp()) - .append(getConcatenationPartIds(), that.getConcatenationPartIds()) - .append(getUploadConcatHeaderValue(), that.getUploadConcatHeaderValue()) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(getUploadType()) - .append(getOffset()) - .append(getEncodedMetadata()) - .append(getLength()) - .append(getId()) - .append(getOwnerKey()) - .append(getCreatorIpAddresses()) - .append(getExpirationTimestamp()) - .append(getConcatenationPartIds()) - .append(getUploadConcatHeaderValue()) - .toHashCode(); - } - - /** - * Get the current time in the number of milliseconds since January 1, 1970, 00:00:00 GMT - */ - protected long getCurrentTime() { - return new Date().getTime(); - } - - private String[] splitToArray(String value, String separatorRegex) { - if (StringUtils.isBlank(value)) { - return new String[0]; - } else { - return StringUtils.trimToEmpty(value).split(separatorRegex); - } - } - - private String decode(String encodedValue) { - if (encodedValue == null) { - return null; - } else { - return new String(Base64.decodeBase64(encodedValue), Charsets.UTF_8); - } - } + metadata.put(key, value); + } + } + return metadata; + } + + /** + * Did the client provide any metadata when creating this upload? + * + * @return True if metadata is present, false otherwise + */ + public boolean hasMetadata() { + return StringUtils.isNotBlank(encodedMetadata); + } + + /** + * Get the total length of the byte array that the client wants to upload. This value is provided + * by the client when creating the upload (POST) or when uploading a new set of bytes (PATCH). + * + * @return The number of bytes that the client specified he will upload + */ + public Long getLength() { + return length; + } + + /** + * Set the total length of the byte array that the client wants to upload. The client can provided + * this value when creating the upload (POST) or when uploading a new set of bytes (PATCH). + * + * @param length The number of bytes that the client specified he will upload + */ + public void setLength(Long length) { + this.length = (length != null && length > 0 ? length : null); + } + + /** + * Did the client already provide a total upload length? + * + * @return True if the total upload length is known, false otherwise + */ + public boolean hasLength() { + return length != null; + } + + /** + * An upload is still in progress: - as long as we did not receive information on the total length + * (see {@link UploadInfo#getLength()}) - the total length does not match the current offset + * + * @return true if the upload is still in progress, false otherwise + */ + public boolean isUploadInProgress() { + return length == null || !offset.equals(length); + } + + /** + * Set the unique identifier of this upload process The unique identifier is represented by a + * {@link UploadId} instance + * + * @param id The unique identifier to use + */ + public void setId(UploadId id) { + this.id = id; + } + + /** + * Get the unique identifier of this upload process The unique identifier is represented by a + * {@link UploadId} instance + * + * @return The unique upload identifier of this upload + */ + public UploadId getId() { + return id; + } + + /** + * Set the owner key for this upload. This key uniquely identifies the owner of the uploaded + * bytes. The user of this library is free to interpret the meaning of "owner". This can be a user + * ID, a company division, a group of users, a tenant... + * + * @param ownerKey The owner key to assign to this upload + */ + public void setOwnerKey(String ownerKey) { + this.ownerKey = ownerKey; + } + + /** + * Get the owner key for this upload. This key uniquely identifies the owner of the uploaded + * bytes. The user of this library is free to interpret the meaning of "owner". This can be a user + * ID, a company division, a group of users, a tenant... + * + * @return The unique identifying key of the owner of this upload + */ + public String getOwnerKey() { + return ownerKey; + } + + /** + * Indicates the timestamp after which the upload expires in milliseconds since January 1, 1970, + * 00:00:00 GMT + * + * @return The expiration timestamp in milliseconds + */ + public Long getExpirationTimestamp() { + return expirationTimestamp; + } + + /** + * Calculate the expiration timestamp based on the provided expiration period. + * + * @param expirationPeriod The period the upload should remain valid + */ + public void updateExpiration(long expirationPeriod) { + expirationTimestamp = getCurrentTime() + expirationPeriod; + } + + /** + * The timestamp this upload was created in number of milliseconds since January 1, 1970, 00:00:00 + * GMT + * + * @return Creation timestamp of this upload object + */ + public Long getCreationTimestamp() { + return creationTimestamp; + } + + /** + * Get the ip-addresses that were involved when this upload was created. The returned value is a + * comma-separated list based on the remote address of the request and the X-Forwareded-For + * header. The list is constructed as "client, proxy1, proxy2". + * + * @return A comma-separated list of ip-addresses + */ + public String getCreatorIpAddresses() { + return creatorIpAddresses; + } + + /** + * Return the type of this upload. An upload can have types specified in {@link UploadType}. The + * type of an upload depends on the Tus concatenation extension: + * https://tus.io/protocols/resumable-upload.html#concatenation + * + * @return The type of this upload as specified in {@link UploadType} + */ + public UploadType getUploadType() { + return uploadType; + } + + /** + * Set the type of this upload. An upload can have types specified in {@link UploadType}. The type + * of an upload depends on the Tus concatenation extension: + * https://tus.io/protocols/resumable-upload.html#concatenation + * + * @param uploadType The type to set on this upload + */ + public void setUploadType(UploadType uploadType) { + this.uploadType = uploadType; + } + + /** + * Set the list of upload identifiers of which this upload is composed of. + * + * @param concatenationPartIds The list of child upload identifiers + */ + public void setConcatenationPartIds(List concatenationPartIds) { + this.concatenationPartIds = concatenationPartIds; + } + + /** + * Get the list of upload identifiers of which this upload is composed of. + * + * @return The list of child upload identifiers + */ + public List getConcatenationPartIds() { + return concatenationPartIds; + } + + /** + * Set the original value of the "Upload-Concat" HTTP header that was provided by the client + * + * @param uploadConcatHeaderValue The original value of the "Upload-Concat" HTTP header + */ + public void setUploadConcatHeaderValue(String uploadConcatHeaderValue) { + this.uploadConcatHeaderValue = uploadConcatHeaderValue; + } + + /** + * Get the original value of the "Upload-Concat" HTTP header that was provided by the client + * + * @return The original value of the "Upload-Concat" HTTP header + */ + public String getUploadConcatHeaderValue() { + return uploadConcatHeaderValue; + } + + /** + * Try to guess the filename of the uploaded data. If we cannot guess the name we fall back to the + * ID. + * + *

NOTE: This is only a guess, there are no guarantees that the return value is correct + * + * @return A potential file name + */ + public String getFileName() { + Map metadata = getMetadata(); + for (String fileNameKey : fileNameKeys) { + if (metadata.containsKey(fileNameKey)) { + return metadata.get(fileNameKey); + } + } + + return getId().toString(); + } + + /** + * Try to guess the mime-type of the uploaded data. + * + *

NOTE: This is only a guess, there are no guarantees that the return value is correct + * + * @return A potential file name + */ + public String getFileMimeType() { + Map metadata = getMetadata(); + for (String fileNameKey : mimeTypeKeys) { + if (metadata.containsKey(fileNameKey)) { + return metadata.get(fileNameKey); + } + } + + return APPLICATION_OCTET_STREAM; + } + + /** + * Check if this upload is expired + * + * @return True if the upload is expired, false otherwise + */ + public boolean isExpired() { + return expirationTimestamp != null && expirationTimestamp < getCurrentTime(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof UploadInfo)) { + return false; + } + + UploadInfo that = (UploadInfo) o; + + return new EqualsBuilder() + .append(getUploadType(), that.getUploadType()) + .append(getOffset(), that.getOffset()) + .append(getEncodedMetadata(), that.getEncodedMetadata()) + .append(getLength(), that.getLength()) + .append(getId(), that.getId()) + .append(getOwnerKey(), that.getOwnerKey()) + .append(getCreatorIpAddresses(), that.getCreatorIpAddresses()) + .append(getExpirationTimestamp(), that.getExpirationTimestamp()) + .append(getConcatenationPartIds(), that.getConcatenationPartIds()) + .append(getUploadConcatHeaderValue(), that.getUploadConcatHeaderValue()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getUploadType()) + .append(getOffset()) + .append(getEncodedMetadata()) + .append(getLength()) + .append(getId()) + .append(getOwnerKey()) + .append(getCreatorIpAddresses()) + .append(getExpirationTimestamp()) + .append(getConcatenationPartIds()) + .append(getUploadConcatHeaderValue()) + .toHashCode(); + } + + /** Get the current time in the number of milliseconds since January 1, 1970, 00:00:00 GMT */ + protected long getCurrentTime() { + return new Date().getTime(); + } + + private String[] splitToArray(String value, String separatorRegex) { + if (StringUtils.isBlank(value)) { + return new String[0]; + } else { + return StringUtils.trimToEmpty(value).split(separatorRegex); + } + } + + private String decode(String encodedValue) { + if (encodedValue == null) { + return null; + } else { + return new String(Base64.decodeBase64(encodedValue), Charsets.UTF_8); + } + } } diff --git a/src/main/java/me/desair/tus/server/upload/UploadLock.java b/src/main/java/me/desair/tus/server/upload/UploadLock.java index e45ea06..3183bff 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadLock.java +++ b/src/main/java/me/desair/tus/server/upload/UploadLock.java @@ -2,23 +2,22 @@ import java.io.IOException; -/** - * Interface that represents a lock on an upload - */ +/** Interface that represents a lock on an upload */ public interface UploadLock extends AutoCloseable { - /** - * Get the upload URI of the upload that is locked by this lock - * @return The URI of the locked upload - */ - String getUploadUri(); + /** + * Get the upload URI of the upload that is locked by this lock + * + * @return The URI of the locked upload + */ + String getUploadUri(); - /** - * Method to release the lock on an upload when done processing it. It's possible that this method is - * called multiple times within the same request - */ - void release(); + /** + * Method to release the lock on an upload when done processing it. It's possible that this method + * is called multiple times within the same request + */ + void release(); - @Override - void close() throws IOException; + @Override + void close() throws IOException; } diff --git a/src/main/java/me/desair/tus/server/upload/UploadLockingService.java b/src/main/java/me/desair/tus/server/upload/UploadLockingService.java index 6dc52af..4a7dd51 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadLockingService.java +++ b/src/main/java/me/desair/tus/server/upload/UploadLockingService.java @@ -1,39 +1,43 @@ package me.desair.tus.server.upload; import java.io.IOException; - import me.desair.tus.server.exception.TusException; /** - * Service interface that can lock a specific upload so that it cannot be modified by other requests/threads. + * Service interface that can lock a specific upload so that it cannot be modified by other + * requests/threads. */ public interface UploadLockingService { - /** - * If the given URI represents a valid upload, lock that upload for processing - * @param requestURI The URI that potentially represents an upload - * @return The lock on the upload, or null if not lock was applied - * @throws TusException If the upload is already locked - */ - UploadLock lockUploadByUri(String requestURI) throws TusException, IOException; + /** + * If the given URI represents a valid upload, lock that upload for processing + * + * @param requestURI The URI that potentially represents an upload + * @return The lock on the upload, or null if not lock was applied + * @throws TusException If the upload is already locked + */ + UploadLock lockUploadByUri(String requestURI) throws TusException, IOException; - /** - * Clean up any stale locks that are still present - * @throws TusException When cleaning a stale lock fails - */ - void cleanupStaleLocks() throws IOException; + /** + * Clean up any stale locks that are still present + * + * @throws TusException When cleaning a stale lock fails + */ + void cleanupStaleLocks() throws IOException; - /** - * Check if the upload with the given ID is currently locked - * @param id The ID of the upload to check - * @return True if the upload is locked, false otherwise - */ - boolean isLocked(UploadId id); + /** + * Check if the upload with the given ID is currently locked + * + * @param id The ID of the upload to check + * @return True if the upload is locked, false otherwise + */ + boolean isLocked(UploadId id); - /** - * Set an instance if IdFactory to be used for creating identities and extracting them from uploadURIs - * - * @param idFactory The {@link UploadIdFactory} to use within this locking service - */ - void setIdFactory(UploadIdFactory idFactory); + /** + * Set an instance if IdFactory to be used for creating identities and extracting them from + * uploadURIs + * + * @param idFactory The {@link UploadIdFactory} to use within this locking service + */ + void setIdFactory(UploadIdFactory idFactory); } diff --git a/src/main/java/me/desair/tus/server/upload/UploadStorageService.java b/src/main/java/me/desair/tus/server/upload/UploadStorageService.java index f5d18c0..3aea929 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadStorageService.java +++ b/src/main/java/me/desair/tus/server/upload/UploadStorageService.java @@ -3,146 +3,166 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.concatenation.UploadConcatenationService; -/** - * Interface to a service that is able to store the (partially) uploaded files. - */ +/** Interface to a service that is able to store the (partially) uploaded files. */ public interface UploadStorageService { - /** - * Method to retrieve the upload info by its upload URL - * @param uploadUrl The URL corresponding to this upload. This parameter can be null. - * @param ownerKey A key representing the owner of the upload - * @return The upload info matching the given URL, or null when not found. - */ - UploadInfo getUploadInfo(String uploadUrl, String ownerKey) throws IOException; - - /** - * Method to retrieve the upload info by its ID - * @param id The ID of the upload - * @return The matching upload info - * @throws IOException When the service is not able to retrieve the upload information - */ - UploadInfo getUploadInfo(UploadId id) throws IOException; - - /** - * The URI which is configured as the upload endpoint - * @return The URI - */ - String getUploadURI(); - - /** - * Append the bytes in the give {@link InputStream} to the upload with the given ID starting at the provided offset. - * This method also updates the {@link UploadInfo} corresponding to this upload. The Upload Storage server should - * not exceed its max upload size when writing bytes. - * @param upload The ID of the upload - * @param inputStream The input stream containing the bytes to append - * @return The new {@link UploadInfo} for this upload - */ - UploadInfo append(UploadInfo upload, InputStream inputStream) throws IOException, TusException; - - /** - * Limit the maximum upload size to the given value - * @param maxUploadSize The maximum upload limit to set - */ - void setMaxUploadSize(Long maxUploadSize); - - /** - * Get the maximum upload size configured on this storage service - * @return The maximum upload size or zero if no maximum - */ - long getMaxUploadSize(); - - /** - * Create an upload location with the given upload information - * @param info The Upload information to use to create the new upload - * @param ownerKey A key representing the owner of the upload - * @return An {@link UploadInfo} object containing the information used to create the upload and its unique ID - */ - UploadInfo create(UploadInfo info, String ownerKey) throws IOException; - - /** - * Update the upload information for the provided ID. - * @param uploadInfo The upload info object containing the ID and information to update - */ - void update(UploadInfo uploadInfo) throws IOException, UploadNotFoundException; - - /** - * Get the uploaded bytes corresponding to the given upload URL as a stream - * @param uploadURI The URI - * @param ownerKey The owner key of this upload - * @return an {@link OutputStream} containing the bytes of the upload - */ - InputStream getUploadedBytes(String uploadURI, String ownerKey) - throws IOException, UploadNotFoundException; - - /** - * Get the uploaded bytes corresponding to the given upload ID as a stream - * @param id the ID of the upload - * @return an {@link OutputStream} containing the bytes of the upload - * @throws IOException When retrieving the bytes from the storage layer fails - * @throws UploadNotFoundException When the proved id is not linked to an upload - */ - InputStream getUploadedBytes(UploadId id) throws IOException, UploadNotFoundException; - - /** - * Copy the uploaded bytes to the given output stream - * @param info The upload of which we should copy the bytes - * @param outputStream The output stream where we have to copy the bytes to - */ - void copyUploadTo(UploadInfo info, OutputStream outputStream) throws UploadNotFoundException, IOException; - - /** - * Clean up any upload data that is expired according to the configured expiration time - * @param uploadLockingService An {@link UploadLockingService} that can be used to check and lock uploads - */ - void cleanupExpiredUploads(UploadLockingService uploadLockingService) throws IOException; - - /** - * Remove the given last amount of bytes from the uploaded data - * @param uploadInfo Upload of which to remove the bytes - * @param byteCount The number of bytes to remove at the end - */ - void removeLastNumberOfBytes(UploadInfo uploadInfo, long byteCount) throws UploadNotFoundException, IOException; - - /** - * Terminate completed and unfinished uploads allowing the Server to free up used resources. - * @param uploadInfo The upload to terminate - */ - void terminateUpload(UploadInfo uploadInfo) throws UploadNotFoundException, IOException; - - /** - * Get the expiration period of an upload in milliseconds - * @return The number of milliseconds before an upload expires, or null if it cannot expire - */ - Long getUploadExpirationPeriod(); - - /** - * Set the expiration period after which an in-progress upload expires - * @param uploadExpirationPeriod The period in milliseconds - */ - void setUploadExpirationPeriod(Long uploadExpirationPeriod); - - /** - * Set the {@link UploadConcatenationService} that this upload storage service should use - * @param concatenationService The UploadConcatenationService implementation to use - */ - void setUploadConcatenationService(UploadConcatenationService concatenationService); - - /** - * Return the {@link UploadConcatenationService} implementation that this upload service is using - * @return The UploadConcatenationService that is being used - */ - UploadConcatenationService getUploadConcatenationService(); - - /** - * Set an instance if IdFactory to be used for creating identities and extracting them from uploadURIs - * - * @param idFactory The {@link UploadIdFactory} to use within this storage service - */ - void setIdFactory(UploadIdFactory idFactory); + /** + * Method to retrieve the upload info by its upload URL + * + * @param uploadUrl The URL corresponding to this upload. This parameter can be null. + * @param ownerKey A key representing the owner of the upload + * @return The upload info matching the given URL, or null when not found. + */ + UploadInfo getUploadInfo(String uploadUrl, String ownerKey) throws IOException; + + /** + * Method to retrieve the upload info by its ID + * + * @param id The ID of the upload + * @return The matching upload info + * @throws IOException When the service is not able to retrieve the upload information + */ + UploadInfo getUploadInfo(UploadId id) throws IOException; + + /** + * The URI which is configured as the upload endpoint + * + * @return The URI + */ + String getUploadURI(); + + /** + * Append the bytes in the give {@link InputStream} to the upload with the given ID starting at + * the provided offset. This method also updates the {@link UploadInfo} corresponding to this + * upload. The Upload Storage server should not exceed its max upload size when writing bytes. + * + * @param upload The ID of the upload + * @param inputStream The input stream containing the bytes to append + * @return The new {@link UploadInfo} for this upload + */ + UploadInfo append(UploadInfo upload, InputStream inputStream) throws IOException, TusException; + + /** + * Limit the maximum upload size to the given value + * + * @param maxUploadSize The maximum upload limit to set + */ + void setMaxUploadSize(Long maxUploadSize); + + /** + * Get the maximum upload size configured on this storage service + * + * @return The maximum upload size or zero if no maximum + */ + long getMaxUploadSize(); + + /** + * Create an upload location with the given upload information + * + * @param info The Upload information to use to create the new upload + * @param ownerKey A key representing the owner of the upload + * @return An {@link UploadInfo} object containing the information used to create the upload and + * its unique ID + */ + UploadInfo create(UploadInfo info, String ownerKey) throws IOException; + + /** + * Update the upload information for the provided ID. + * + * @param uploadInfo The upload info object containing the ID and information to update + */ + void update(UploadInfo uploadInfo) throws IOException, UploadNotFoundException; + + /** + * Get the uploaded bytes corresponding to the given upload URL as a stream + * + * @param uploadURI The URI + * @param ownerKey The owner key of this upload + * @return an {@link OutputStream} containing the bytes of the upload + */ + InputStream getUploadedBytes(String uploadURI, String ownerKey) + throws IOException, UploadNotFoundException; + + /** + * Get the uploaded bytes corresponding to the given upload ID as a stream + * + * @param id the ID of the upload + * @return an {@link OutputStream} containing the bytes of the upload + * @throws IOException When retrieving the bytes from the storage layer fails + * @throws UploadNotFoundException When the proved id is not linked to an upload + */ + InputStream getUploadedBytes(UploadId id) throws IOException, UploadNotFoundException; + + /** + * Copy the uploaded bytes to the given output stream + * + * @param info The upload of which we should copy the bytes + * @param outputStream The output stream where we have to copy the bytes to + */ + void copyUploadTo(UploadInfo info, OutputStream outputStream) + throws UploadNotFoundException, IOException; + + /** + * Clean up any upload data that is expired according to the configured expiration time + * + * @param uploadLockingService An {@link UploadLockingService} that can be used to check and lock + * uploads + */ + void cleanupExpiredUploads(UploadLockingService uploadLockingService) throws IOException; + + /** + * Remove the given last amount of bytes from the uploaded data + * + * @param uploadInfo Upload of which to remove the bytes + * @param byteCount The number of bytes to remove at the end + */ + void removeLastNumberOfBytes(UploadInfo uploadInfo, long byteCount) + throws UploadNotFoundException, IOException; + + /** + * Terminate completed and unfinished uploads allowing the Server to free up used resources. + * + * @param uploadInfo The upload to terminate + */ + void terminateUpload(UploadInfo uploadInfo) throws UploadNotFoundException, IOException; + + /** + * Get the expiration period of an upload in milliseconds + * + * @return The number of milliseconds before an upload expires, or null if it cannot expire + */ + Long getUploadExpirationPeriod(); + + /** + * Set the expiration period after which an in-progress upload expires + * + * @param uploadExpirationPeriod The period in milliseconds + */ + void setUploadExpirationPeriod(Long uploadExpirationPeriod); + + /** + * Set the {@link UploadConcatenationService} that this upload storage service should use + * + * @param concatenationService The UploadConcatenationService implementation to use + */ + void setUploadConcatenationService(UploadConcatenationService concatenationService); + + /** + * Return the {@link UploadConcatenationService} implementation that this upload service is using + * + * @return The UploadConcatenationService that is being used + */ + UploadConcatenationService getUploadConcatenationService(); + + /** + * Set an instance if IdFactory to be used for creating identities and extracting them from + * uploadURIs + * + * @param idFactory The {@link UploadIdFactory} to use within this storage service + */ + void setIdFactory(UploadIdFactory idFactory); } diff --git a/src/main/java/me/desair/tus/server/upload/UploadType.java b/src/main/java/me/desair/tus/server/upload/UploadType.java index aa2e652..5dceeae 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadType.java +++ b/src/main/java/me/desair/tus/server/upload/UploadType.java @@ -1,21 +1,13 @@ package me.desair.tus.server.upload; -/** - * Enum that lists all the possible upload types in the tus protocol - */ +/** Enum that lists all the possible upload types in the tus protocol */ public enum UploadType { - /** - * REGULAR indicates a normal upload - */ - REGULAR, + /** REGULAR indicates a normal upload */ + REGULAR, - /** - * PARTIAL indicates an upload that is part of a concatenated upload - */ - PARTIAL, + /** PARTIAL indicates an upload that is part of a concatenated upload */ + PARTIAL, - /** - * CONCATENATED is the upload that combines different partial uploads - */ - CONCATENATED + /** CONCATENATED is the upload that combines different partial uploads */ + CONCATENATED } diff --git a/src/main/java/me/desair/tus/server/upload/cache/ThreadLocalCachedStorageAndLockingService.java b/src/main/java/me/desair/tus/server/upload/cache/ThreadLocalCachedStorageAndLockingService.java index 3c25ffc..3190788 100644 --- a/src/main/java/me/desair/tus/server/upload/cache/ThreadLocalCachedStorageAndLockingService.java +++ b/src/main/java/me/desair/tus/server/upload/cache/ThreadLocalCachedStorageAndLockingService.java @@ -5,7 +5,6 @@ import java.io.OutputStream; import java.lang.ref.WeakReference; import java.util.Objects; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.UploadId; @@ -17,208 +16,213 @@ import me.desair.tus.server.upload.concatenation.UploadConcatenationService; /** - * Combined implementation of {@link UploadStorageService} and {@link UploadLockingService}. - * Uses both of them as delegates but allowing to reduce disk operations during a request processing - * by caching UploadInfo in the memory. - * UploadLockingService service is used as a delegate to cleanup cached data on releasing a lock. + * Combined implementation of {@link UploadStorageService} and {@link UploadLockingService}. Uses + * both of them as delegates but allowing to reduce disk operations during a request processing by + * caching UploadInfo in the memory. UploadLockingService service is used as a delegate to cleanup + * cached data on releasing a lock. */ -public class ThreadLocalCachedStorageAndLockingService implements UploadLockingService, UploadStorageService { - - private final ThreadLocal> uploadInfoCache = new ThreadLocal<>(); - private final UploadLockingService lockingServiceDelegate; - private final UploadStorageService storageServiceDelegate; - private UploadIdFactory idFactory; - - public ThreadLocalCachedStorageAndLockingService(UploadStorageService storageServiceDelegate, - UploadLockingService lockingServiceDelegate) { - if (storageServiceDelegate.getClass() == ThreadLocalCachedStorageAndLockingService.class) { - this.storageServiceDelegate = - ((ThreadLocalCachedStorageAndLockingService) storageServiceDelegate).storageServiceDelegate; - } else { - this.storageServiceDelegate = storageServiceDelegate; - } - if (lockingServiceDelegate.getClass() == ThreadLocalCachedStorageAndLockingService.class) { - this.lockingServiceDelegate = - ((ThreadLocalCachedStorageAndLockingService) lockingServiceDelegate).lockingServiceDelegate; - } else { - this.lockingServiceDelegate = lockingServiceDelegate; - } - } - - @Override - public UploadInfo getUploadInfo(UploadId id) throws IOException { - UploadInfo uploadInfo; - WeakReference ref = uploadInfoCache.get(); - if (ref == null || (uploadInfo = ref.get()) == null || !id.equals(uploadInfo.getId())) { - uploadInfo = storageServiceDelegate.getUploadInfo(id); - uploadInfoCache.set(new WeakReference<>(uploadInfo)); - } - return uploadInfo; - } - - @Override - public UploadInfo getUploadInfo(String uploadUrl, String ownerKey) throws IOException { - UploadInfo uploadInfo = getUploadInfo(idFactory.readUploadId(uploadUrl)); - if (uploadInfo == null || !Objects.equals(uploadInfo.getOwnerKey(), ownerKey)) { - uploadInfo = storageServiceDelegate.getUploadInfo(uploadUrl, ownerKey); - uploadInfoCache.set(new WeakReference<>(uploadInfo)); - } - return uploadInfo; - } - - @Override - public void update(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { - storageServiceDelegate.update(uploadInfo); - uploadInfoCache.set(new WeakReference<>(uploadInfo)); - } - - @Override - public void setIdFactory(UploadIdFactory idFactory) { - this.idFactory = idFactory; - this.storageServiceDelegate.setIdFactory(idFactory); - this.lockingServiceDelegate.setIdFactory(idFactory); - } - - @Override - public String getUploadURI() { - return storageServiceDelegate.getUploadURI(); - } - +public class ThreadLocalCachedStorageAndLockingService + implements UploadLockingService, UploadStorageService { + + private final ThreadLocal> uploadInfoCache = new ThreadLocal<>(); + private final UploadLockingService lockingServiceDelegate; + private final UploadStorageService storageServiceDelegate; + private UploadIdFactory idFactory; + + public ThreadLocalCachedStorageAndLockingService( + UploadStorageService storageServiceDelegate, UploadLockingService lockingServiceDelegate) { + if (storageServiceDelegate.getClass() == ThreadLocalCachedStorageAndLockingService.class) { + this.storageServiceDelegate = + ((ThreadLocalCachedStorageAndLockingService) storageServiceDelegate) + .storageServiceDelegate; + } else { + this.storageServiceDelegate = storageServiceDelegate; + } + if (lockingServiceDelegate.getClass() == ThreadLocalCachedStorageAndLockingService.class) { + this.lockingServiceDelegate = + ((ThreadLocalCachedStorageAndLockingService) lockingServiceDelegate) + .lockingServiceDelegate; + } else { + this.lockingServiceDelegate = lockingServiceDelegate; + } + } + + @Override + public UploadInfo getUploadInfo(UploadId id) throws IOException { + UploadInfo uploadInfo; + WeakReference ref = uploadInfoCache.get(); + if (ref == null || (uploadInfo = ref.get()) == null || !id.equals(uploadInfo.getId())) { + uploadInfo = storageServiceDelegate.getUploadInfo(id); + uploadInfoCache.set(new WeakReference<>(uploadInfo)); + } + return uploadInfo; + } + + @Override + public UploadInfo getUploadInfo(String uploadUrl, String ownerKey) throws IOException { + UploadInfo uploadInfo = getUploadInfo(idFactory.readUploadId(uploadUrl)); + if (uploadInfo == null || !Objects.equals(uploadInfo.getOwnerKey(), ownerKey)) { + uploadInfo = storageServiceDelegate.getUploadInfo(uploadUrl, ownerKey); + uploadInfoCache.set(new WeakReference<>(uploadInfo)); + } + return uploadInfo; + } + + @Override + public void update(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { + storageServiceDelegate.update(uploadInfo); + uploadInfoCache.set(new WeakReference<>(uploadInfo)); + } + + @Override + public void setIdFactory(UploadIdFactory idFactory) { + this.idFactory = idFactory; + this.storageServiceDelegate.setIdFactory(idFactory); + this.lockingServiceDelegate.setIdFactory(idFactory); + } + + @Override + public String getUploadURI() { + return storageServiceDelegate.getUploadURI(); + } + + @Override + public UploadInfo append(UploadInfo upload, InputStream inputStream) + throws IOException, TusException { + UploadInfo info = storageServiceDelegate.append(upload, inputStream); + uploadInfoCache.set(new WeakReference<>(info)); + return info; + } + + @Override + public void setMaxUploadSize(Long maxUploadSize) { + storageServiceDelegate.setMaxUploadSize(maxUploadSize); + } + + @Override + public long getMaxUploadSize() { + return storageServiceDelegate.getMaxUploadSize(); + } + + @Override + public UploadInfo create(UploadInfo info, String ownerKey) throws IOException { + UploadInfo uploadInfo = storageServiceDelegate.create(info, ownerKey); + uploadInfoCache.set(new WeakReference<>(uploadInfo)); + return uploadInfo; + } + + @Override + public InputStream getUploadedBytes(String uploadURI, String ownerKey) + throws IOException, UploadNotFoundException { + return storageServiceDelegate.getUploadedBytes(uploadURI, ownerKey); + } + + @Override + public InputStream getUploadedBytes(UploadId id) throws IOException, UploadNotFoundException { + return storageServiceDelegate.getUploadedBytes(id); + } + + @Override + public void copyUploadTo(UploadInfo info, OutputStream outputStream) + throws UploadNotFoundException, IOException { + storageServiceDelegate.copyUploadTo(info, outputStream); + uploadInfoCache.set(new WeakReference<>(info)); + } + + @Override + public void cleanupExpiredUploads(UploadLockingService uploadLockingService) throws IOException { + storageServiceDelegate.cleanupExpiredUploads(uploadLockingService); + // Since any cached uploads was potentially removed by the storage service + // we clean the cache to prevent any stale state + cleanupCache(); + } + + @Override + public void removeLastNumberOfBytes(UploadInfo uploadInfo, long byteCount) + throws UploadNotFoundException, IOException { + storageServiceDelegate.removeLastNumberOfBytes(uploadInfo, byteCount); + uploadInfoCache.set(new WeakReference<>(uploadInfo)); + } + + @Override + public void terminateUpload(UploadInfo uploadInfo) throws UploadNotFoundException, IOException { + storageServiceDelegate.terminateUpload(uploadInfo); + // Since the upload is terminated and potentially removed by the storage service + // we clean the cache to prevent any stale state + cleanupCache(); + } + + @Override + public Long getUploadExpirationPeriod() { + return storageServiceDelegate.getUploadExpirationPeriod(); + } + + @Override + public void setUploadExpirationPeriod(Long uploadExpirationPeriod) { + storageServiceDelegate.setUploadExpirationPeriod(uploadExpirationPeriod); + } + + @Override + public void setUploadConcatenationService(UploadConcatenationService concatenationService) { + storageServiceDelegate.setUploadConcatenationService(concatenationService); + } + + @Override + public UploadConcatenationService getUploadConcatenationService() { + return storageServiceDelegate.getUploadConcatenationService(); + } + + @Override + public UploadLock lockUploadByUri(String requestURI) throws TusException, IOException { + UploadLock uploadLock = lockingServiceDelegate.lockUploadByUri(requestURI); + return new CachedLock(uploadLock); + } + + @Override + public void cleanupStaleLocks() throws IOException { + lockingServiceDelegate.cleanupStaleLocks(); + cleanupCache(); + } + + @Override + public boolean isLocked(UploadId id) { + return lockingServiceDelegate.isLocked(id); + } + + private void cleanupCache() { + WeakReference ref = uploadInfoCache.get(); + if (ref != null) { + uploadInfoCache.remove(); + ref.clear(); + } + } + + class CachedLock implements UploadLock { + + private final UploadLock delegate; + + CachedLock(UploadLock delegate) { + this.delegate = delegate; + } + + @Override + public String getUploadUri() { + return delegate != null ? delegate.getUploadUri() : null; + } + @Override - public UploadInfo append(UploadInfo upload, InputStream inputStream) throws IOException, TusException { - UploadInfo info = storageServiceDelegate.append(upload, inputStream); - uploadInfoCache.set(new WeakReference<>(info)); - return info; - } - - @Override - public void setMaxUploadSize(Long maxUploadSize) { - storageServiceDelegate.setMaxUploadSize(maxUploadSize); - } - - @Override - public long getMaxUploadSize() { - return storageServiceDelegate.getMaxUploadSize(); - } - - @Override - public UploadInfo create(UploadInfo info, String ownerKey) throws IOException { - UploadInfo uploadInfo = storageServiceDelegate.create(info, ownerKey); - uploadInfoCache.set(new WeakReference<>(uploadInfo)); - return uploadInfo; - - } - - @Override - public InputStream getUploadedBytes(String uploadURI, String ownerKey) throws IOException, UploadNotFoundException { - return storageServiceDelegate.getUploadedBytes(uploadURI, ownerKey); - } - - @Override - public InputStream getUploadedBytes(UploadId id) throws IOException, UploadNotFoundException { - return storageServiceDelegate.getUploadedBytes(id); - } - - @Override - public void copyUploadTo(UploadInfo info, OutputStream outputStream) throws UploadNotFoundException, IOException { - storageServiceDelegate.copyUploadTo(info, outputStream); - uploadInfoCache.set(new WeakReference<>(info)); - } - - @Override - public void cleanupExpiredUploads(UploadLockingService uploadLockingService) throws IOException { - storageServiceDelegate.cleanupExpiredUploads(uploadLockingService); - //Since any cached uploads was potentially removed by the storage service - //we clean the cache to prevent any stale state - cleanupCache(); - } - - @Override - public void removeLastNumberOfBytes(UploadInfo uploadInfo, long byteCount) throws UploadNotFoundException, - IOException { - storageServiceDelegate.removeLastNumberOfBytes(uploadInfo, byteCount); - uploadInfoCache.set(new WeakReference<>(uploadInfo)); - } - - @Override - public void terminateUpload(UploadInfo uploadInfo) throws UploadNotFoundException, IOException { - storageServiceDelegate.terminateUpload(uploadInfo); - //Since the upload is terminated and potentially removed by the storage service - //we clean the cache to prevent any stale state - cleanupCache(); - } - - @Override - public Long getUploadExpirationPeriod() { - return storageServiceDelegate.getUploadExpirationPeriod(); - } - - @Override - public void setUploadExpirationPeriod(Long uploadExpirationPeriod) { - storageServiceDelegate.setUploadExpirationPeriod(uploadExpirationPeriod); - } - - @Override - public void setUploadConcatenationService(UploadConcatenationService concatenationService) { - storageServiceDelegate.setUploadConcatenationService(concatenationService); - } - - @Override - public UploadConcatenationService getUploadConcatenationService() { - return storageServiceDelegate.getUploadConcatenationService(); - } - - @Override - public UploadLock lockUploadByUri(String requestURI) throws TusException, IOException { - UploadLock uploadLock = lockingServiceDelegate.lockUploadByUri(requestURI); - return new CachedLock(uploadLock); - } - - @Override - public void cleanupStaleLocks() throws IOException { - lockingServiceDelegate.cleanupStaleLocks(); - cleanupCache(); - } - - @Override - public boolean isLocked(UploadId id) { - return lockingServiceDelegate.isLocked(id); - } - - private void cleanupCache() { - WeakReference ref = uploadInfoCache.get(); - if (ref != null) { - uploadInfoCache.remove(); - ref.clear(); - } + public void release() { + if (delegate != null) { + delegate.release(); + } } - class CachedLock implements UploadLock { - - private final UploadLock delegate; - - CachedLock(UploadLock delegate) { - this.delegate = delegate; - } - - @Override - public String getUploadUri() { - return delegate != null ? delegate.getUploadUri() : null; - } - - @Override - public void release() { - if (delegate != null) { - delegate.release(); - } - } - - @Override - public void close() throws IOException { - if (delegate != null) { - delegate.close(); - } - cleanupCache(); - } + @Override + public void close() throws IOException { + if (delegate != null) { + delegate.close(); + } + cleanupCache(); } + } } diff --git a/src/main/java/me/desair/tus/server/upload/concatenation/UploadConcatenationService.java b/src/main/java/me/desair/tus/server/upload/concatenation/UploadConcatenationService.java index 1373fd4..60571ab 100644 --- a/src/main/java/me/desair/tus/server/upload/concatenation/UploadConcatenationService.java +++ b/src/main/java/me/desair/tus/server/upload/concatenation/UploadConcatenationService.java @@ -3,42 +3,43 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; - import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.UploadInfo; /** - * Interface for a service that is able to concatenate partial uploads - * into a concatenated upload + * Interface for a service that is able to concatenate partial uploads into a concatenated upload */ public interface UploadConcatenationService { - /** - * Merge the given concatenated upload if all the underlying partial uploads are completed. - * If the underlying partial uploads are still in-progress, this method does nothing. Otherwise - * the upload information of the concatenated upload is updated. - * - * @param uploadInfo The concatenated upload - * @throws IOException If merging the upload fails - * @throws UploadNotFoundException When one of the partial uploads cannot be found - */ - void merge(UploadInfo uploadInfo) throws IOException, UploadNotFoundException; + /** + * Merge the given concatenated upload if all the underlying partial uploads are completed. If the + * underlying partial uploads are still in-progress, this method does nothing. Otherwise the + * upload information of the concatenated upload is updated. + * + * @param uploadInfo The concatenated upload + * @throws IOException If merging the upload fails + * @throws UploadNotFoundException When one of the partial uploads cannot be found + */ + void merge(UploadInfo uploadInfo) throws IOException, UploadNotFoundException; - /** - * Get the concatenated bytes of this concatenated upload - * @param uploadInfo The concatenated upload - * @return The concatenated bytes, or null if this upload is still in progress - * @throws IOException When return the concatenated bytes fails - * @throws UploadNotFoundException When the or one of the partial uploads cannot be found - */ - InputStream getConcatenatedBytes(UploadInfo uploadInfo) throws IOException, UploadNotFoundException; + /** + * Get the concatenated bytes of this concatenated upload + * + * @param uploadInfo The concatenated upload + * @return The concatenated bytes, or null if this upload is still in progress + * @throws IOException When return the concatenated bytes fails + * @throws UploadNotFoundException When the or one of the partial uploads cannot be found + */ + InputStream getConcatenatedBytes(UploadInfo uploadInfo) + throws IOException, UploadNotFoundException; - /** - * Get all underlying partial uploads associated with the given concatenated upload - * @param info The concatenated upload - * @return The underlying partial uploads - * @throws IOException When retrieving the underlying partial uploads fails - * @throws UploadNotFoundException When one of the partial uploads cannot be found - */ - List getPartialUploads(UploadInfo info) throws IOException, UploadNotFoundException; + /** + * Get all underlying partial uploads associated with the given concatenated upload + * + * @param info The concatenated upload + * @return The underlying partial uploads + * @throws IOException When retrieving the underlying partial uploads fails + * @throws UploadNotFoundException When one of the partial uploads cannot be found + */ + List getPartialUploads(UploadInfo info) throws IOException, UploadNotFoundException; } diff --git a/src/main/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumeration.java b/src/main/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumeration.java index 7f35911..c78bbd6 100644 --- a/src/main/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumeration.java +++ b/src/main/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumeration.java @@ -6,7 +6,6 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.List; - import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.upload.UploadStorageService; @@ -18,55 +17,54 @@ */ public class UploadInputStreamEnumeration implements Enumeration { - private static final Logger log = LoggerFactory.getLogger(UploadInputStreamEnumeration.class); - - private List uploads; - private UploadStorageService uploadStorageService; - private Iterator uploadIterator; - private InputStream currentInputStream = null; + private static final Logger log = LoggerFactory.getLogger(UploadInputStreamEnumeration.class); - public UploadInputStreamEnumeration(List uploadList, - UploadStorageService uploadStorageService) { - this.uploads = new ArrayList<>(uploadList); - this.uploadStorageService = uploadStorageService; - this.uploadIterator = this.uploads.iterator(); - } + private List uploads; + private UploadStorageService uploadStorageService; + private Iterator uploadIterator; + private InputStream currentInputStream = null; - @Override - public boolean hasMoreElements() { - if (uploadIterator != null && uploadIterator.hasNext()) { - currentInputStream = getNextInputStream(); - } else { - currentInputStream = null; - } + public UploadInputStreamEnumeration( + List uploadList, UploadStorageService uploadStorageService) { + this.uploads = new ArrayList<>(uploadList); + this.uploadStorageService = uploadStorageService; + this.uploadIterator = this.uploads.iterator(); + } - //if we could not get a next upload stream, set the iterator to null - // to make sure repeated calls give the same result - if (currentInputStream == null) { - uploadIterator = null; - return false; - } else { - return true; - } + @Override + public boolean hasMoreElements() { + if (uploadIterator != null && uploadIterator.hasNext()) { + currentInputStream = getNextInputStream(); + } else { + currentInputStream = null; } - @Override - public InputStream nextElement() { - return currentInputStream; + // if we could not get a next upload stream, set the iterator to null + // to make sure repeated calls give the same result + if (currentInputStream == null) { + uploadIterator = null; + return false; + } else { + return true; } + } - private InputStream getNextInputStream() { - InputStream is = null; - UploadInfo info = uploadIterator.next(); - if (info != null) { - try { - is = uploadStorageService.getUploadedBytes(info.getId()); - } catch (IOException | UploadNotFoundException ex) { - log.error("Error while retrieving input stream for upload with ID " + info.getId(), ex); - is = null; - } - } - return is; - } + @Override + public InputStream nextElement() { + return currentInputStream; + } + private InputStream getNextInputStream() { + InputStream is = null; + UploadInfo info = uploadIterator.next(); + if (info != null) { + try { + is = uploadStorageService.getUploadedBytes(info.getId()); + } catch (IOException | UploadNotFoundException ex) { + log.error("Error while retrieving input stream for upload with ID " + info.getId(), ex); + is = null; + } + } + return is; + } } diff --git a/src/main/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationService.java b/src/main/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationService.java index 844d9b3..3006c9e 100644 --- a/src/main/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationService.java +++ b/src/main/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationService.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; - import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.upload.UploadStorageService; @@ -14,122 +13,129 @@ import org.slf4j.LoggerFactory; /** - * {@link UploadConcatenationService} implementation that uses the file system to keep track - * of concatenated uploads. The concatenation is executed "virtually" meaning that upload bytes - * are not duplicated to the upload but "concatenated" on the fly. + * {@link UploadConcatenationService} implementation that uses the file system to keep track of + * concatenated uploads. The concatenation is executed "virtually" meaning that upload bytes are not + * duplicated to the upload but "concatenated" on the fly. */ public class VirtualConcatenationService implements UploadConcatenationService { - private static final Logger log = LoggerFactory.getLogger(VirtualConcatenationService.class); - - private UploadStorageService uploadStorageService; + private static final Logger log = LoggerFactory.getLogger(VirtualConcatenationService.class); - public VirtualConcatenationService(UploadStorageService uploadStorageService) { - this.uploadStorageService = uploadStorageService; - } + private UploadStorageService uploadStorageService; - @Override - public void merge(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { - if (uploadInfo != null && uploadInfo.isUploadInProgress() - && uploadInfo.getConcatenationPartIds() != null) { + public VirtualConcatenationService(UploadStorageService uploadStorageService) { + this.uploadStorageService = uploadStorageService; + } - Long expirationPeriod = uploadStorageService.getUploadExpirationPeriod(); + @Override + public void merge(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { + if (uploadInfo != null + && uploadInfo.isUploadInProgress() + && uploadInfo.getConcatenationPartIds() != null) { - List partialUploads = getPartialUploads(uploadInfo); + Long expirationPeriod = uploadStorageService.getUploadExpirationPeriod(); - Long totalLength = calculateTotalLength(partialUploads); - boolean completed = checkAllCompleted(expirationPeriod, partialUploads); + List partialUploads = getPartialUploads(uploadInfo); - if (totalLength != null && totalLength > 0) { - uploadInfo.setLength(totalLength); + Long totalLength = calculateTotalLength(partialUploads); + boolean completed = checkAllCompleted(expirationPeriod, partialUploads); - if (completed) { - uploadInfo.setOffset(totalLength); - } + if (totalLength != null && totalLength > 0) { + uploadInfo.setLength(totalLength); - if (expirationPeriod != null) { - uploadInfo.updateExpiration(expirationPeriod); - } - - updateUpload(uploadInfo); - } + if (completed) { + uploadInfo.setOffset(totalLength); } - } - @Override - public InputStream getConcatenatedBytes(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { - merge(uploadInfo); - - if (uploadInfo == null || uploadInfo.isUploadInProgress()) { - return null; - } else { - List uploads = getPartialUploads(uploadInfo); - return new SequenceInputStream(new UploadInputStreamEnumeration(uploads, uploadStorageService)); + if (expirationPeriod != null) { + uploadInfo.updateExpiration(expirationPeriod); } - } - @Override - public List getPartialUploads(UploadInfo info) throws IOException, UploadNotFoundException { - List concatenationParts = info.getConcatenationPartIds(); - - if (concatenationParts == null || concatenationParts.isEmpty()) { - return Collections.emptyList(); + updateUpload(uploadInfo); + } + } + } + + @Override + public InputStream getConcatenatedBytes(UploadInfo uploadInfo) + throws IOException, UploadNotFoundException { + merge(uploadInfo); + + if (uploadInfo == null || uploadInfo.isUploadInProgress()) { + return null; + } else { + List uploads = getPartialUploads(uploadInfo); + return new SequenceInputStream( + new UploadInputStreamEnumeration(uploads, uploadStorageService)); + } + } + + @Override + public List getPartialUploads(UploadInfo info) + throws IOException, UploadNotFoundException { + List concatenationParts = info.getConcatenationPartIds(); + + if (concatenationParts == null || concatenationParts.isEmpty()) { + return Collections.emptyList(); + } else { + List output = new ArrayList<>(concatenationParts.size()); + for (String childUri : concatenationParts) { + UploadInfo childInfo = uploadStorageService.getUploadInfo(childUri, info.getOwnerKey()); + if (childInfo == null) { + throw new UploadNotFoundException( + "Upload with URI " + childUri + " was not found for owner " + info.getOwnerKey()); } else { - List output = new ArrayList<>(concatenationParts.size()); - for (String childUri : concatenationParts) { - UploadInfo childInfo = uploadStorageService.getUploadInfo(childUri, info.getOwnerKey()); - if (childInfo == null) { - throw new UploadNotFoundException("Upload with URI " + childUri - + " was not found for owner " + info.getOwnerKey()); - } else { - output.add(childInfo); - } - } - return output; + output.add(childInfo); } + } + return output; } - - private Long calculateTotalLength(List partialUploads) { - Long totalLength = 0L; - - for (UploadInfo childInfo : partialUploads) { - if (childInfo.getLength() == null) { - //One of our partial uploads does not have a length, we can't calculate the total length yet - totalLength = null; - } else if (totalLength != null) { - totalLength += childInfo.getLength(); - } - } - - return totalLength; + } + + private Long calculateTotalLength(List partialUploads) { + Long totalLength = 0L; + + for (UploadInfo childInfo : partialUploads) { + if (childInfo.getLength() == null) { + // One of our partial uploads does not have a length, we can't calculate the total + // length + // yet + totalLength = null; + } else if (totalLength != null) { + totalLength += childInfo.getLength(); + } } - private boolean checkAllCompleted(Long expirationPeriod, List partialUploads) - throws IOException { + return totalLength; + } - boolean completed = true; + private boolean checkAllCompleted(Long expirationPeriod, List partialUploads) + throws IOException { - for (UploadInfo childInfo : partialUploads) { - if (childInfo.isUploadInProgress()) { - completed = false; + boolean completed = true; - } else if (expirationPeriod != null) { - //Make sure our child uploads do not expire - //since the partial child upload is complete, it's safe to update it. - childInfo.updateExpiration(expirationPeriod); - updateUpload(childInfo); - } - } + for (UploadInfo childInfo : partialUploads) { + if (childInfo.isUploadInProgress()) { + completed = false; - return completed; + } else if (expirationPeriod != null) { + // Make sure our child uploads do not expire + // since the partial child upload is complete, it's safe to update it. + childInfo.updateExpiration(expirationPeriod); + updateUpload(childInfo); + } } - private void updateUpload(UploadInfo uploadInfo) throws IOException { - try { - uploadStorageService.update(uploadInfo); - } catch (UploadNotFoundException e) { - log.warn("Unexpected exception occurred while saving upload info with ID " + uploadInfo.getId(), e); - } - } + return completed; + } + private void updateUpload(UploadInfo uploadInfo) throws IOException { + try { + uploadStorageService.update(uploadInfo); + } catch (UploadNotFoundException e) { + log.warn( + "Unexpected exception occurred while saving upload info with ID " + uploadInfo.getId(), + e); + } + } } diff --git a/src/main/java/me/desair/tus/server/upload/disk/AbstractDiskBasedService.java b/src/main/java/me/desair/tus/server/upload/disk/AbstractDiskBasedService.java index a9cd430..083484c 100644 --- a/src/main/java/me/desair/tus/server/upload/disk/AbstractDiskBasedService.java +++ b/src/main/java/me/desair/tus/server/upload/disk/AbstractDiskBasedService.java @@ -4,52 +4,50 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; - import me.desair.tus.server.TusFileUploadService; import me.desair.tus.server.upload.UploadId; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Common abstract super class to implement service that use the disk file system - */ +/** Common abstract super class to implement service that use the disk file system */ public class AbstractDiskBasedService { - private static final Logger log = LoggerFactory.getLogger(TusFileUploadService.class); + private static final Logger log = LoggerFactory.getLogger(TusFileUploadService.class); - private Path storagePath; + private Path storagePath; - public AbstractDiskBasedService(String path) { - Validate.notBlank(path, "The storage path cannot be blank"); - this.storagePath = Paths.get(path); - } + public AbstractDiskBasedService(String path) { + Validate.notBlank(path, "The storage path cannot be blank"); + this.storagePath = Paths.get(path); + } - protected Path getStoragePath() { - return storagePath; - } + protected Path getStoragePath() { + return storagePath; + } - protected Path getPathInStorageDirectory(UploadId id) { - if (!Files.exists(storagePath)) { - init(); - } - - if (id == null) { - return null; - } else { - return storagePath.resolve(id.toString()); - } + protected Path getPathInStorageDirectory(UploadId id) { + if (!Files.exists(storagePath)) { + init(); } - private synchronized void init() { - if (!Files.exists(storagePath)) { - try { - Files.createDirectories(storagePath); - } catch (IOException e) { - String message = "Unable to create the directory specified by the storage path " + storagePath; - log.error(message, e); - throw new StoragePathNotAvailableException(message, e); - } - } + if (id == null) { + return null; + } else { + return storagePath.resolve(id.toString()); + } + } + + private synchronized void init() { + if (!Files.exists(storagePath)) { + try { + Files.createDirectories(storagePath); + } catch (IOException e) { + String message = + "Unable to create the directory specified by the storage path " + storagePath; + log.error(message, e); + throw new StoragePathNotAvailableException(message, e); + } } + } } diff --git a/src/main/java/me/desair/tus/server/upload/disk/DiskLockingService.java b/src/main/java/me/desair/tus/server/upload/disk/DiskLockingService.java index 71eafb9..8e8753d 100644 --- a/src/main/java/me/desair/tus/server/upload/disk/DiskLockingService.java +++ b/src/main/java/me/desair/tus/server/upload/disk/DiskLockingService.java @@ -6,7 +6,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.exception.UploadAlreadyLockedException; import me.desair.tus.server.upload.UploadId; @@ -17,91 +16,89 @@ /** * {@link UploadLockingService} implementation that uses the file system for implementing locking - *

- * File locking can also apply to shared network drives. This way the framework supports clustering as long as - * the upload storage directory is mounted as a shared (network) drive. - *

- * File locks are also automatically released on application (JVM) shutdown. This means the file locking is not - * persistent and prevents cleanup and stale lock issues. + * + *

File locking can also apply to shared network drives. This way the framework supports + * clustering as long as the upload storage directory is mounted as a shared (network) drive. + * + *

File locks are also automatically released on application (JVM) shutdown. This means the file + * locking is not persistent and prevents cleanup and stale lock issues. */ public class DiskLockingService extends AbstractDiskBasedService implements UploadLockingService { - private static final String LOCK_SUB_DIRECTORY = "locks"; + private static final String LOCK_SUB_DIRECTORY = "locks"; - private UploadIdFactory idFactory; + private UploadIdFactory idFactory; - public DiskLockingService(String storagePath) { - super(storagePath + File.separator + LOCK_SUB_DIRECTORY); - } + public DiskLockingService(String storagePath) { + super(storagePath + File.separator + LOCK_SUB_DIRECTORY); + } - public DiskLockingService(UploadIdFactory idFactory, String storagePath) { - this(storagePath); - Validate.notNull(idFactory, "The IdFactory cannot be null"); - this.idFactory = idFactory; - } + public DiskLockingService(UploadIdFactory idFactory, String storagePath) { + this(storagePath); + Validate.notNull(idFactory, "The IdFactory cannot be null"); + this.idFactory = idFactory; + } - @Override - public UploadLock lockUploadByUri(String requestURI) throws TusException, IOException { + @Override + public UploadLock lockUploadByUri(String requestURI) throws TusException, IOException { - UploadId id = idFactory.readUploadId(requestURI); + UploadId id = idFactory.readUploadId(requestURI); - UploadLock lock = null; + UploadLock lock = null; - Path lockPath = getLockPath(id); - //If lockPath is not null, we know this is a valid Upload URI - if (lockPath != null) { - lock = new FileBasedLock(requestURI, lockPath); - } - return lock; + Path lockPath = getLockPath(id); + // If lockPath is not null, we know this is a valid Upload URI + if (lockPath != null) { + lock = new FileBasedLock(requestURI, lockPath); } + return lock; + } - @Override - public void cleanupStaleLocks() throws IOException { - try (DirectoryStream locksStream = Files.newDirectoryStream(getStoragePath())) { - for (Path path : locksStream) { - - FileTime lastModifiedTime = Files.getLastModifiedTime(path); - if (lastModifiedTime.toMillis() < System.currentTimeMillis() - 10000L) { - UploadId id = new UploadId(path.getFileName().toString()); + @Override + public void cleanupStaleLocks() throws IOException { + try (DirectoryStream locksStream = Files.newDirectoryStream(getStoragePath())) { + for (Path path : locksStream) { - if (!isLocked(id)) { - Files.deleteIfExists(path); - } - } + FileTime lastModifiedTime = Files.getLastModifiedTime(path); + if (lastModifiedTime.toMillis() < System.currentTimeMillis() - 10000L) { + UploadId id = new UploadId(path.getFileName().toString()); - } + if (!isLocked(id)) { + Files.deleteIfExists(path); + } } + } } + } - @Override - public boolean isLocked(UploadId id) { - boolean locked = false; - Path lockPath = getLockPath(id); + @Override + public boolean isLocked(UploadId id) { + boolean locked = false; + Path lockPath = getLockPath(id); - if (lockPath != null) { - //Try to obtain a lock to see if the upload is currently locked - try (UploadLock lock = new FileBasedLock(id.toString(), lockPath)) { + if (lockPath != null) { + // Try to obtain a lock to see if the upload is currently locked + try (UploadLock lock = new FileBasedLock(id.toString(), lockPath)) { - //We got the lock, so it means no one else is locking it. - locked = false; + // We got the lock, so it means no one else is locking it. + locked = false; - } catch (UploadAlreadyLockedException | IOException e) { - //There was already a lock - locked = true; - } - } - - return locked; + } catch (UploadAlreadyLockedException | IOException e) { + // There was already a lock + locked = true; + } } - @Override - public void setIdFactory(UploadIdFactory idFactory) { - Validate.notNull(idFactory, "The IdFactory cannot be null"); - this.idFactory = idFactory; - } + return locked; + } - private Path getLockPath(UploadId id) { - return getPathInStorageDirectory(id); - } + @Override + public void setIdFactory(UploadIdFactory idFactory) { + Validate.notNull(idFactory, "The IdFactory cannot be null"); + this.idFactory = idFactory; + } + private Path getLockPath(UploadId id) { + return getPathInStorageDirectory(id); + } } diff --git a/src/main/java/me/desair/tus/server/upload/disk/DiskStorageService.java b/src/main/java/me/desair/tus/server/upload/disk/DiskStorageService.java index 34f5627..fb84af0 100644 --- a/src/main/java/me/desair/tus/server/upload/disk/DiskStorageService.java +++ b/src/main/java/me/desair/tus/server/upload/disk/DiskStorageService.java @@ -17,7 +17,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; - import me.desair.tus.server.exception.InvalidUploadOffsetException; import me.desair.tus.server.exception.TusException; import me.desair.tus.server.exception.UploadNotFoundException; @@ -35,318 +34,324 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Implementation of {@link UploadStorageService} that implements storage on disk - */ +/** Implementation of {@link UploadStorageService} that implements storage on disk */ public class DiskStorageService extends AbstractDiskBasedService implements UploadStorageService { - private static final Logger log = LoggerFactory.getLogger(DiskStorageService.class); - - private static final String UPLOAD_SUB_DIRECTORY = "uploads"; - private static final String INFO_FILE = "info"; - private static final String DATA_FILE = "data"; - - private Long maxUploadSize = null; - private Long uploadExpirationPeriod = null; - private UploadIdFactory idFactory; - private UploadConcatenationService uploadConcatenationService; - - public DiskStorageService(String storagePath) { - super(storagePath + File.separator + UPLOAD_SUB_DIRECTORY); - setUploadConcatenationService(new VirtualConcatenationService(this)); + private static final Logger log = LoggerFactory.getLogger(DiskStorageService.class); + + private static final String UPLOAD_SUB_DIRECTORY = "uploads"; + private static final String INFO_FILE = "info"; + private static final String DATA_FILE = "data"; + + private Long maxUploadSize = null; + private Long uploadExpirationPeriod = null; + private UploadIdFactory idFactory; + private UploadConcatenationService uploadConcatenationService; + + public DiskStorageService(String storagePath) { + super(storagePath + File.separator + UPLOAD_SUB_DIRECTORY); + setUploadConcatenationService(new VirtualConcatenationService(this)); + } + + public DiskStorageService(UploadIdFactory idFactory, String storagePath) { + this(storagePath); + Validate.notNull(idFactory, "The IdFactory cannot be null"); + this.idFactory = idFactory; + } + + @Override + public void setIdFactory(UploadIdFactory idFactory) { + Validate.notNull(idFactory, "The IdFactory cannot be null"); + this.idFactory = idFactory; + } + + @Override + public void setMaxUploadSize(Long maxUploadSize) { + this.maxUploadSize = (maxUploadSize != null && maxUploadSize > 0 ? maxUploadSize : 0); + } + + @Override + public long getMaxUploadSize() { + return maxUploadSize == null ? 0 : maxUploadSize; + } + + @Override + public UploadInfo getUploadInfo(String uploadUrl, String ownerKey) throws IOException { + UploadInfo uploadInfo = getUploadInfo(idFactory.readUploadId(uploadUrl)); + if (uploadInfo == null || !Objects.equals(uploadInfo.getOwnerKey(), ownerKey)) { + return null; + } else { + return uploadInfo; } - - public DiskStorageService(UploadIdFactory idFactory, String storagePath) { - this(storagePath); - Validate.notNull(idFactory, "The IdFactory cannot be null"); - this.idFactory = idFactory; + } + + @Override + public UploadInfo getUploadInfo(UploadId id) throws IOException { + try { + Path infoPath = getInfoPath(id); + return Utils.readSerializable(infoPath, UploadInfo.class); + } catch (UploadNotFoundException e) { + return null; } + } - @Override - public void setIdFactory(UploadIdFactory idFactory) { - Validate.notNull(idFactory, "The IdFactory cannot be null"); - this.idFactory = idFactory; - } + @Override + public String getUploadURI() { + return idFactory.getUploadURI(); + } - @Override - public void setMaxUploadSize(Long maxUploadSize) { - this.maxUploadSize = (maxUploadSize != null && maxUploadSize > 0 ? maxUploadSize : 0); - } + @Override + public UploadInfo create(UploadInfo info, String ownerKey) throws IOException { + UploadId id = createNewId(); - @Override - public long getMaxUploadSize() { - return maxUploadSize == null ? 0 : maxUploadSize; - } + createUploadDirectory(id); - @Override - public UploadInfo getUploadInfo(String uploadUrl, String ownerKey) throws IOException { - UploadInfo uploadInfo = getUploadInfo(idFactory.readUploadId(uploadUrl)); - if (uploadInfo == null || !Objects.equals(uploadInfo.getOwnerKey(), ownerKey)) { - return null; - } else { - return uploadInfo; - } - } + try { + Path bytesPath = getBytesPath(id); - @Override - public UploadInfo getUploadInfo(UploadId id) throws IOException { - try { - Path infoPath = getInfoPath(id); - return Utils.readSerializable(infoPath, UploadInfo.class); - } catch (UploadNotFoundException e) { - return null; - } - } + // Create an empty file to storage the bytes of this upload + Files.createFile(bytesPath); - @Override - public String getUploadURI() { - return idFactory.getUploadURI(); - } + // Set starting values + info.setId(id); + info.setOffset(0L); + info.setOwnerKey(ownerKey); - @Override - public UploadInfo create(UploadInfo info, String ownerKey) throws IOException { - UploadId id = createNewId(); + update(info); - createUploadDirectory(id); + return info; + } catch (UploadNotFoundException e) { + // Normally this cannot happen + log.error("Unable to create UploadInfo because of an upload not found exception", e); + return null; + } + } - try { - Path bytesPath = getBytesPath(id); + @Override + public void update(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { + Path infoPath = getInfoPath(uploadInfo.getId()); + Utils.writeSerializable(uploadInfo, infoPath); + } - //Create an empty file to storage the bytes of this upload - Files.createFile(bytesPath); + @Override + public UploadInfo append(UploadInfo info, InputStream inputStream) + throws IOException, TusException { + if (info != null) { + Path bytesPath = getBytesPath(info.getId()); - //Set starting values - info.setId(id); - info.setOffset(0L); - info.setOwnerKey(ownerKey); + long max = getMaxUploadSize() > 0 ? getMaxUploadSize() : Long.MAX_VALUE; + long transferred = 0; + Long offset = info.getOffset(); + long newOffset = offset; - update(info); + try (ReadableByteChannel uploadedBytes = Channels.newChannel(inputStream); + FileChannel file = FileChannel.open(bytesPath, WRITE)) { - return info; - } catch (UploadNotFoundException e) { - //Normally this cannot happen - log.error("Unable to create UploadInfo because of an upload not found exception", e); - return null; + try { + // Lock will be released when the channel closes + file.lock(); + + // Validate that the given offset is at the end of the file + if (!offset.equals(file.size())) { + throw new InvalidUploadOffsetException( + "The upload offset does not correspond to the written" + + " bytes. You can only append to the end of an upload"); + } + + // write all bytes in the channel up to the configured maximum + transferred = file.transferFrom(uploadedBytes, offset, max - offset); + file.force(true); + newOffset = offset + transferred; + + } catch (Exception ex) { + // An error occurred, try to write as much data as possible + newOffset = writeAsMuchAsPossible(file); + throw ex; } - } - @Override - public void update(UploadInfo uploadInfo) throws IOException, UploadNotFoundException { - Path infoPath = getInfoPath(uploadInfo.getId()); - Utils.writeSerializable(uploadInfo, infoPath); + } finally { + info.setOffset(newOffset); + update(info); + } } - @Override - public UploadInfo append(UploadInfo info, InputStream inputStream) throws IOException, TusException { - if (info != null) { - Path bytesPath = getBytesPath(info.getId()); - - long max = getMaxUploadSize() > 0 ? getMaxUploadSize() : Long.MAX_VALUE; - long transferred = 0; - Long offset = info.getOffset(); - long newOffset = offset; - - try (ReadableByteChannel uploadedBytes = Channels.newChannel(inputStream); - FileChannel file = FileChannel.open(bytesPath, WRITE)) { - - try { - //Lock will be released when the channel closes - file.lock(); - - //Validate that the given offset is at the end of the file - if (!offset.equals(file.size())) { - throw new InvalidUploadOffsetException("The upload offset does not correspond to the written" - + " bytes. You can only append to the end of an upload"); - } - - //write all bytes in the channel up to the configured maximum - transferred = file.transferFrom(uploadedBytes, offset, max - offset); - file.force(true); - newOffset = offset + transferred; - - } catch (Exception ex) { - //An error occurred, try to write as much data as possible - newOffset = writeAsMuchAsPossible(file); - throw ex; - } - - } finally { - info.setOffset(newOffset); - update(info); - } - } - - return info; - } + return info; + } - @Override - public void removeLastNumberOfBytes(UploadInfo info, long byteCount) - throws UploadNotFoundException, IOException { + @Override + public void removeLastNumberOfBytes(UploadInfo info, long byteCount) + throws UploadNotFoundException, IOException { - if (info != null && byteCount > 0) { - Path bytesPath = getBytesPath(info.getId()); + if (info != null && byteCount > 0) { + Path bytesPath = getBytesPath(info.getId()); - try (FileChannel file = FileChannel.open(bytesPath, WRITE)) { + try (FileChannel file = FileChannel.open(bytesPath, WRITE)) { - //Lock will be released when the channel closes - file.lock(); + // Lock will be released when the channel closes + file.lock(); - file.truncate(file.size() - byteCount); - file.force(true); + file.truncate(file.size() - byteCount); + file.force(true); - info.setOffset(file.size()); - update(info); - } - } + info.setOffset(file.size()); + update(info); + } } + } - @Override - public void terminateUpload(UploadInfo info) throws UploadNotFoundException, IOException { - if (info != null) { - Path uploadPath = getPathInStorageDirectory(info.getId()); - FileUtils.deleteDirectory(uploadPath.toFile()); - } + @Override + public void terminateUpload(UploadInfo info) throws UploadNotFoundException, IOException { + if (info != null) { + Path uploadPath = getPathInStorageDirectory(info.getId()); + FileUtils.deleteDirectory(uploadPath.toFile()); } - - @Override - public Long getUploadExpirationPeriod() { - return uploadExpirationPeriod; + } + + @Override + public Long getUploadExpirationPeriod() { + return uploadExpirationPeriod; + } + + @Override + public void setUploadExpirationPeriod(Long uploadExpirationPeriod) { + this.uploadExpirationPeriod = uploadExpirationPeriod; + } + + @Override + public void setUploadConcatenationService(UploadConcatenationService concatenationService) { + Validate.notNull(concatenationService); + this.uploadConcatenationService = concatenationService; + } + + @Override + public UploadConcatenationService getUploadConcatenationService() { + return uploadConcatenationService; + } + + @Override + public InputStream getUploadedBytes(String uploadURI, String ownerKey) + throws IOException, UploadNotFoundException { + + UploadId id = idFactory.readUploadId(uploadURI); + + UploadInfo uploadInfo = getUploadInfo(id); + if (uploadInfo == null || !Objects.equals(uploadInfo.getOwnerKey(), ownerKey)) { + throw new UploadNotFoundException( + "The upload with id " + id + " could not be found for owner " + ownerKey); + } else { + return getUploadedBytes(id); } - - @Override - public void setUploadExpirationPeriod(Long uploadExpirationPeriod) { - this.uploadExpirationPeriod = uploadExpirationPeriod; + } + + @Override + public InputStream getUploadedBytes(UploadId id) throws IOException, UploadNotFoundException { + InputStream inputStream = null; + UploadInfo uploadInfo = getUploadInfo(id); + if (UploadType.CONCATENATED.equals(uploadInfo.getUploadType()) + && uploadConcatenationService != null) { + inputStream = uploadConcatenationService.getConcatenatedBytes(uploadInfo); + + } else { + Path bytesPath = getBytesPath(id); + // If bytesPath is not null, we know this is a valid Upload URI + if (bytesPath != null) { + inputStream = Channels.newInputStream(FileChannel.open(bytesPath, READ)); + } } - @Override - public void setUploadConcatenationService(UploadConcatenationService concatenationService) { - Validate.notNull(concatenationService); - this.uploadConcatenationService = concatenationService; - } + return inputStream; + } - @Override - public UploadConcatenationService getUploadConcatenationService() { - return uploadConcatenationService; - } + @Override + public void copyUploadTo(UploadInfo info, OutputStream outputStream) + throws UploadNotFoundException, IOException { - @Override - public InputStream getUploadedBytes(String uploadURI, String ownerKey) - throws IOException, UploadNotFoundException { + List uploads = getUploads(info); - UploadId id = idFactory.readUploadId(uploadURI); + WritableByteChannel outputChannel = Channels.newChannel(outputStream); - UploadInfo uploadInfo = getUploadInfo(id); - if (uploadInfo == null || !Objects.equals(uploadInfo.getOwnerKey(), ownerKey)) { - throw new UploadNotFoundException("The upload with id " + id + " could not be found for owner " + ownerKey); - } else { - return getUploadedBytes(id); - } - } - - @Override - public InputStream getUploadedBytes(UploadId id) throws IOException, UploadNotFoundException { - InputStream inputStream = null; - UploadInfo uploadInfo = getUploadInfo(id); - if (UploadType.CONCATENATED.equals(uploadInfo.getUploadType()) && uploadConcatenationService != null) { - inputStream = uploadConcatenationService.getConcatenatedBytes(uploadInfo); - - } else { - Path bytesPath = getBytesPath(id); - //If bytesPath is not null, we know this is a valid Upload URI - if (bytesPath != null) { - inputStream = Channels.newInputStream(FileChannel.open(bytesPath, READ)); - } - } - - return inputStream; - } - - @Override - public void copyUploadTo(UploadInfo info, OutputStream outputStream) - throws UploadNotFoundException, IOException { - - List uploads = getUploads(info); - - WritableByteChannel outputChannel = Channels.newChannel(outputStream); - - for (UploadInfo upload : uploads) { - if (upload == null) { - log.warn("We cannot copy the bytes of an upload that does not exist"); - - } else if (upload.isUploadInProgress()) { - log.warn("We cannot copy the bytes of upload {} because it is still in progress", upload.getId()); - - } else { - Path bytesPath = getBytesPath(upload.getId()); - try (FileChannel file = FileChannel.open(bytesPath, READ)) { - //Efficiently copy the bytes to the output stream - file.transferTo(0, upload.getLength(), outputChannel); - } - } - } - } + for (UploadInfo upload : uploads) { + if (upload == null) { + log.warn("We cannot copy the bytes of an upload that does not exist"); - @Override - public void cleanupExpiredUploads(UploadLockingService uploadLockingService) throws IOException { - try (DirectoryStream expiredUploadsStream = Files.newDirectoryStream(getStoragePath(), - new ExpiredUploadFilter(this, uploadLockingService))) { + } else if (upload.isUploadInProgress()) { + log.warn( + "We cannot copy the bytes of upload {} because it is still in progress", + upload.getId()); - for (Path path : expiredUploadsStream) { - FileUtils.deleteDirectory(path.toFile()); - } + } else { + Path bytesPath = getBytesPath(upload.getId()); + try (FileChannel file = FileChannel.open(bytesPath, READ)) { + // Efficiently copy the bytes to the output stream + file.transferTo(0, upload.getLength(), outputChannel); } + } } + } - private List getUploads(UploadInfo info) throws IOException, UploadNotFoundException { - List uploads; + @Override + public void cleanupExpiredUploads(UploadLockingService uploadLockingService) throws IOException { + try (DirectoryStream expiredUploadsStream = + Files.newDirectoryStream( + getStoragePath(), new ExpiredUploadFilter(this, uploadLockingService))) { - if (info != null && UploadType.CONCATENATED.equals(info.getUploadType()) - && uploadConcatenationService != null) { - uploadConcatenationService.merge(info); - uploads = uploadConcatenationService.getPartialUploads(info); - } else { - uploads = Collections.singletonList(info); - } - return uploads; + for (Path path : expiredUploadsStream) { + FileUtils.deleteDirectory(path.toFile()); + } } - - private Path getBytesPath(UploadId id) throws UploadNotFoundException { - return getPathInUploadDir(id, DATA_FILE); + } + + private List getUploads(UploadInfo info) throws IOException, UploadNotFoundException { + List uploads; + + if (info != null + && UploadType.CONCATENATED.equals(info.getUploadType()) + && uploadConcatenationService != null) { + uploadConcatenationService.merge(info); + uploads = uploadConcatenationService.getPartialUploads(info); + } else { + uploads = Collections.singletonList(info); } - - private Path getInfoPath(UploadId id) throws UploadNotFoundException { - return getPathInUploadDir(id, INFO_FILE); + return uploads; + } + + private Path getBytesPath(UploadId id) throws UploadNotFoundException { + return getPathInUploadDir(id, DATA_FILE); + } + + private Path getInfoPath(UploadId id) throws UploadNotFoundException { + return getPathInUploadDir(id, INFO_FILE); + } + + private Path createUploadDirectory(UploadId id) throws IOException { + return Files.createDirectories(getPathInStorageDirectory(id)); + } + + private Path getPathInUploadDir(UploadId id, String fileName) throws UploadNotFoundException { + // Get the upload directory + Path uploadDir = getPathInStorageDirectory(id); + if (uploadDir != null && Files.exists(uploadDir)) { + return uploadDir.resolve(fileName); + } else { + throw new UploadNotFoundException("The upload for id " + id + " was not found."); } - - private Path createUploadDirectory(UploadId id) throws IOException { - return Files.createDirectories(getPathInStorageDirectory(id)); - } - - private Path getPathInUploadDir(UploadId id, String fileName) throws UploadNotFoundException { - //Get the upload directory - Path uploadDir = getPathInStorageDirectory(id); - if (uploadDir != null && Files.exists(uploadDir)) { - return uploadDir.resolve(fileName); - } else { - throw new UploadNotFoundException("The upload for id " + id + " was not found."); - } - } - - private synchronized UploadId createNewId() throws IOException { - UploadId id; - do { - id = idFactory.createId(); - //For extra safety, double check that this ID is not in use yet - } while (getUploadInfo(id) != null); - return id; - } - - private long writeAsMuchAsPossible(FileChannel file) throws IOException { - long offset = 0; - if (file != null) { - file.force(true); - offset = file.size(); - } - return offset; + } + + private synchronized UploadId createNewId() throws IOException { + UploadId id; + do { + id = idFactory.createId(); + // For extra safety, double check that this ID is not in use yet + } while (getUploadInfo(id) != null); + return id; + } + + private long writeAsMuchAsPossible(FileChannel file) throws IOException { + long offset = 0; + if (file != null) { + file.force(true); + offset = file.size(); } + return offset; + } } diff --git a/src/main/java/me/desair/tus/server/upload/disk/ExpiredUploadFilter.java b/src/main/java/me/desair/tus/server/upload/disk/ExpiredUploadFilter.java index f7c7536..922e4d9 100644 --- a/src/main/java/me/desair/tus/server/upload/disk/ExpiredUploadFilter.java +++ b/src/main/java/me/desair/tus/server/upload/disk/ExpiredUploadFilter.java @@ -4,45 +4,43 @@ import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.util.Objects; - import me.desair.tus.server.upload.UploadId; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.upload.UploadLockingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Directory stream filter that only accepts uploads that are still in progress and expired - */ +/** Directory stream filter that only accepts uploads that are still in progress and expired */ public class ExpiredUploadFilter implements DirectoryStream.Filter { - private static final Logger log = LoggerFactory.getLogger(ExpiredUploadFilter.class); - - private DiskStorageService diskStorageService; - private UploadLockingService uploadLockingService; + private static final Logger log = LoggerFactory.getLogger(ExpiredUploadFilter.class); - ExpiredUploadFilter(DiskStorageService diskStorageService, UploadLockingService uploadLockingService) { - this.diskStorageService = diskStorageService; - this.uploadLockingService = uploadLockingService; - } + private DiskStorageService diskStorageService; + private UploadLockingService uploadLockingService; - @Override - public boolean accept(Path upload) throws IOException { - UploadId id = null; - try { - id = new UploadId(upload.getFileName().toString()); - UploadInfo info = diskStorageService.getUploadInfo(id); + ExpiredUploadFilter( + DiskStorageService diskStorageService, UploadLockingService uploadLockingService) { + this.diskStorageService = diskStorageService; + this.uploadLockingService = uploadLockingService; + } - if (info != null && info.isExpired() && !uploadLockingService.isLocked(id)) { - return true; - } + @Override + public boolean accept(Path upload) throws IOException { + UploadId id = null; + try { + id = new UploadId(upload.getFileName().toString()); + UploadInfo info = diskStorageService.getUploadInfo(id); - } catch (Exception ex) { - if (log.isDebugEnabled()) { - log.debug("Not able to determine state of upload " + Objects.toString(id), ex); - } - } + if (info != null && info.isExpired() && !uploadLockingService.isLocked(id)) { + return true; + } - return false; + } catch (Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Not able to determine state of upload " + Objects.toString(id), ex); + } } + + return false; + } } diff --git a/src/main/java/me/desair/tus/server/upload/disk/FileBasedLock.java b/src/main/java/me/desair/tus/server/upload/disk/FileBasedLock.java index 371b875..e741b4b 100644 --- a/src/main/java/me/desair/tus/server/upload/disk/FileBasedLock.java +++ b/src/main/java/me/desair/tus/server/upload/disk/FileBasedLock.java @@ -9,7 +9,6 @@ import java.nio.channels.OverlappingFileLockException; import java.nio.file.Files; import java.nio.file.Path; - import me.desair.tus.server.exception.UploadAlreadyLockedException; import me.desair.tus.server.upload.UploadLock; import me.desair.tus.server.util.Utils; @@ -18,81 +17,82 @@ import org.slf4j.LoggerFactory; /** - * Upload locking implementation using the file system file locking mechanism. - * File locking can also apply to shared network drives. This way the framework supports clustering as long as - * the upload storage directory is mounted as a shared (network) drive. - *

- * File locks are also automatically released on application (JVM) shutdown. This means the file locking is not - * persistent and prevents cleanup and stale lock issues. + * Upload locking implementation using the file system file locking mechanism. File locking can also + * apply to shared network drives. This way the framework supports clustering as long as the upload + * storage directory is mounted as a shared (network) drive. + * + *

File locks are also automatically released on application (JVM) shutdown. This means the file + * locking is not persistent and prevents cleanup and stale lock issues. */ public class FileBasedLock implements UploadLock { - private static final Logger log = LoggerFactory.getLogger(FileBasedLock.class); - - private String uploadUri; - private FileChannel fileChannel = null; - protected Path lockPath; + private static final Logger log = LoggerFactory.getLogger(FileBasedLock.class); - public FileBasedLock(String uploadUri, Path lockPath) throws UploadAlreadyLockedException, IOException { - Validate.notBlank(uploadUri, "The upload URI cannot be blank"); - Validate.notNull(lockPath, "The path to the lock cannot be null"); - this.uploadUri = uploadUri; - this.lockPath = lockPath; + private String uploadUri; + private FileChannel fileChannel = null; + protected Path lockPath; - tryToObtainFileLock(); - } + public FileBasedLock(String uploadUri, Path lockPath) + throws UploadAlreadyLockedException, IOException { + Validate.notBlank(uploadUri, "The upload URI cannot be blank"); + Validate.notNull(lockPath, "The path to the lock cannot be null"); + this.uploadUri = uploadUri; + this.lockPath = lockPath; - private void tryToObtainFileLock() throws UploadAlreadyLockedException, IOException { - String message = "The upload " + getUploadUri() + " is already locked"; - - try { - //Try to acquire a lock - fileChannel = createFileChannel(); - FileLock fileLock = Utils.lockFileExclusively(fileChannel); + tryToObtainFileLock(); + } - //If the upload is already locked, our lock will be null - if (fileLock == null) { - fileChannel.close(); - throw new UploadAlreadyLockedException(message); - } + private void tryToObtainFileLock() throws UploadAlreadyLockedException, IOException { + String message = "The upload " + getUploadUri() + " is already locked"; - } catch (OverlappingFileLockException e) { - if (fileChannel != null) { - try { - fileChannel.close(); - } catch (IOException e1) { - //Should not happen - } - } - throw new UploadAlreadyLockedException(message); - } catch (IOException e) { - throw new IOException("Unable to create or open file required to implement file-based locking", e); - } - } + try { + // Try to acquire a lock + fileChannel = createFileChannel(); + FileLock fileLock = Utils.lockFileExclusively(fileChannel); - @Override - public String getUploadUri() { - return uploadUri; - } + // If the upload is already locked, our lock will be null + if (fileLock == null) { + fileChannel.close(); + throw new UploadAlreadyLockedException(message); + } - @Override - public void release() { + } catch (OverlappingFileLockException e) { + if (fileChannel != null) { try { - //Closing the channel will also release the lock - fileChannel.close(); - Files.deleteIfExists(lockPath); - } catch (IOException e) { - log.warn("Unable to release file lock for URI " + getUploadUri(), e); + fileChannel.close(); + } catch (IOException e1) { + // Should not happen } + } + throw new UploadAlreadyLockedException(message); + } catch (IOException e) { + throw new IOException( + "Unable to create or open file required to implement file-based locking", e); } - - @Override - public void close() throws IOException { - release(); + } + + @Override + public String getUploadUri() { + return uploadUri; + } + + @Override + public void release() { + try { + // Closing the channel will also release the lock + fileChannel.close(); + Files.deleteIfExists(lockPath); + } catch (IOException e) { + log.warn("Unable to release file lock for URI " + getUploadUri(), e); } + } - protected FileChannel createFileChannel() throws IOException { - return FileChannel.open(lockPath, CREATE, WRITE); - } + @Override + public void close() throws IOException { + release(); + } + protected FileChannel createFileChannel() throws IOException { + return FileChannel.open(lockPath, CREATE, WRITE); + } } diff --git a/src/main/java/me/desair/tus/server/upload/disk/StoragePathNotAvailableException.java b/src/main/java/me/desair/tus/server/upload/disk/StoragePathNotAvailableException.java index 8e09514..23d62ca 100644 --- a/src/main/java/me/desair/tus/server/upload/disk/StoragePathNotAvailableException.java +++ b/src/main/java/me/desair/tus/server/upload/disk/StoragePathNotAvailableException.java @@ -1,10 +1,8 @@ package me.desair.tus.server.upload.disk; -/** - * Exception thrown when the disk storage path cannot be read or created. - */ +/** Exception thrown when the disk storage path cannot be read or created. */ public class StoragePathNotAvailableException extends RuntimeException { - public StoragePathNotAvailableException(String message, Throwable e) { - super(message, e); - } + public StoragePathNotAvailableException(String message, Throwable e) { + super(message, e); + } } diff --git a/src/main/java/me/desair/tus/server/util/AbstractExtensionRequestHandler.java b/src/main/java/me/desair/tus/server/util/AbstractExtensionRequestHandler.java index a914299..f68a588 100644 --- a/src/main/java/me/desair/tus/server/util/AbstractExtensionRequestHandler.java +++ b/src/main/java/me/desair/tus/server/util/AbstractExtensionRequestHandler.java @@ -5,35 +5,37 @@ import me.desair.tus.server.upload.UploadStorageService; import org.apache.commons.lang3.StringUtils; -/** - * Abstract request handler to add tus extension values to the correct header - */ +/** Abstract request handler to add tus extension values to the correct header */ public abstract class AbstractExtensionRequestHandler extends AbstractRequestHandler { - @Override - public boolean supports(HttpMethod method) { - return HttpMethod.OPTIONS.equals(method); - } + @Override + public boolean supports(HttpMethod method) { + return HttpMethod.OPTIONS.equals(method); + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) { - StringBuilder extensionBuilder = new StringBuilder(StringUtils.trimToEmpty( - servletResponse.getHeader(HttpHeader.TUS_EXTENSION))); + StringBuilder extensionBuilder = + new StringBuilder( + StringUtils.trimToEmpty(servletResponse.getHeader(HttpHeader.TUS_EXTENSION))); - appendExtensions(extensionBuilder); + appendExtensions(extensionBuilder); - servletResponse.setHeader(HttpHeader.TUS_EXTENSION, extensionBuilder.toString()); - } + servletResponse.setHeader(HttpHeader.TUS_EXTENSION, extensionBuilder.toString()); + } - protected abstract void appendExtensions(StringBuilder extensionBuilder); + protected abstract void appendExtensions(StringBuilder extensionBuilder); - protected void addExtension(StringBuilder stringBuilder, String extension) { - if (stringBuilder.length() > 0) { - stringBuilder.append(","); - } - stringBuilder.append(extension); + protected void addExtension(StringBuilder stringBuilder, String extension) { + if (stringBuilder.length() > 0) { + stringBuilder.append(","); } + stringBuilder.append(extension); + } } diff --git a/src/main/java/me/desair/tus/server/util/AbstractRequestHandler.java b/src/main/java/me/desair/tus/server/util/AbstractRequestHandler.java index 1f00075..24c02c6 100644 --- a/src/main/java/me/desair/tus/server/util/AbstractRequestHandler.java +++ b/src/main/java/me/desair/tus/server/util/AbstractRequestHandler.java @@ -3,13 +3,13 @@ import me.desair.tus.server.RequestHandler; /** - * Abstract {@link me.desair.tus.server.RequestHandler} implementation that contains the common functionality + * Abstract {@link me.desair.tus.server.RequestHandler} implementation that contains the common + * functionality */ public abstract class AbstractRequestHandler implements RequestHandler { - @Override - public boolean isErrorHandler() { - return false; - } - + @Override + public boolean isErrorHandler() { + return false; + } } diff --git a/src/main/java/me/desair/tus/server/util/AbstractTusExtension.java b/src/main/java/me/desair/tus/server/util/AbstractTusExtension.java index 1d64f2b..f407037 100644 --- a/src/main/java/me/desair/tus/server/util/AbstractTusExtension.java +++ b/src/main/java/me/desair/tus/server/util/AbstractTusExtension.java @@ -1,10 +1,9 @@ package me.desair.tus.server.util; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.LinkedList; import java.util.List; -import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.RequestHandler; import me.desair.tus.server.RequestValidator; @@ -14,51 +13,63 @@ public abstract class AbstractTusExtension implements TusExtension { - private List requestValidators = new LinkedList<>(); - private List requestHandlers = new LinkedList<>(); + private List requestValidators = new LinkedList<>(); + private List requestHandlers = new LinkedList<>(); - public AbstractTusExtension() { - initValidators(requestValidators); - initRequestHandlers(requestHandlers); - } + public AbstractTusExtension() { + initValidators(requestValidators); + initRequestHandlers(requestHandlers); + } - protected abstract void initValidators(List requestValidators); + protected abstract void initValidators(List requestValidators); - protected abstract void initRequestHandlers(List requestHandlers); + protected abstract void initRequestHandlers(List requestHandlers); - @Override - public void validate(HttpMethod method, HttpServletRequest servletRequest, - UploadStorageService uploadStorageService, String ownerKey) - throws TusException, IOException { + @Override + public void validate( + HttpMethod method, + HttpServletRequest servletRequest, + UploadStorageService uploadStorageService, + String ownerKey) + throws TusException, IOException { - for (RequestValidator requestValidator : requestValidators) { - if (requestValidator.supports(method)) { - requestValidator.validate(method, servletRequest, uploadStorageService, ownerKey); - } - } + for (RequestValidator requestValidator : requestValidators) { + if (requestValidator.supports(method)) { + requestValidator.validate(method, servletRequest, uploadStorageService, ownerKey); + } } + } - @Override - public void process(HttpMethod method, TusServletRequest servletRequest, - TusServletResponse servletResponse, UploadStorageService uploadStorageService, - String ownerKey) throws IOException, TusException { + @Override + public void process( + HttpMethod method, + TusServletRequest servletRequest, + TusServletResponse servletResponse, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - for (RequestHandler requestHandler : requestHandlers) { - if (requestHandler.supports(method)) { - requestHandler.process(method, servletRequest, servletResponse, uploadStorageService, ownerKey); - } - } + for (RequestHandler requestHandler : requestHandlers) { + if (requestHandler.supports(method)) { + requestHandler.process( + method, servletRequest, servletResponse, uploadStorageService, ownerKey); + } } + } - @Override - public void handleError(HttpMethod method, TusServletRequest request, TusServletResponse response, - UploadStorageService uploadStorageService, String ownerKey) - throws IOException, TusException { + @Override + public void handleError( + HttpMethod method, + TusServletRequest request, + TusServletResponse response, + UploadStorageService uploadStorageService, + String ownerKey) + throws IOException, TusException { - for (RequestHandler requestHandler : requestHandlers) { - if (requestHandler.supports(method) && requestHandler.isErrorHandler()) { - requestHandler.process(method, request, response, uploadStorageService, ownerKey); - } - } + for (RequestHandler requestHandler : requestHandlers) { + if (requestHandler.supports(method) && requestHandler.isErrorHandler()) { + requestHandler.process(method, request, response, uploadStorageService, ownerKey); + } } + } } diff --git a/src/main/java/me/desair/tus/server/util/HttpChunkedEncodingInputStream.java b/src/main/java/me/desair/tus/server/util/HttpChunkedEncodingInputStream.java index 9ffb0c6..58c7784 100644 --- a/src/main/java/me/desair/tus/server/util/HttpChunkedEncodingInputStream.java +++ b/src/main/java/me/desair/tus/server/util/HttpChunkedEncodingInputStream.java @@ -8,421 +8,419 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; - import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Transparently coalesces chunks of a HTTP stream that uses Transfer-Encoding chunked. - * This {@link InputStream} wrapper also supports collecting Trailer header values that are - * sent at the end of the stream. - *

- * Based on org.apache.commons.httpclient.ChunkedInputStream + * Transparently coalesces chunks of a HTTP stream that uses Transfer-Encoding chunked. This {@link + * InputStream} wrapper also supports collecting Trailer header values that are sent at the end of + * the stream. + * + *

Based on org.apache.commons.httpclient.ChunkedInputStream */ public class HttpChunkedEncodingInputStream extends InputStream { - private static final Logger log = LoggerFactory.getLogger(HttpChunkedEncodingInputStream.class); + private static final Logger log = LoggerFactory.getLogger(HttpChunkedEncodingInputStream.class); - /** The input stream that we're wrapping */ - private InputStream in; + /** The input stream that we're wrapping */ + private InputStream in; - /** The current chunk size */ - private int chunkSize = 0; + /** The current chunk size */ + private int chunkSize = 0; - /** The current position within the current chunk */ - private int pos = 0; + /** The current position within the current chunk */ + private int pos = 0; - /** True if we'are at the beginning of stream */ - private boolean bof = true; + /** True if we'are at the beginning of stream */ + private boolean bof = true; - /** True if we've reached the end of stream */ - private boolean eof = false; + /** True if we've reached the end of stream */ + private boolean eof = false; - /** True if this stream is closed */ - private boolean closed = false; + /** True if this stream is closed */ + private boolean closed = false; - /** Map to store any trailer headers */ - private Map> trailerHeaders = null; + /** Map to store any trailer headers */ + private Map> trailerHeaders = null; - /** - * Wrap the given input stream and store any trailing headers in the provided map. - * @param in the raw input stream - * @param trailerHeaders Map to store any trailer header values. Can be null. - */ - public HttpChunkedEncodingInputStream( - InputStream in, Map> trailerHeaders) { + /** + * Wrap the given input stream and store any trailing headers in the provided map. + * + * @param in the raw input stream + * @param trailerHeaders Map to store any trailer header values. Can be null. + */ + public HttpChunkedEncodingInputStream(InputStream in, Map> trailerHeaders) { - if (in == null) { - throw new IllegalArgumentException("InputStream parameter may not be null"); - } - this.in = in; - this.trailerHeaders = trailerHeaders; + if (in == null) { + throw new IllegalArgumentException("InputStream parameter may not be null"); } - - /** - * Wrap the given input stream. Do not store any trailing headers. - * - * @param in the raw input stream - */ - public HttpChunkedEncodingInputStream(InputStream in) { - this(in, null); + this.in = in; + this.trailerHeaders = trailerHeaders; + } + + /** + * Wrap the given input stream. Do not store any trailing headers. + * + * @param in the raw input stream + */ + public HttpChunkedEncodingInputStream(InputStream in) { + this(in, null); + } + + /** + * Reads the next byte of data from the input stream. The value byte is returned as an int + * in the range 0 to 255. + * + * @return -1 of the end of the stream has been reached or the next data byte + * @throws IOException If an IO problem occurs + */ + @Override + public int read() throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); } - - /** - * Reads the next byte of data from the input stream. The value byte is - * returned as an int in the range 0 to - * 255. - * - * @return -1 of the end of the stream has been reached or the next data byte - * @throws IOException If an IO problem occurs - */ - @Override - public int read() throws IOException { - - if (closed) { - throw new IOException("Attempted read from closed stream."); - } - if (eof) { - return -1; - } - if (pos >= chunkSize) { - nextChunk(); - if (eof) { - return -1; - } - } - pos++; - return in.read(); + if (eof) { + return -1; } - - /** - * Read some bytes from the stream. - * @param b The byte array that will hold the contents from the stream. - * @param off The offset into the byte array at which bytes will start to be placed. - * @param len the maximum number of bytes that can be returned. - * @return The number of bytes returned or -1 if the end of stream has been reached. - * @see java.io.InputStream#read(byte[], int, int) - * @throws IOException if an IO problem occurs. - */ - @Override - public int read(byte[] b, int off, int len) throws IOException { - - if (closed) { - throw new IOException("Attempted read from closed stream."); - } - - if (eof) { - return -1; - } - if (pos >= chunkSize) { - nextChunk(); - if (eof) { - return -1; - } - } - int minLen = Math.min(len, chunkSize - pos); - int count = in.read(b, off, minLen); - pos += count; - return count; + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } } - - /** - * Read some bytes from the stream. - * @param b The byte array that will hold the contents from the stream. - * @return The number of bytes returned or -1 if the end of stream has been reached. - * @see java.io.InputStream#read(byte[]) - * @throws IOException if an IO problem occurs. - */ - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); + pos++; + return in.read(); + } + + /** + * Read some bytes from the stream. + * + * @param b The byte array that will hold the contents from the stream. + * @param off The offset into the byte array at which bytes will start to be placed. + * @param len the maximum number of bytes that can be returned. + * @return The number of bytes returned or -1 if the end of stream has been reached. + * @see java.io.InputStream#read(byte[], int, int) + * @throws IOException if an IO problem occurs. + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); } - /** - * Read the CRLF terminator. - * @throws IOException If an IO error occurs. - */ - private void readCRLF() throws IOException { - int cr = in.read(); - int lf = in.read(); - if ((cr != '\r') || (lf != '\n')) { - throw new IOException( - "CRLF expected at end of chunk: " + cr + "/" + lf); - } + if (eof) { + return -1; } - - /** - * Read the next chunk. - * @throws IOException If an IO error occurs. - */ - private void nextChunk() throws IOException { - if (!bof) { - readCRLF(); - } - chunkSize = getChunkSize(); - if (chunkSize < 0) { - throw new IOException("Negative chunk size"); - } - - bof = false; - pos = 0; - if (chunkSize == 0) { - eof = true; - parseTrailerHeaders(); - } + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } } - - /** - * Expects the stream to start with a chunk size in hex with optional - * comments after a semicolon. The line must end with a CRLF: "a3; some - * comment\r\n" Positions the stream at the start of the next line. - * - * @return the chunk size as integer - * - * @throws IOException when the chunk size could not be parsed - */ - - private int getChunkSize() throws IOException { - - String dataString = readChunkSizeInformation(); - - int separator = dataString.indexOf(';'); - dataString = (separator > 0) - ? dataString.substring(0, separator).trim() - : dataString.trim(); - - int result; - try { - result = Integer.parseInt(dataString.trim(), 16); - } catch (NumberFormatException e) { - throw new IOException("Bad chunk size: " + dataString); - } - return result; + int minLen = Math.min(len, chunkSize - pos); + int count = in.read(b, off, minLen); + pos += count; + return count; + } + + /** + * Read some bytes from the stream. + * + * @param b The byte array that will hold the contents from the stream. + * @return The number of bytes returned or -1 if the end of stream has been reached. + * @see java.io.InputStream#read(byte[]) + * @throws IOException if an IO problem occurs. + */ + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the CRLF terminator. + * + * @throws IOException If an IO error occurs. + */ + private void readCRLF() throws IOException { + int cr = in.read(); + int lf = in.read(); + if ((cr != '\r') || (lf != '\n')) { + throw new IOException("CRLF expected at end of chunk: " + cr + "/" + lf); + } + } + + /** + * Read the next chunk. + * + * @throws IOException If an IO error occurs. + */ + private void nextChunk() throws IOException { + if (!bof) { + readCRLF(); + } + chunkSize = getChunkSize(); + if (chunkSize < 0) { + throw new IOException("Negative chunk size"); } - private String readChunkSizeInformation() throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bof = false; + pos = 0; + if (chunkSize == 0) { + eof = true; + parseTrailerHeaders(); + } + } + + /** + * Expects the stream to start with a chunk size in hex with optional comments after a semicolon. + * The line must end with a CRLF: "a3; some comment\r\n" Positions the stream at the start of the + * next line. + * + * @return the chunk size as integer + * @throws IOException when the chunk size could not be parsed + */ + private int getChunkSize() throws IOException { + + String dataString = readChunkSizeInformation(); + + int separator = dataString.indexOf(';'); + dataString = (separator > 0) ? dataString.substring(0, separator).trim() : dataString.trim(); + + int result; + try { + result = Integer.parseInt(dataString.trim(), 16); + } catch (NumberFormatException e) { + throw new IOException("Bad chunk size: " + dataString); + } + return result; + } + + private String readChunkSizeInformation() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + ChunkSizeState state = ChunkSizeState.NORMAL; + while (state != ChunkSizeState.END) { + int b = in.read(); + if (b == -1) { + throw new IOException("Chunked stream ended unexpectedly"); + } + state = state.process(in, baos, b); + } - ChunkSizeState state = ChunkSizeState.NORMAL; - while (state != ChunkSizeState.END) { - int b = in.read(); - if (b == -1) { - throw new IOException("Chunked stream ended unexpectedly"); - } - state = state.process(in, baos, b); + // parse data + return new String(baos.toByteArray(), Charset.forName("US-ASCII")); + } + + /** + * Reads and stores the Trailer headers. + * + * @throws IOException If an IO problem occurs + */ + private void parseTrailerHeaders() throws IOException { + if (trailerHeaders != null) { + List> footers = parseHeaders(in, StandardCharsets.US_ASCII); + for (Pair footer : footers) { + List values = trailerHeaders.get(footer.getKey()); + if (values == null) { + values = new LinkedList<>(); + trailerHeaders.put(footer.getKey(), values); } - //parse data - return new String(baos.toByteArray(), Charset.forName("US-ASCII")); + values.add(footer.getValue()); + } } - - /** - * Reads and stores the Trailer headers. - * @throws IOException If an IO problem occurs - */ - private void parseTrailerHeaders() throws IOException { - if (trailerHeaders != null) { - List> footers = parseHeaders(in, StandardCharsets.US_ASCII); - for (Pair footer : footers) { - List values = trailerHeaders.get(footer.getKey()); - if (values == null) { - values = new LinkedList<>(); - trailerHeaders.put(footer.getKey(), values); - } - - values.add(footer.getValue()); - } + } + + /** + * Upon close, this reads the remainder of the chunked message, leaving the underlying socket at a + * position to start reading the next response without scanning. + * + * @throws IOException If an IO problem occurs. + */ + @Override + public void close() throws IOException { + if (!closed) { + try { + if (!eof) { + exhaustInputStream(); } + } finally { + eof = true; + closed = true; + } } - - /** - * Upon close, this reads the remainder of the chunked message, - * leaving the underlying socket at a position to start reading the - * next response without scanning. - * @throws IOException If an IO problem occurs. - */ - @Override - public void close() throws IOException { - if (!closed) { - try { - if (!eof) { - exhaustInputStream(); - } - } finally { - eof = true; - closed = true; - } - } + } + + /** + * Exhaust our input stream, reading until EOF has been encountered. + * + *

Note that this function is intended as a non-public utility. This is a little weird, but it + * seemed silly to make a utility class for this one function, so instead it is just static and + * shared that way. + * + * @throws IOException If an IO problem occurs + */ + private void exhaustInputStream() throws IOException { + // read and discard the remainder of the message + byte[] buffer = new byte[1024]; + + log.trace("Clearing underlying input stream, this is what was left:"); + while (in.read(buffer) >= 0) { + if (log.isTraceEnabled()) { + log.trace(new String(buffer, StandardCharsets.UTF_8)); + } } - - /** - * Exhaust our input stream, reading until EOF has been encountered. - * - *

Note that this function is intended as a non-public utility. - * This is a little weird, but it seemed silly to make a utility - * class for this one function, so instead it is just static and - * shared that way.

- * - * @throws IOException If an IO problem occurs - */ - private void exhaustInputStream() throws IOException { - // read and discard the remainder of the message - byte[] buffer = new byte[1024]; - - log.trace("Clearing underlying input stream, this is what was left:"); - while (in.read(buffer) >= 0) { - if (log.isTraceEnabled()) { - log.trace(new String(buffer, StandardCharsets.UTF_8)); - } + } + + private List> parseHeaders(InputStream is, Charset charset) + throws IOException { + List> headers = new LinkedList<>(); + String name = null; + StringBuilder value = null; + String line = readLine(is, charset); + while (org.apache.commons.lang3.StringUtils.isNotBlank(line)) { + // Parse the header name and value + // Check for folded headers first + // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 + // discussion on folded headers + if (isLwsChar(line.charAt(0))) { + // we have continuation folded header + // so append value + if (value != null) { + value.append(' '); + value.append(line.trim()); } - } + } else { + // make sure we save the previous name, value pair if present + addHeaderValue(headers, name, value); - private List> parseHeaders(InputStream is, Charset charset) throws IOException { - List> headers = new LinkedList<>(); - String name = null; - StringBuilder value = null; - String line = readLine(is, charset); - while (org.apache.commons.lang3.StringUtils.isNotBlank(line)) { - // Parse the header name and value - // Check for folded headers first - // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 - // discussion on folded headers - if (isLwsChar(line.charAt(0))) { - // we have continuation folded header - // so append value - if (value != null) { - value.append(' '); - value.append(line.trim()); - } - } else { - // make sure we save the previous name, value pair if present - addHeaderValue(headers, name, value); - - // Otherwise we should have normal HTTP header line - // Parse the header name and value - int colon = line.indexOf(':'); - if (colon >= 0) { - name = line.substring(0, colon).trim(); - value = new StringBuilder(line.substring(colon + 1).trim()); - } - } - - line = readLine(is, charset); + // Otherwise we should have normal HTTP header line + // Parse the header name and value + int colon = line.indexOf(':'); + if (colon >= 0) { + name = line.substring(0, colon).trim(); + value = new StringBuilder(line.substring(colon + 1).trim()); } + } - // make sure we save the last name,value pair if present - addHeaderValue(headers, name, value); - - return headers; + line = readLine(is, charset); } - private void addHeaderValue(List> headers, String name, - StringBuilder value) { - if (name != null) { - headers.add(Pair.of(name, value.toString())); - } - } + // make sure we save the last name,value pair if present + addHeaderValue(headers, name, value); - private boolean isLwsChar(char c) { - return c == ' ' || c == '\t'; + return headers; + } + + private void addHeaderValue( + List> headers, String name, StringBuilder value) { + if (name != null) { + headers.add(Pair.of(name, value.toString())); } + } - private String readLine(InputStream inputStream, Charset charset) throws IOException { - byte[] rawdata = readRawLine(inputStream); - if (rawdata == null || rawdata.length == 0) { - return null; - } - // strip CR and LF from the end - int len = rawdata.length; - int offset = 0; - if (rawdata[len - 1] == '\n') { - offset++; - if (len > 1 && rawdata[len - 2] == '\r') { - offset++; - } - } + private boolean isLwsChar(char c) { + return c == ' ' || c == '\t'; + } - return new String(rawdata, 0, len - offset, charset); + private String readLine(InputStream inputStream, Charset charset) throws IOException { + byte[] rawdata = readRawLine(inputStream); + if (rawdata == null || rawdata.length == 0) { + return null; } - - private byte[] readRawLine(InputStream inputStream) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - int ch; - while ((ch = inputStream.read()) >= 0) { - buf.write(ch); - if (ch == '\n') { // be tolerant (RFC-2616 Section 19.3) - break; - } - } - return buf.toByteArray(); + // strip CR and LF from the end + int len = rawdata.length; + int offset = 0; + if (rawdata[len - 1] == '\n') { + offset++; + if (len > 1 && rawdata[len - 2] == '\r') { + offset++; + } } - private enum ChunkSizeState { - NORMAL { - @Override - public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) - throws IOException { - - ChunkSizeState newState; - if (b == '\r') { - newState = READ_CARRIAGE_RETURN; - } else { - if (b == '\"') { - newState = INSIDE_QUOTED_STRING; - } else { - newState = NORMAL; - } - baos.write(b); - } - return newState; - } - }, - READ_CARRIAGE_RETURN { - @Override - public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) - throws IOException { - - if (b != '\n') { - // this was not CRLF - throw new IOException("Protocol violation: Unexpected" - + " single newline character in chunk size"); - } - return END; - } - }, - INSIDE_QUOTED_STRING { - @Override - public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) - throws IOException { - - ChunkSizeState newState; - if (b == '\\') { - int nextByte = in.read(); - if (nextByte >= 0) { - baos.write(nextByte); - } - newState = INSIDE_QUOTED_STRING; - } else { - if (b == '\"') { - newState = NORMAL; - } else { - newState = INSIDE_QUOTED_STRING; - } - baos.write(b); - } - return newState; - } - }, - END { - @Override - public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) - throws IOException { - throw new UnsupportedOperationException("The END state cannot do any processing"); - } - }; - - public abstract ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) - throws IOException; + return new String(rawdata, 0, len - offset, charset); + } + + private byte[] readRawLine(InputStream inputStream) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + int ch; + while ((ch = inputStream.read()) >= 0) { + buf.write(ch); + if (ch == '\n') { // be tolerant (RFC-2616 Section 19.3) + break; + } } + return buf.toByteArray(); + } + + private enum ChunkSizeState { + NORMAL { + @Override + public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) + throws IOException { + + ChunkSizeState newState; + if (b == '\r') { + newState = READ_CARRIAGE_RETURN; + } else { + if (b == '\"') { + newState = INSIDE_QUOTED_STRING; + } else { + newState = NORMAL; + } + baos.write(b); + } + return newState; + } + }, + READ_CARRIAGE_RETURN { + @Override + public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) + throws IOException { + + if (b != '\n') { + // this was not CRLF + throw new IOException( + "Protocol violation: Unexpected" + " single newline character in chunk size"); + } + return END; + } + }, + INSIDE_QUOTED_STRING { + @Override + public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) + throws IOException { + + ChunkSizeState newState; + if (b == '\\') { + int nextByte = in.read(); + if (nextByte >= 0) { + baos.write(nextByte); + } + newState = INSIDE_QUOTED_STRING; + } else { + if (b == '\"') { + newState = NORMAL; + } else { + newState = INSIDE_QUOTED_STRING; + } + baos.write(b); + } + return newState; + } + }, + END { + @Override + public ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) + throws IOException { + throw new UnsupportedOperationException("The END state cannot do any processing"); + } + }; + + public abstract ChunkSizeState process(InputStream in, ByteArrayOutputStream baos, int b) + throws IOException; + } } diff --git a/src/main/java/me/desair/tus/server/util/TusServletRequest.java b/src/main/java/me/desair/tus/server/util/TusServletRequest.java index 463dcf7..28094e5 100644 --- a/src/main/java/me/desair/tus/server/util/TusServletRequest.java +++ b/src/main/java/me/desair/tus/server/util/TusServletRequest.java @@ -1,5 +1,7 @@ package me.desair.tus.server.util; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; import java.io.IOException; import java.io.InputStream; import java.security.DigestInputStream; @@ -12,9 +14,6 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.TusExtension; import me.desair.tus.server.checksum.ChecksumAlgorithm; @@ -24,129 +23,132 @@ public class TusServletRequest extends HttpServletRequestWrapper { - private CountingInputStream countingInputStream; - private Map digestInputStreamMap = new EnumMap<>(ChecksumAlgorithm.class); - - private InputStream contentInputStream = null; - private boolean isChunkedTransferDecodingEnabled = true; - - private Map> trailerHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - private Set processedBySet = new TreeSet<>(); - - /** - * Constructs a request object wrapping the given request. - * - * @param request The upload request we need to wrap - * @param isChunkedTransferDecodingEnabled Should this request wrapper decode a chunked input stream - * @throws IllegalArgumentException if the request is null - */ - public TusServletRequest(HttpServletRequest request, boolean isChunkedTransferDecodingEnabled) { - super(request); - this.isChunkedTransferDecodingEnabled = isChunkedTransferDecodingEnabled; + private CountingInputStream countingInputStream; + private Map digestInputStreamMap = + new EnumMap<>(ChecksumAlgorithm.class); + + private InputStream contentInputStream = null; + private boolean isChunkedTransferDecodingEnabled = true; + + private Map> trailerHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private Set processedBySet = new TreeSet<>(); + + /** + * Constructs a request object wrapping the given request. + * + * @param request The upload request we need to wrap + * @param isChunkedTransferDecodingEnabled Should this request wrapper decode a chunked input + * stream + * @throws IllegalArgumentException if the request is null + */ + public TusServletRequest(HttpServletRequest request, boolean isChunkedTransferDecodingEnabled) { + super(request); + this.isChunkedTransferDecodingEnabled = isChunkedTransferDecodingEnabled; + } + + /** + * Constructs a request object wrapping the given request. + * + * @param request The upload request we need to wrap + * @throws IllegalArgumentException if the request is null + */ + public TusServletRequest(HttpServletRequest request) { + this(request, false); + } + + public InputStream getContentInputStream() throws IOException { + if (contentInputStream == null) { + contentInputStream = super.getInputStream(); + + // If we're dealing with chunked transfer encoding, + // abstract it so that the rest of our code doesn't need to care + boolean isChunked = hasChunkedTransferEncoding(); + if (isChunked && isChunkedTransferDecodingEnabled) { + contentInputStream = new HttpChunkedEncodingInputStream(contentInputStream, trailerHeaders); + } + + countingInputStream = new CountingInputStream(contentInputStream); + contentInputStream = countingInputStream; + + ChecksumAlgorithm checksumAlgorithm = + ChecksumAlgorithm.forUploadChecksumHeader(getHeader(HttpHeader.UPLOAD_CHECKSUM)); + + List algorithms; + + if (isChunked) { + // Since the Checksum header can still come at the end, keep track of all checksums + algorithms = Arrays.asList(ChecksumAlgorithm.values()); + } else if (checksumAlgorithm != null) { + algorithms = Collections.singletonList(checksumAlgorithm); + } else { + algorithms = Collections.emptyList(); + } + + for (ChecksumAlgorithm algorithm : algorithms) { + DigestInputStream is = + new DigestInputStream(contentInputStream, algorithm.getMessageDigest()); + digestInputStreamMap.put(algorithm, is); + + contentInputStream = is; + } } - /** - * Constructs a request object wrapping the given request. - * - * @param request The upload request we need to wrap - * @throws IllegalArgumentException if the request is null - */ - public TusServletRequest(HttpServletRequest request) { - this(request, false); + return contentInputStream; + } + + public long getBytesRead() { + return countingInputStream == null ? 0 : countingInputStream.getByteCount(); + } + + public boolean hasCalculatedChecksum() { + return !digestInputStreamMap.isEmpty(); + } + + public String getCalculatedChecksum(ChecksumAlgorithm algorithm) { + MessageDigest messageDigest = getMessageDigest(algorithm); + return messageDigest == null ? null : Base64.encodeBase64String(messageDigest.digest()); + } + + /** + * Get the set of checksum algorithms that are actively calculated within this request + * + * @return The set of active checksum algorithms + */ + public Set getEnabledChecksums() { + return digestInputStreamMap.keySet(); + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(name); + + if (StringUtils.isBlank(value) && trailerHeaders.containsKey(name)) { + List values = trailerHeaders.get(name); + if (values != null && !values.isEmpty()) { + value = values.get(0); + } } - public InputStream getContentInputStream() throws IOException { - if (contentInputStream == null) { - contentInputStream = super.getInputStream(); - - //If we're dealing with chunked transfer encoding, - //abstract it so that the rest of our code doesn't need to care - boolean isChunked = hasChunkedTransferEncoding(); - if (isChunked && isChunkedTransferDecodingEnabled) { - contentInputStream = new HttpChunkedEncodingInputStream(contentInputStream, trailerHeaders); - } - - countingInputStream = new CountingInputStream(contentInputStream); - contentInputStream = countingInputStream; - - ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.forUploadChecksumHeader( - getHeader(HttpHeader.UPLOAD_CHECKSUM)); + return value; + } - List algorithms; + public boolean isProcessedBy(TusExtension processor) { + return processedBySet.contains(processor.getName()); + } - if (isChunked) { - //Since the Checksum header can still come at the end, keep track of all checksums - algorithms = Arrays.asList(ChecksumAlgorithm.values()); - } else if (checksumAlgorithm != null) { - algorithms = Collections.singletonList(checksumAlgorithm); - } else { - algorithms = Collections.emptyList(); - } + public void addProcessor(TusExtension processor) { + processedBySet.add(processor.getName()); + } - for (ChecksumAlgorithm algorithm : algorithms) { - DigestInputStream is = new DigestInputStream(contentInputStream, algorithm.getMessageDigest()); - digestInputStreamMap.put(algorithm, is); - - contentInputStream = is; - } - } - - return contentInputStream; - } - - public long getBytesRead() { - return countingInputStream == null ? 0 : countingInputStream.getByteCount(); - } - - public boolean hasCalculatedChecksum() { - return !digestInputStreamMap.isEmpty(); - } - - public String getCalculatedChecksum(ChecksumAlgorithm algorithm) { - MessageDigest messageDigest = getMessageDigest(algorithm); - return messageDigest == null ? null : - Base64.encodeBase64String(messageDigest.digest()); - } - - /** - * Get the set of checksum algorithms that are actively calculated within this request - * @return The set of active checksum algorithms - */ - public Set getEnabledChecksums() { - return digestInputStreamMap.keySet(); - } - - @Override - public String getHeader(String name) { - String value = super.getHeader(name); - - if (StringUtils.isBlank(value) && trailerHeaders.containsKey(name)) { - List values = trailerHeaders.get(name); - if (values != null && !values.isEmpty()) { - value = values.get(0); - } - } - - return value; - } - - public boolean isProcessedBy(TusExtension processor) { - return processedBySet.contains(processor.getName()); - } - - public void addProcessor(TusExtension processor) { - processedBySet.add(processor.getName()); - } - - private boolean hasChunkedTransferEncoding() { - return StringUtils.equalsIgnoreCase("chunked", getHeader(HttpHeader.TRANSFER_ENCODING)); - } + private boolean hasChunkedTransferEncoding() { + return StringUtils.equalsIgnoreCase("chunked", getHeader(HttpHeader.TRANSFER_ENCODING)); + } - private MessageDigest getMessageDigest(ChecksumAlgorithm algorithm) { - if (digestInputStreamMap.containsKey(algorithm)) { - return digestInputStreamMap.get(algorithm).getMessageDigest(); - } else { - return null; - } + private MessageDigest getMessageDigest(ChecksumAlgorithm algorithm) { + if (digestInputStreamMap.containsKey(algorithm)) { + return digestInputStreamMap.get(algorithm).getMessageDigest(); + } else { + return null; } + } } diff --git a/src/main/java/me/desair/tus/server/util/TusServletResponse.java b/src/main/java/me/desair/tus/server/util/TusServletResponse.java index 14237f0..5b0239d 100644 --- a/src/main/java/me/desair/tus/server/util/TusServletResponse.java +++ b/src/main/java/me/desair/tus/server/util/TusServletResponse.java @@ -1,93 +1,92 @@ package me.desair.tus.server.util; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; - import org.apache.commons.lang3.StringUtils; /** - * {@link HttpServletResponseWrapper} to capture header values set on the current {@link HttpServletResponse} + * {@link HttpServletResponseWrapper} to capture header values set on the current {@link + * HttpServletResponse} */ public class TusServletResponse extends HttpServletResponseWrapper { - private Map> headers = new HashMap<>(); - - /** - * Constructs a response adaptor wrapping the given response. - * - * @param response The response that has to be wrapped - * @throws IllegalArgumentException if the response is null - */ - public TusServletResponse(HttpServletResponse response) { - super(response); + private Map> headers = new HashMap<>(); + + /** + * Constructs a response adaptor wrapping the given response. + * + * @param response The response that has to be wrapped + * @throws IllegalArgumentException if the response is null + */ + public TusServletResponse(HttpServletResponse response) { + super(response); + } + + @Override + public void setDateHeader(String name, long date) { + super.setDateHeader(name, date); + overwriteHeader(name, Objects.toString(date)); + } + + @Override + public void addDateHeader(String name, long date) { + super.addDateHeader(name, date); + recordHeader(name, Objects.toString(date)); + } + + @Override + public void setHeader(String name, String value) { + super.setHeader(name, value); + overwriteHeader(name, value); + } + + @Override + public void addHeader(String name, String value) { + super.addHeader(name, value); + recordHeader(name, value); + } + + @Override + public void setIntHeader(String name, int value) { + super.setIntHeader(name, value); + overwriteHeader(name, Objects.toString(value)); + } + + @Override + public void addIntHeader(String name, int value) { + super.addIntHeader(name, value); + recordHeader(name, Objects.toString(value)); + } + + @Override + public String getHeader(String name) { + String value; + if (headers.containsKey(name)) { + value = headers.get(name).get(0); + } else { + value = super.getHeader(name); } - - @Override - public void setDateHeader(String name, long date) { - super.setDateHeader(name, date); - overwriteHeader(name, Objects.toString(date)); - } - - @Override - public void addDateHeader(String name, long date) { - super.addDateHeader(name, date); - recordHeader(name, Objects.toString(date)); + return StringUtils.trimToNull(value); + } + + private void recordHeader(String name, String value) { + List values = headers.get(name); + if (values == null) { + values = new LinkedList<>(); + headers.put(name, values); } - @Override - public void setHeader(String name, String value) { - super.setHeader(name, value); - overwriteHeader(name, value); - } - - @Override - public void addHeader(String name, String value) { - super.addHeader(name, value); - recordHeader(name, value); - } - - @Override - public void setIntHeader(String name, int value) { - super.setIntHeader(name, value); - overwriteHeader(name, Objects.toString(value)); - } - - @Override - public void addIntHeader(String name, int value) { - super.addIntHeader(name, value); - recordHeader(name, Objects.toString(value)); - } - - @Override - public String getHeader(String name) { - String value; - if (headers.containsKey(name)) { - value = headers.get(name).get(0); - } else { - value = super.getHeader(name); - } - return StringUtils.trimToNull(value); - } - - private void recordHeader(String name, String value) { - List values = headers.get(name); - if (values == null) { - values = new LinkedList<>(); - headers.put(name, values); - } - - values.add(value); - } - - private void overwriteHeader(String name, String value) { - List values = new LinkedList<>(); - values.add(value); - headers.put(name, values); - } + values.add(value); + } + private void overwriteHeader(String name, String value) { + List values = new LinkedList<>(); + values.add(value); + headers.put(name, values); + } } diff --git a/src/main/java/me/desair/tus/server/util/Utils.java b/src/main/java/me/desair/tus/server/util/Utils.java index 252915a..4423d6c 100644 --- a/src/main/java/me/desair/tus/server/util/Utils.java +++ b/src/main/java/me/desair/tus/server/util/Utils.java @@ -5,6 +5,7 @@ import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; +import jakarta.servlet.http.HttpServletRequest; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.ObjectInputStream; @@ -18,139 +19,135 @@ import java.nio.file.Path; import java.util.LinkedList; import java.util.List; -import jakarta.servlet.http.HttpServletRequest; - import me.desair.tus.server.HttpHeader; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Utility class that contains various static helper methods - */ +/** Utility class that contains various static helper methods */ public class Utils { - private static final Logger log = LoggerFactory.getLogger(Utils.class); - private static final int LOCK_FILE_RETRY_COUNT = 3; - private static final long LOCK_FILE_SLEEP_TIME = 500; + private static final Logger log = LoggerFactory.getLogger(Utils.class); + private static final int LOCK_FILE_RETRY_COUNT = 3; + private static final long LOCK_FILE_SLEEP_TIME = 500; - private Utils() { - //This is a utility class that only holds static utility methods - } + private Utils() { + // This is a utility class that only holds static utility methods + } - public static String getHeader(HttpServletRequest request, String header) { - return StringUtils.trimToEmpty(request.getHeader(header)); - } + public static String getHeader(HttpServletRequest request, String header) { + return StringUtils.trimToEmpty(request.getHeader(header)); + } - public static Long getLongHeader(HttpServletRequest request, String header) { - try { - return Long.valueOf(getHeader(request, header)); - } catch (NumberFormatException ex) { - return null; - } + public static Long getLongHeader(HttpServletRequest request, String header) { + try { + return Long.valueOf(getHeader(request, header)); + } catch (NumberFormatException ex) { + return null; } - - /** - * Build a comma-separated list based on the remote address of the request and the - * X-Forwareded-For header. The list is constructed as "client, proxy1, proxy2". - * @return A comma-separated list of ip-addresses - */ - public static String buildRemoteIpList(HttpServletRequest servletRequest) { - String ipAddresses = servletRequest.getRemoteAddr(); - String xforwardedForHeader = getHeader(servletRequest, HttpHeader.X_FORWARDED_FOR); - if (xforwardedForHeader.length() > 0) { - ipAddresses = xforwardedForHeader + ", " + ipAddresses; - } - return ipAddresses; + } + + /** + * Build a comma-separated list based on the remote address of the request and the + * X-Forwareded-For header. The list is constructed as "client, proxy1, proxy2". + * + * @return A comma-separated list of ip-addresses + */ + public static String buildRemoteIpList(HttpServletRequest servletRequest) { + String ipAddresses = servletRequest.getRemoteAddr(); + String xforwardedForHeader = getHeader(servletRequest, HttpHeader.X_FORWARDED_FOR); + if (xforwardedForHeader.length() > 0) { + ipAddresses = xforwardedForHeader + ", " + ipAddresses; } + return ipAddresses; + } - public static List parseConcatenationIDsFromHeader(String uploadConcatValue) { - List output = new LinkedList<>(); + public static List parseConcatenationIDsFromHeader(String uploadConcatValue) { + List output = new LinkedList<>(); - String idString = StringUtils.substringAfter(uploadConcatValue, ";"); - for (String id : StringUtils.trimToEmpty(idString).split("\\s")) { - output.add(id); - } - - return output; + String idString = StringUtils.substringAfter(uploadConcatValue, ";"); + for (String id : StringUtils.trimToEmpty(idString).split("\\s")) { + output.add(id); } - public static T readSerializable(Path path, Class clazz) throws IOException { - T info = null; - if (path != null) { - try (FileChannel channel = FileChannel.open(path, READ)) { - //Lock will be released when the channel is closed - if (lockFileShared(channel) != null) { - - try (ObjectInputStream ois = new ObjectInputStream(Channels.newInputStream(channel))) { - info = clazz.cast(ois.readObject()); - } catch (ClassNotFoundException e) { - //This should not happen - info = null; - } - } else { - throw new IOException("Unable to lock file " + path); - } - } + return output; + } + + public static T readSerializable(Path path, Class clazz) throws IOException { + T info = null; + if (path != null) { + try (FileChannel channel = FileChannel.open(path, READ)) { + // Lock will be released when the channel is closed + if (lockFileShared(channel) != null) { + + try (ObjectInputStream ois = new ObjectInputStream(Channels.newInputStream(channel))) { + info = clazz.cast(ois.readObject()); + } catch (ClassNotFoundException e) { + // This should not happen + info = null; + } + } else { + throw new IOException("Unable to lock file " + path); } - return info; + } } - - - public static void writeSerializable(Serializable object, Path path) throws IOException { - if (path != null) { - try (FileChannel channel = FileChannel.open(path, WRITE, CREATE, TRUNCATE_EXISTING)) { - //Lock will be released when the channel is closed - if (lockFileExclusively(channel) != null) { - - try (OutputStream buffer = new BufferedOutputStream(Channels.newOutputStream(channel)); - ObjectOutput output = new ObjectOutputStream(buffer)) { - - output.writeObject(object); - } - } else { - throw new IOException("Unable to lock file " + path); - } - } + return info; + } + + public static void writeSerializable(Serializable object, Path path) throws IOException { + if (path != null) { + try (FileChannel channel = FileChannel.open(path, WRITE, CREATE, TRUNCATE_EXISTING)) { + // Lock will be released when the channel is closed + if (lockFileExclusively(channel) != null) { + + try (OutputStream buffer = new BufferedOutputStream(Channels.newOutputStream(channel)); + ObjectOutput output = new ObjectOutputStream(buffer)) { + + output.writeObject(object); + } + } else { + throw new IOException("Unable to lock file " + path); } + } } - - public static FileLock lockFileExclusively(FileChannel channel) throws IOException { - return lockFile(channel, false); + } + + public static FileLock lockFileExclusively(FileChannel channel) throws IOException { + return lockFile(channel, false); + } + + public static FileLock lockFileShared(FileChannel channel) throws IOException { + return lockFile(channel, true); + } + + /** + * Sleep the specified number of milliseconds + * + * @param sleepTimeMillis The time to sleep in milliseconds + */ + public static void sleep(long sleepTimeMillis) { + try { + Thread.sleep(sleepTimeMillis); + } catch (InterruptedException e) { + log.warn("Sleep was interrupted"); + // Restore interrupted state... + Thread.currentThread().interrupt(); } + } - public static FileLock lockFileShared(FileChannel channel) throws IOException { - return lockFile(channel, true); - } + private static FileLock lockFile(FileChannel channel, boolean shared) throws IOException { + int i = 0; + FileLock lock = null; + do { + if (i > 0) { + sleep(LOCK_FILE_SLEEP_TIME); + } - /** - * Sleep the specified number of milliseconds - * @param sleepTimeMillis The time to sleep in milliseconds - */ - public static void sleep(long sleepTimeMillis) { - try { - Thread.sleep(sleepTimeMillis); - } catch (InterruptedException e) { - log.warn("Sleep was interrupted"); - // Restore interrupted state... - Thread.currentThread().interrupt(); - } - } - - private static FileLock lockFile(FileChannel channel, boolean shared) throws IOException { - int i = 0; - FileLock lock = null; - do { - if (i > 0) { - sleep(LOCK_FILE_SLEEP_TIME); - } - - lock = channel.tryLock(0L, Long.MAX_VALUE, shared); + lock = channel.tryLock(0L, Long.MAX_VALUE, shared); - i++; - } while (lock == null && i < LOCK_FILE_RETRY_COUNT); - - return lock; - } + i++; + } while (lock == null && i < LOCK_FILE_RETRY_COUNT); + return lock; + } } diff --git a/src/test/java/me/desair/tus/server/AbstractTusExtensionIntegrationTest.java b/src/test/java/me/desair/tus/server/AbstractTusExtensionIntegrationTest.java index 5b529a8..59be21b 100644 --- a/src/test/java/me/desair/tus/server/AbstractTusExtensionIntegrationTest.java +++ b/src/test/java/me/desair/tus/server/AbstractTusExtensionIntegrationTest.java @@ -12,7 +12,6 @@ import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.Arrays; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.upload.UploadStorageService; @@ -31,76 +30,82 @@ @RunWith(MockitoJUnitRunner.Silent.class) public abstract class AbstractTusExtensionIntegrationTest { - private static final Logger log = LoggerFactory.getLogger(AbstractTusExtensionIntegrationTest.class); - - protected AbstractTusExtension tusFeature; - - protected MockHttpServletRequest servletRequest; - - protected MockHttpServletResponse servletResponse; - - @Mock - protected UploadStorageService uploadStorageService; - - protected UploadInfo uploadInfo; - - protected void prepareUploadInfo(Long offset, Long length) throws IOException, TusException { - uploadInfo = new UploadInfo(); - uploadInfo.setOffset(offset); - uploadInfo.setLength(length); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(uploadInfo); - when(uploadStorageService.append(any(UploadInfo.class), any(InputStream.class))).thenReturn(uploadInfo); - } - - protected void setRequestHeaders(String... headers) { - if (headers != null && headers.length > 0) { - for (String header : headers) { - switch (header) { - case HttpHeader.TUS_RESUMABLE: - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - break; - case HttpHeader.CONTENT_TYPE: - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - break; - case HttpHeader.UPLOAD_OFFSET: - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, uploadInfo.getOffset()); - break; - case HttpHeader.CONTENT_LENGTH: - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, - uploadInfo.getLength() - uploadInfo.getOffset()); - break; - default: - log.warn("Undefined HTTP header " + header); - break; - } - } + private static final Logger log = + LoggerFactory.getLogger(AbstractTusExtensionIntegrationTest.class); + + protected AbstractTusExtension tusFeature; + + protected MockHttpServletRequest servletRequest; + + protected MockHttpServletResponse servletResponse; + + @Mock protected UploadStorageService uploadStorageService; + + protected UploadInfo uploadInfo; + + protected void prepareUploadInfo(Long offset, Long length) throws IOException, TusException { + uploadInfo = new UploadInfo(); + uploadInfo.setOffset(offset); + uploadInfo.setLength(length); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(uploadInfo); + when(uploadStorageService.append(any(UploadInfo.class), any(InputStream.class))) + .thenReturn(uploadInfo); + } + + protected void setRequestHeaders(String... headers) { + if (headers != null && headers.length > 0) { + for (String header : headers) { + switch (header) { + case HttpHeader.TUS_RESUMABLE: + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + break; + case HttpHeader.CONTENT_TYPE: + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + break; + case HttpHeader.UPLOAD_OFFSET: + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, uploadInfo.getOffset()); + break; + case HttpHeader.CONTENT_LENGTH: + servletRequest.addHeader( + HttpHeader.CONTENT_LENGTH, uploadInfo.getLength() - uploadInfo.getOffset()); + break; + default: + log.warn("Undefined HTTP header " + header); + break; } + } } + } - protected void executeCall(HttpMethod method, boolean readContent) throws TusException, IOException { - tusFeature.validate(method, servletRequest, uploadStorageService, null); - TusServletRequest tusServletRequest = new TusServletRequest(this.servletRequest, true); - - if (readContent) { - StringWriter writer = new StringWriter(); - IOUtils.copy(tusServletRequest.getContentInputStream(), writer, StandardCharsets.UTF_8); - } - - tusFeature.process(method, tusServletRequest, - new TusServletResponse(servletResponse), uploadStorageService, null); - } - - protected void assertResponseHeader(String header, String value) { - assertThat(servletResponse.getHeader(header), is(value)); - } - - protected void assertResponseHeader(String header, String... values) { - assertThat(Arrays.asList(servletResponse.getHeader(header).split(",")), - containsInAnyOrder(values)); - } + protected void executeCall(HttpMethod method, boolean readContent) + throws TusException, IOException { + tusFeature.validate(method, servletRequest, uploadStorageService, null); + TusServletRequest tusServletRequest = new TusServletRequest(this.servletRequest, true); - protected void assertResponseStatus(int httpStatus) { - assertThat(servletResponse.getStatus(), is(httpStatus)); + if (readContent) { + StringWriter writer = new StringWriter(); + IOUtils.copy(tusServletRequest.getContentInputStream(), writer, StandardCharsets.UTF_8); } + tusFeature.process( + method, + tusServletRequest, + new TusServletResponse(servletResponse), + uploadStorageService, + null); + } + + protected void assertResponseHeader(String header, String value) { + assertThat(servletResponse.getHeader(header), is(value)); + } + + protected void assertResponseHeader(String header, String... values) { + assertThat( + Arrays.asList(servletResponse.getHeader(header).split(",")), containsInAnyOrder(values)); + } + + protected void assertResponseStatus(int httpStatus) { + assertThat(servletResponse.getStatus(), is(httpStatus)); + } } diff --git a/src/test/java/me/desair/tus/server/HttpMethodTest.java b/src/test/java/me/desair/tus/server/HttpMethodTest.java index a85fb65..c50db1b 100644 --- a/src/test/java/me/desair/tus/server/HttpMethodTest.java +++ b/src/test/java/me/desair/tus/server/HttpMethodTest.java @@ -3,73 +3,75 @@ import static org.junit.Assert.assertEquals; import java.util.EnumSet; - import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; public class HttpMethodTest { - @Test - public void forName() throws Exception { - assertEquals(HttpMethod.DELETE, HttpMethod.forName("delete")); - assertEquals(HttpMethod.GET, HttpMethod.forName("get")); - assertEquals(HttpMethod.HEAD, HttpMethod.forName("head")); - assertEquals(HttpMethod.PATCH, HttpMethod.forName("patch")); - assertEquals(HttpMethod.POST, HttpMethod.forName("post")); - assertEquals(HttpMethod.PUT, HttpMethod.forName("put")); - assertEquals(HttpMethod.OPTIONS, HttpMethod.forName("options")); - assertEquals(null, HttpMethod.forName("test")); - } - - @Test - public void getMethodNormal() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setMethod("patch"); - - assertEquals(HttpMethod.PATCH, - HttpMethod.getMethodIfSupported(servletRequest, EnumSet.allOf(HttpMethod.class))); - } - - @Test - public void getMethodOverridden() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setMethod("post"); - servletRequest.addHeader(HttpHeader.METHOD_OVERRIDE, "patch"); - - assertEquals(HttpMethod.PATCH, - HttpMethod.getMethodIfSupported(servletRequest, EnumSet.allOf(HttpMethod.class))); - } - - @Test - public void getMethodOverriddenDoesNotExist() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setMethod("post"); - servletRequest.addHeader(HttpHeader.METHOD_OVERRIDE, "test"); - - assertEquals(HttpMethod.POST, - HttpMethod.getMethodIfSupported(servletRequest, EnumSet.allOf(HttpMethod.class))); - } - - @Test(expected = NullPointerException.class) - public void getMethodNull() throws Exception { - HttpMethod.getMethodIfSupported(null, EnumSet.allOf(HttpMethod.class)); - } - - @Test - public void getMethodNotSupported() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setMethod("put"); - - assertEquals(null, - HttpMethod.getMethodIfSupported(servletRequest, EnumSet.noneOf(HttpMethod.class))); - } - - @Test - public void getMethodRequestNotExists() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setMethod("test"); - - assertEquals(null, - HttpMethod.getMethodIfSupported(servletRequest, EnumSet.noneOf(HttpMethod.class))); - } -} \ No newline at end of file + @Test + public void forName() throws Exception { + assertEquals(HttpMethod.DELETE, HttpMethod.forName("delete")); + assertEquals(HttpMethod.GET, HttpMethod.forName("get")); + assertEquals(HttpMethod.HEAD, HttpMethod.forName("head")); + assertEquals(HttpMethod.PATCH, HttpMethod.forName("patch")); + assertEquals(HttpMethod.POST, HttpMethod.forName("post")); + assertEquals(HttpMethod.PUT, HttpMethod.forName("put")); + assertEquals(HttpMethod.OPTIONS, HttpMethod.forName("options")); + assertEquals(null, HttpMethod.forName("test")); + } + + @Test + public void getMethodNormal() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setMethod("patch"); + + assertEquals( + HttpMethod.PATCH, + HttpMethod.getMethodIfSupported(servletRequest, EnumSet.allOf(HttpMethod.class))); + } + + @Test + public void getMethodOverridden() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setMethod("post"); + servletRequest.addHeader(HttpHeader.METHOD_OVERRIDE, "patch"); + + assertEquals( + HttpMethod.PATCH, + HttpMethod.getMethodIfSupported(servletRequest, EnumSet.allOf(HttpMethod.class))); + } + + @Test + public void getMethodOverriddenDoesNotExist() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setMethod("post"); + servletRequest.addHeader(HttpHeader.METHOD_OVERRIDE, "test"); + + assertEquals( + HttpMethod.POST, + HttpMethod.getMethodIfSupported(servletRequest, EnumSet.allOf(HttpMethod.class))); + } + + @Test(expected = NullPointerException.class) + public void getMethodNull() throws Exception { + HttpMethod.getMethodIfSupported(null, EnumSet.allOf(HttpMethod.class)); + } + + @Test + public void getMethodNotSupported() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setMethod("put"); + + assertEquals( + null, HttpMethod.getMethodIfSupported(servletRequest, EnumSet.noneOf(HttpMethod.class))); + } + + @Test + public void getMethodRequestNotExists() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setMethod("test"); + + assertEquals( + null, HttpMethod.getMethodIfSupported(servletRequest, EnumSet.noneOf(HttpMethod.class))); + } +} diff --git a/src/test/java/me/desair/tus/server/ITTusFileUploadService.java b/src/test/java/me/desair/tus/server/ITTusFileUploadService.java index 37f6dfa..f65712e 100644 --- a/src/test/java/me/desair/tus/server/ITTusFileUploadService.java +++ b/src/test/java/me/desair/tus/server/ITTusFileUploadService.java @@ -12,6 +12,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -23,8 +24,6 @@ import java.util.Arrays; import java.util.Locale; import java.util.UUID; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.exception.TusException; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.util.Utils; @@ -40,1336 +39,1409 @@ public class ITTusFileUploadService { - protected static final String UPLOAD_URI = "/test/upload"; - protected static final String OWNER_KEY = "JOHN_DOE"; + protected static final String UPLOAD_URI = "/test/upload"; + protected static final String OWNER_KEY = "JOHN_DOE"; - private static final DateFormat mockDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + private static final DateFormat mockDateFormat = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); - protected MockHttpServletRequest servletRequest; - protected MockHttpServletResponse servletResponse; + protected MockHttpServletRequest servletRequest; + protected MockHttpServletResponse servletResponse; - protected TusFileUploadService tusFileUploadService; + protected TusFileUploadService tusFileUploadService; - protected static Path storagePath; + protected static Path storagePath; - @BeforeClass - public static void setupDataFolder() throws IOException { - storagePath = Paths.get("target", "tus", "data").toAbsolutePath(); - Files.createDirectories(storagePath); - } + @BeforeClass + public static void setupDataFolder() throws IOException { + storagePath = Paths.get("target", "tus", "data").toAbsolutePath(); + Files.createDirectories(storagePath); + } - @AfterClass - public static void destroyDataFolder() throws IOException { - FileUtils.deleteDirectory(storagePath.toFile()); - } + @AfterClass + public static void destroyDataFolder() throws IOException { + FileUtils.deleteDirectory(storagePath.toFile()); + } - @Before - public void setUp() { - reset(); - tusFileUploadService = new TusFileUploadService() + @Before + public void setUp() { + reset(); + tusFileUploadService = + new TusFileUploadService() .withUploadURI(UPLOAD_URI) .withStoragePath(storagePath.toAbsolutePath().toString()) .withMaxUploadSize(1073741824L) .withUploadExpirationPeriod(2L * 24 * 60 * 60 * 1000) .withDownloadFeature() .withChunkedTransferDecoding(true); + } + + protected void reset() { + servletRequest = new MockHttpServletRequest(); + servletRequest.setRemoteAddr("192.168.1.1"); + servletRequest.addHeader(HttpHeader.X_FORWARDED_FOR, "10.0.2.1, 123.231.12.4"); + servletResponse = new MockHttpServletResponse(); + } + + @Test + public void testSupportedHttpMethods() { + assertThat( + tusFileUploadService.getSupportedHttpMethods(), + containsInAnyOrder( + HttpMethod.HEAD, + HttpMethod.OPTIONS, + HttpMethod.PATCH, + HttpMethod.POST, + HttpMethod.DELETE, + HttpMethod.GET)); + + assertThat( + tusFileUploadService.getEnabledFeatures(), + containsInAnyOrder( + "core", + "creation", + "checksum", + "termination", + "download", + "expiration", + "concatenation")); + } + + @Test + public void testDisableFeature() throws Exception { + tusFileUploadService.disableTusExtension("download"); + tusFileUploadService.disableTusExtension("termination"); + + assertThat( + tusFileUploadService.getSupportedHttpMethods(), + containsInAnyOrder(HttpMethod.HEAD, HttpMethod.OPTIONS, HttpMethod.PATCH, HttpMethod.POST)); + + assertThat( + tusFileUploadService.getEnabledFeatures(), + containsInAnyOrder("core", "creation", "checksum", "expiration", "concatenation")); + + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + + reset(); + servletRequest.setMethod("DELETE"); + servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + + @Test(expected = IllegalArgumentException.class) + public void testDisableCore() { + tusFileUploadService.disableTusExtension("core"); + } + + @Test(expected = NullPointerException.class) + public void testWithFileStoreServiceNull() throws Exception { + tusFileUploadService.withUploadStorageService(null); + } + + @Test + public void testProcessCompleteUpload() throws Exception { + String uploadContent = "This is my test upload content"; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 Mfhm5HaSPUf+pUakdMxARo4rvfQ="); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(uploadContent.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Check with HEAD request upload is complete + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "" + uploadContent.getBytes().length); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Get upload info from service + UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertFalse(info.isUploadInProgress()); + assertThat(info.getLength(), is((long) uploadContent.getBytes().length)); + assertThat(info.getOffset(), is((long) uploadContent.getBytes().length)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); + + // Try retrieving the uploaded bytes without owner key + try { + tusFileUploadService.getUploadedBytes(location); + fail(); + } catch (TusException ex) { + assertThat(ex.getStatus(), is(404)); } - protected void reset() { - servletRequest = new MockHttpServletRequest(); - servletRequest.setRemoteAddr("192.168.1.1"); - servletRequest.addHeader(HttpHeader.X_FORWARDED_FOR, "10.0.2.1, 123.231.12.4"); - servletResponse = new MockHttpServletResponse(); - } - - @Test - public void testSupportedHttpMethods() { - assertThat(tusFileUploadService.getSupportedHttpMethods(), containsInAnyOrder( - HttpMethod.HEAD, HttpMethod.OPTIONS, HttpMethod.PATCH, - HttpMethod.POST, HttpMethod.DELETE, HttpMethod.GET)); - - assertThat(tusFileUploadService.getEnabledFeatures(), containsInAnyOrder( - "core", "creation", "checksum", "termination", "download", "expiration", "concatenation")); - } - - @Test - public void testDisableFeature() throws Exception { - tusFileUploadService.disableTusExtension("download"); - tusFileUploadService.disableTusExtension("termination"); - - assertThat(tusFileUploadService.getSupportedHttpMethods(), containsInAnyOrder( - HttpMethod.HEAD, HttpMethod.OPTIONS, HttpMethod.PATCH, - HttpMethod.POST)); - - assertThat(tusFileUploadService.getEnabledFeatures(), containsInAnyOrder( - "core", "creation", "checksum", "expiration", "concatenation")); - - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - - reset(); - servletRequest.setMethod("DELETE"); - servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - } - - @Test(expected = IllegalArgumentException.class) - public void testDisableCore() { - tusFileUploadService.disableTusExtension("core"); - } - - @Test(expected = NullPointerException.class) - public void testWithFileStoreServiceNull() throws Exception { - tusFileUploadService.withUploadStorageService(null); - } - - @Test - public void testProcessCompleteUpload() throws Exception { - String uploadContent = "This is my test upload content"; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 Mfhm5HaSPUf+pUakdMxARo4rvfQ="); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(uploadContent.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Check with HEAD request upload is complete - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "" + uploadContent.getBytes().length); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Get upload info from service - UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertFalse(info.isUploadInProgress()); - assertThat(info.getLength(), is((long) uploadContent.getBytes().length)); - assertThat(info.getOffset(), is((long) uploadContent.getBytes().length)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); - - //Try retrieving the uploaded bytes without owner key - try { - tusFileUploadService.getUploadedBytes(location); - fail(); - } catch (TusException ex) { - assertThat(ex.getStatus(), is(404)); - } - - //Get uploaded bytes from service - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("This is my test upload content")); - } - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Download the upload - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + uploadContent.getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_OK); - assertThat(servletResponse.getContentAsString(), is("This is my test upload content")); - - //Pretend that we processed the upload and that we can remove it - tusFileUploadService.deleteUpload(location, OWNER_KEY); - - //Check that the upload is really gone - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - } - - @Test - public void testTerminateViaHttpRequest() throws Exception { - String uploadContent = "This is my terminated test upload"; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(uploadContent.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Download the upload to make sure it was uploaded correctly - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + uploadContent.getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_OK); - assertThat(servletResponse.getContentAsString(), is("This is my terminated test upload")); - - //Terminate the upload so that the server can remove it - reset(); - servletRequest.setMethod("DELETE"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Check that the upload is really gone - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - } - - @Test - public void testProcessUploadTwoParts() throws Exception { - String part1 = "29\r\nThis is the first part of my test upload " - + "\r\n0\r\nUpload-Checksum: sha1 n5RQbRwM6UVAD+9iuHEmnN6HCGQ="; - String part2 = "1C\r\nand this is the second part." - + "\r\n0\r\nUpload-Checksum: sha1 oNge323kGFKICxp+Me5xJgPvGEM="; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "69"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Upload part 1 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "41"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(part1.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "41"); - - //Check with service that upload is still in progress - UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertTrue(info.isUploadInProgress()); - assertThat(info.getLength(), is(69L)); - assertThat(info.getOffset(), is(41L)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Verify that we cannot download an in-progress upload - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(422); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertThat(servletResponse.getContentAsString(), is("")); - - //Upload part 2 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "28"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, "41"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(part2.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "69"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Check with HEAD request upload is complete - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "69"); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "69"); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Get upload info from service - info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertFalse(info.isUploadInProgress()); - assertThat(info.getLength(), is(69L)); - assertThat(info.getOffset(), is(69L)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); - - //Get uploaded bytes from service - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("This is the first part of my test upload and this is the second part.")); - } - } - - @Test - public void testProcessUploadDeferredLength() throws Exception { - String part1 = "When sending this part, we don't know the length and "; - String part2 = "when sending this part, we know the length but the upload is not complete. "; - String part3 = "Finally when sending the third part, the upload is complete."; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - Long expirationTimestampBefore = - Long.parseLong(String.valueOf( - mockDateFormat.parse(servletResponse.getHeader(HttpHeader.UPLOAD_EXPIRES)).getTime())); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload part 1 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part1.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part1.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Check with service that upload is still in progress - UploadInfo info = tusFileUploadService.getUploadInfo(location, null); - assertTrue(info.isUploadInProgress()); - assertThat(info.getLength(), is(nullValue())); - assertThat(info.getOffset(), is((long) part1.getBytes().length)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Check with HEAD request length is still not known - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_DEFER_LENGTH, "1"); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Upload part 2 bytes with length - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part2.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, part1.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, (part1 + part2 + part3).getBytes().length); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part2.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2).getBytes().length); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Check with HEAD request length is known - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2).getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "" + (part1 + part2 + part3).getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - info = tusFileUploadService.getUploadInfo(location, null); - assertTrue(info.isUploadInProgress()); - assertThat(info.getLength(), is((long) (part1 + part2 + part3).getBytes().length)); - - //check that expiration timestamp was updated - assertThat(info.getExpirationTimestamp(), greaterThan(expirationTimestampBefore)); - - //Upload part 3 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part3.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, (part1 + part2).getBytes().length); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part3.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2 + part3).getBytes().length); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Get upload info from service - info = tusFileUploadService.getUploadInfo(location, null); - assertFalse(info.isUploadInProgress()); - assertThat(info.getLength(), is((long) (part1 + part2 + part3).getBytes().length)); - assertThat(info.getOffset(), is((long) (part1 + part2 + part3).getBytes().length)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - - //Get uploaded bytes from service - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, null)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("When sending this part, we don't know the length and " + - "when sending this part, we know the length but the upload is not complete. " + - "Finally when sending the third part, the upload is complete.")); - } - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Download the upload - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, null); - assertResponseStatus(HttpServletResponse.SC_OK); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + (part1 + part2 + part3).getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertThat(servletResponse.getContentAsString(), is( - "When sending this part, we don't know the length and " + - "when sending this part, we know the length but the upload is not complete. " + - "Finally when sending the third part, the upload is complete.")); - - } - - @Test - public void testProcessUploadInvalidChecksumSecondPart() throws Exception { - String part1 = "29\r\nThis is the first part of my test upload " + - "\r\n0\r\nUPLOAD-CHECKSUM: sha1 n5RQbRwM6UVAD+9iuHEmnN6HCGQ="; - String part2 = "1C\r\nand this is the second part." + - "\r\n0\r\nupload-checksum: sha1 invalid"; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "69"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Upload part 1 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "41"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(part1.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "41"); - - Long expirationTimestampBefore = - Long.parseLong(String.valueOf( - mockDateFormat.parse(servletResponse.getHeader(HttpHeader.UPLOAD_EXPIRES)).getTime())); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Upload part 2 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "28"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, "41"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(part2.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - - //We expect the server to return a checksum mismatch error - assertResponseStatus(460); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - - //Check that upload info is still from the first patch - UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertTrue(info.isUploadInProgress()); - assertThat(info.getLength(), is(69L)); - assertThat(info.getOffset(), is(41L)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - - //check that expiration timestamp was updated - assertThat(info.getExpirationTimestamp(), greaterThan(expirationTimestampBefore)); - - //We only stored the first valid part - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("This is the first part of my test upload ")); - } - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Terminate our in progress upload - reset(); - servletRequest.setMethod("DELETE"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - - //We expect the server to return a no content code to indicate successful deletion - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Check that the upload is really gone - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - } - - @Test - public void testCleanupExpiredUpload() throws Exception { - //Set the expiration period to 500 ms - tusFileUploadService.withUploadExpirationPeriod(500L); - - String part1 = "This is the first part of my test upload"; - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part1.getBytes().length + 20L); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload part 1 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part1.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part1.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); - - //Check with service that upload is still in progress - UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertTrue(info.isUploadInProgress()); - assertThat(info.getLength(), is(part1.getBytes().length + 20L)); - assertThat(info.getOffset(), is(Long.valueOf(part1.getBytes().length))); - - //Now wait until the upload expired and run the cleanup - Utils.sleep(1000L); - tusFileUploadService.cleanup(); - - //Check with HEAD request that the upload is gone - //If a Client does attempt to resume an upload which has since been removed by the Server, - //the Server SHOULD respond with the404 Not Found or 410 Gone status. - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + // Get uploaded bytes from service + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is("This is my test upload content")); } - @Test - public void testConcatenationCompleted() throws Exception { - String part1 = "29\r\nThis is the first part of my test upload " + - "\r\n0\r\nUpload-Checksum: sha1 n5RQbRwM6UVAD+9iuHEmnN6HCGQ="; - String part2 = "1C\r\nand this is the second part." + - "\r\n0\r\nUpload-Checksum: sha1 oNge323kGFKICxp+Me5xJgPvGEM="; - - //Create first upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "41"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location1 = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Upload part 1 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location1); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "41"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(part1.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "41"); - - //Make sure cleanup does not interfere with this test - tusFileUploadService.cleanup(); - - //Create the second upload - reset(); - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "28"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location2 = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload part 2 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location2); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "28"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, "0"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(part2.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "28"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Create the final concatenated upload - reset(); - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final ; " + location1 + " " + location2); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, - "filename d29ybGRfZG9taW5hdGlvbl9tYXBfY29uY2F0ZW5hdGVkLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Check with HEAD request upload is complete - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "69"); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "69"); - assertResponseHeader(HttpHeader.UPLOAD_CONCAT, "final ; " + location1 + " " + location2); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, - "filename d29ybGRfZG9taW5hdGlvbl9tYXBfY29uY2F0ZW5hdGVkLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Get upload info from service - UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertFalse(info.isUploadInProgress()); - assertThat(info.getLength(), is(69L)); - assertThat(info.getOffset(), is(69L)); - assertThat(info.isUploadInProgress(), is(false)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_map_concatenated.pdf") - ) - ); - assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); - - //Download the upload - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_OK); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "69"); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, - "filename d29ybGRfZG9taW5hdGlvbl9tYXBfY29uY2F0ZW5hdGVkLnBkZg=="); - assertThat(servletResponse.getContentAsString(), - is("This is the first part of my test upload and this is the second part.")); - - //Get uploaded bytes from service - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("This is the first part of my test upload and this is the second part.")); - } + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Download the upload + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + uploadContent.getBytes().length); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_OK); + assertThat(servletResponse.getContentAsString(), is("This is my test upload content")); + + // Pretend that we processed the upload and that we can remove it + tusFileUploadService.deleteUpload(location, OWNER_KEY); + + // Check that the upload is really gone + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testTerminateViaHttpRequest() throws Exception { + String uploadContent = "This is my terminated test upload"; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(uploadContent.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Download the upload to make sure it was uploaded correctly + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + uploadContent.getBytes().length); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_OK); + assertThat(servletResponse.getContentAsString(), is("This is my terminated test upload")); + + // Terminate the upload so that the server can remove it + reset(); + servletRequest.setMethod("DELETE"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Check that the upload is really gone + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testProcessUploadTwoParts() throws Exception { + String part1 = + "29\r\nThis is the first part of my test upload " + + "\r\n0\r\nUpload-Checksum: sha1 n5RQbRwM6UVAD+9iuHEmnN6HCGQ="; + String part2 = + "1C\r\nand this is the second part." + + "\r\n0\r\nUpload-Checksum: sha1 oNge323kGFKICxp+Me5xJgPvGEM="; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "69"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Upload part 1 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "41"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(part1.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "41"); + + // Check with service that upload is still in progress + UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertTrue(info.isUploadInProgress()); + assertThat(info.getLength(), is(69L)); + assertThat(info.getOffset(), is(41L)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Verify that we cannot download an in-progress upload + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(422); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertThat(servletResponse.getContentAsString(), is("")); + + // Upload part 2 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "28"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, "41"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(part2.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "69"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Check with HEAD request upload is complete + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "69"); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "69"); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Get upload info from service + info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertFalse(info.isUploadInProgress()); + assertThat(info.getLength(), is(69L)); + assertThat(info.getOffset(), is(69L)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); + + // Get uploaded bytes from service + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is("This is the first part of my test upload and this is the second part.")); } - - @Test - public void testConcatenationUnfinished() throws Exception { - String part1 = "When sending this part, the final upload was already created. "; - String part2 = "This is the second part of our concatenated upload. "; - String part3 = "Finally when sending the third part, the final upload is complete."; - - //Create upload part 1 - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename cGFydDEucGRm"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location1 = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - reset(); - //Create upload part 2 - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename cGFydDIucGRm"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location2 = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - reset(); - //Create upload part 3 - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename cGFydDMucGRm"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location3 = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload part 2 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location2); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part2.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part2.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part2.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part2.getBytes().length); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - reset(); - //Create final upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;" + location1 + " " + location2 + " " + location3); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String locationFinal = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Check with HEAD request that length of final upload is undefined - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(locationFinal); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNull(HttpHeader.UPLOAD_OFFSET); - assertResponseHeaderNull(HttpHeader.UPLOAD_LENGTH); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); - assertResponseHeader(HttpHeader.UPLOAD_CONCAT, "final;" + location1 + " " + location2 + " " + location3); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Verify that we cannot download an unfinished final upload - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(locationFinal); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseStatus(422); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertThat(servletResponse.getContentAsString(), is("")); - - //Upload part 1 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location1); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part1.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part1.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part1.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Upload part 3 bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location3); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part3.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part3.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(part3.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part3.getBytes().length); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Check with HEAD request length of final upload is known - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(locationFinal); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2 + part3).getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "" + (part1 + part2 + part3).getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); - assertResponseHeader(HttpHeader.UPLOAD_CONCAT, "final;" + location1 + " " + location2 + " " + location3); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Download the upload - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(locationFinal); - - tusFileUploadService.process(servletRequest, servletResponse, null); - assertResponseStatus(HttpServletResponse.SC_OK); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + (part1 + part2 + part3).getBytes().length); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); - assertThat(servletResponse.getContentAsString(), is( - "When sending this part, the final upload was already created. " + - "This is the second part of our concatenated upload. " + - "Finally when sending the third part, the final upload is complete.")); - - //Get uploaded bytes from service - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(locationFinal, null)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("When sending this part, the final upload was already created. " + - "This is the second part of our concatenated upload. " + - "Finally when sending the third part, the final upload is complete.")); - } - } - - @Test - public void testChunkedDecodingDisabledAndRegexUploadURI() throws Exception { - String chunkedContent = "1B;test=value\r\nThis upload looks chunked, \r\n" - + "D\r\nbut it's not!\r\n" - + "\r\n0\r\n"; - - //Create service without chunked decoding - tusFileUploadService = new TusFileUploadService() - .withUploadURI("/users/[0-9]+/files/upload") - .withStoragePath(storagePath.toAbsolutePath().toString()) - .withDownloadFeature(); - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI("/users/98765/files/upload"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "67"); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNull(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = servletResponse.getHeader(HttpHeader.LOCATION); - - //Upload content - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "67"); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(chunkedContent.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNull(HttpHeader.UPLOAD_EXPIRES); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "67"); - - //Check with HEAD request upload is complete - reset(); - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "67"); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "67"); - assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); - assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Get upload info from service - UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); - assertFalse(info.isUploadInProgress()); - assertThat(info.getLength(), is(67L)); - assertThat(info.getOffset(), is(67L)); - assertThat(info.getMetadata(), allOf( - hasSize(1), - hasEntry("filename", "world_domination_plan.pdf") - ) - ); - - //Get uploaded bytes from service - try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("1B;test=value\r\nThis upload looks chunked, \r\n" - + "D\r\nbut it's not!\r\n" - + "\r\n0\r\n")); - } + } + + @Test + public void testProcessUploadDeferredLength() throws Exception { + String part1 = "When sending this part, we don't know the length and "; + String part2 = "when sending this part, we know the length but the upload is not complete. "; + String part3 = "Finally when sending the third part, the upload is complete."; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + Long expirationTimestampBefore = + Long.parseLong( + String.valueOf( + mockDateFormat + .parse(servletResponse.getHeader(HttpHeader.UPLOAD_EXPIRES)) + .getTime())); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload part 1 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part1.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part1.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Check with service that upload is still in progress + UploadInfo info = tusFileUploadService.getUploadInfo(location, null); + assertTrue(info.isUploadInProgress()); + assertThat(info.getLength(), is(nullValue())); + assertThat(info.getOffset(), is((long) part1.getBytes().length)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Check with HEAD request length is still not known + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); + assertResponseHeader(HttpHeader.UPLOAD_DEFER_LENGTH, "1"); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Upload part 2 bytes with length + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part2.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, part1.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, (part1 + part2 + part3).getBytes().length); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part2.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2).getBytes().length); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Check with HEAD request length is known + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2).getBytes().length); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "" + (part1 + part2 + part3).getBytes().length); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + info = tusFileUploadService.getUploadInfo(location, null); + assertTrue(info.isUploadInProgress()); + assertThat(info.getLength(), is((long) (part1 + part2 + part3).getBytes().length)); + + // check that expiration timestamp was updated + assertThat(info.getExpirationTimestamp(), greaterThan(expirationTimestampBefore)); + + // Upload part 3 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part3.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, (part1 + part2).getBytes().length); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part3.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2 + part3).getBytes().length); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Get upload info from service + info = tusFileUploadService.getUploadInfo(location, null); + assertFalse(info.isUploadInProgress()); + assertThat(info.getLength(), is((long) (part1 + part2 + part3).getBytes().length)); + assertThat(info.getOffset(), is((long) (part1 + part2 + part3).getBytes().length)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + + // Get uploaded bytes from service + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, null)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is( + "When sending this part, we don't know the length and " + + "when sending this part, we know the length but the upload is not complete. " + + "Finally when sending the third part, the upload is complete.")); } - @Test - public void testOptions() throws Exception { - //Do options request and check response headers - servletRequest.setMethod("OPTIONS"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_MAX_SIZE, "1073741824"); - assertResponseHeader(HttpHeader.TUS_CHECKSUM_ALGORITHM, "md5", "sha1", "sha256", "sha384", "sha512"); - assertResponseHeader(HttpHeader.TUS_EXTENSION, "creation", "creation-defer-length", "checksum", - "checksum-trailer", "termination", "download", "expiration", - "concatenation", "concatenation-unfinished"); + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Download the upload + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, null); + assertResponseStatus(HttpServletResponse.SC_OK); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + (part1 + part2 + part3).getBytes().length); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertThat( + servletResponse.getContentAsString(), + is( + "When sending this part, we don't know the length and " + + "when sending this part, we know the length but the upload is not complete. " + + "Finally when sending the third part, the upload is complete.")); + } + + @Test + public void testProcessUploadInvalidChecksumSecondPart() throws Exception { + String part1 = + "29\r\nThis is the first part of my test upload " + + "\r\n0\r\nUPLOAD-CHECKSUM: sha1 n5RQbRwM6UVAD+9iuHEmnN6HCGQ="; + String part2 = "1C\r\nand this is the second part." + "\r\n0\r\nupload-checksum: sha1 invalid"; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "69"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Upload part 1 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "41"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(part1.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "41"); + + Long expirationTimestampBefore = + Long.parseLong( + String.valueOf( + mockDateFormat + .parse(servletResponse.getHeader(HttpHeader.UPLOAD_EXPIRES)) + .getTime())); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Upload part 2 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "28"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, "41"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(part2.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + + // We expect the server to return a checksum mismatch error + assertResponseStatus(460); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + + // Check that upload info is still from the first patch + UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertTrue(info.isUploadInProgress()); + assertThat(info.getLength(), is(69L)); + assertThat(info.getOffset(), is(41L)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + + // check that expiration timestamp was updated + assertThat(info.getExpirationTimestamp(), greaterThan(expirationTimestampBefore)); + + // We only stored the first valid part + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is("This is the first part of my test upload ")); } - @Test - public void testHeadOnNonExistingUpload() throws Exception { - servletRequest.setMethod("HEAD"); - servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Terminate our in progress upload + reset(); + servletRequest.setMethod("DELETE"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + + // We expect the server to return a no content code to indicate successful deletion + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Check that the upload is really gone + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testCleanupExpiredUpload() throws Exception { + // Set the expiration period to 500 ms + tusFileUploadService.withUploadExpirationPeriod(500L); + + String part1 = "This is the first part of my test upload"; + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part1.getBytes().length + 20L); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload part 1 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part1.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part1.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); + + // Check with service that upload is still in progress + UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertTrue(info.isUploadInProgress()); + assertThat(info.getLength(), is(part1.getBytes().length + 20L)); + assertThat(info.getOffset(), is(Long.valueOf(part1.getBytes().length))); + + // Now wait until the upload expired and run the cleanup + Utils.sleep(1000L); + tusFileUploadService.cleanup(); + + // Check with HEAD request that the upload is gone + // If a Client does attempt to resume an upload which has since been removed by the Server, + // the Server SHOULD respond with the404 Not Found or 410 Gone status. + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + } + + @Test + public void testConcatenationCompleted() throws Exception { + String part1 = + "29\r\nThis is the first part of my test upload " + + "\r\n0\r\nUpload-Checksum: sha1 n5RQbRwM6UVAD+9iuHEmnN6HCGQ="; + String part2 = + "1C\r\nand this is the second part." + + "\r\n0\r\nUpload-Checksum: sha1 oNge323kGFKICxp+Me5xJgPvGEM="; + + // Create first upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "41"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location1 = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Upload part 1 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location1); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "41"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(part1.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "41"); + + // Make sure cleanup does not interfere with this test + tusFileUploadService.cleanup(); + + // Create the second upload + reset(); + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "28"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location2 = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload part 2 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location2); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "28"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, "0"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(part2.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "28"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Create the final concatenated upload + reset(); + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final ; " + location1 + " " + location2); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, + "filename d29ybGRfZG9taW5hdGlvbl9tYXBfY29uY2F0ZW5hdGVkLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Check with HEAD request upload is complete + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "69"); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "69"); + assertResponseHeader(HttpHeader.UPLOAD_CONCAT, "final ; " + location1 + " " + location2); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, + "filename d29ybGRfZG9taW5hdGlvbl9tYXBfY29uY2F0ZW5hdGVkLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Get upload info from service + UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertFalse(info.isUploadInProgress()); + assertThat(info.getLength(), is(69L)); + assertThat(info.getOffset(), is(69L)); + assertThat(info.isUploadInProgress(), is(false)); + assertThat( + info.getMetadata(), + allOf(hasSize(1), hasEntry("filename", "world_domination_map_concatenated.pdf"))); + assertThat(info.getCreatorIpAddresses(), is("10.0.2.1, 123.231.12.4, 192.168.1.1")); + + // Download the upload + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_OK); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "69"); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, + "filename d29ybGRfZG9taW5hdGlvbl9tYXBfY29uY2F0ZW5hdGVkLnBkZg=="); + assertThat( + servletResponse.getContentAsString(), + is("This is the first part of my test upload and this is the second part.")); + + // Get uploaded bytes from service + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is("This is the first part of my test upload and this is the second part.")); } - - @Test - public void testInvalidTusResumable() throws Exception { - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_PRECONDITION_FAILED); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - } - - @Test - public void testMaxUploadLengthExceeded() throws Exception { - tusFileUploadService.withMaxUploadSize(10L); - - String uploadContent = "This is upload is too long"; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testConcatenationUnfinished() throws Exception { + String part1 = "When sending this part, the final upload was already created. "; + String part2 = "This is the second part of our concatenated upload. "; + String part3 = "Finally when sending the third part, the final upload is complete."; + + // Create upload part 1 + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename cGFydDEucGRm"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location1 = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + reset(); + // Create upload part 2 + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename cGFydDIucGRm"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location2 = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + reset(); + // Create upload part 3 + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename cGFydDMucGRm"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location3 = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload part 2 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location2); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part2.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part2.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part2.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part2.getBytes().length); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + reset(); + // Create final upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_CONCAT, "final;" + location1 + " " + location2 + " " + location3); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String locationFinal = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Check with HEAD request that length of final upload is undefined + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(locationFinal); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNull(HttpHeader.UPLOAD_OFFSET); + assertResponseHeaderNull(HttpHeader.UPLOAD_LENGTH); + assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); + assertResponseHeader( + HttpHeader.UPLOAD_CONCAT, "final;" + location1 + " " + location2 + " " + location3); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Verify that we cannot download an unfinished final upload + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(locationFinal); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseStatus(422); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertThat(servletResponse.getContentAsString(), is("")); + + // Upload part 1 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location1); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part1.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part1.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part1.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part1.getBytes().length); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Upload part 3 bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location3); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, part3.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, part3.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(part3.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + part3.getBytes().length); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Check with HEAD request length of final upload is known + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(locationFinal); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + (part1 + part2 + part3).getBytes().length); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "" + (part1 + part2 + part3).getBytes().length); + assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); + assertResponseHeader( + HttpHeader.UPLOAD_CONCAT, "final;" + location1 + " " + location2 + " " + location3); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Download the upload + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(locationFinal); + + tusFileUploadService.process(servletRequest, servletResponse, null); + assertResponseStatus(HttpServletResponse.SC_OK); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + (part1 + part2 + part3).getBytes().length); + assertResponseHeader(HttpHeader.UPLOAD_METADATA, "filename ZmluYWwucGRm"); + assertThat( + servletResponse.getContentAsString(), + is( + "When sending this part, the final upload was already created. " + + "This is the second part of our concatenated upload. " + + "Finally when sending the third part, the final upload is complete.")); + + // Get uploaded bytes from service + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(locationFinal, null)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is( + "When sending this part, the final upload was already created. " + + "This is the second part of our concatenated upload. " + + "Finally when sending the third part, the final upload is complete.")); } + } - @Test - public void testInvalidMethods() throws Exception { - servletRequest.setMethod("PUT"); - servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - - reset(); - servletRequest.setMethod("CONNECT"); - servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - - reset(); - servletRequest.setMethod("TRACE"); - servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + @Test + public void testChunkedDecodingDisabledAndRegexUploadURI() throws Exception { + String chunkedContent = + "1B;test=value\r\nThis upload looks chunked, \r\n" + "D\r\nbut it's not!\r\n" + "\r\n0\r\n"; - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - - } - - protected void assertResponseHeader(final String header, final String value) { - assertThat(servletResponse.getHeader(header), is(value)); - } - - protected void assertResponseHeader(final String header, final String... values) { - assertThat(Arrays.asList(servletResponse.getHeader(header).split(",")), - containsInAnyOrder(values)); - } - - protected void assertResponseHeaderNotBlank(final String header) { - assertTrue(StringUtils.isNotBlank(servletResponse.getHeader(header))); - } - - protected void assertResponseHeaderNull(final String header) { - assertTrue(servletResponse.getHeader(header) == null); - } - - protected void assertResponseStatus(final int httpStatus) { - assertThat(servletResponse.getStatus(), is(httpStatus)); + // Create service without chunked decoding + tusFileUploadService = + new TusFileUploadService() + .withUploadURI("/users/[0-9]+/files/upload") + .withStoragePath(storagePath.toAbsolutePath().toString()) + .withDownloadFeature(); + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI("/users/98765/files/upload"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "67"); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNull(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = servletResponse.getHeader(HttpHeader.LOCATION); + + // Upload content + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, "67"); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(chunkedContent.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNull(HttpHeader.UPLOAD_EXPIRES); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "67"); + + // Check with HEAD request upload is complete + reset(); + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "67"); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "67"); + assertResponseHeaderNull(HttpHeader.UPLOAD_DEFER_LENGTH); + assertResponseHeader( + HttpHeader.UPLOAD_METADATA, "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Get upload info from service + UploadInfo info = tusFileUploadService.getUploadInfo(location, OWNER_KEY); + assertFalse(info.isUploadInProgress()); + assertThat(info.getLength(), is(67L)); + assertThat(info.getOffset(), is(67L)); + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + + // Get uploaded bytes from service + try (InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(location, OWNER_KEY)) { + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is( + "1B;test=value\r\nThis upload looks chunked, \r\n" + + "D\r\nbut it's not!\r\n" + + "\r\n0\r\n")); } - -} \ No newline at end of file + } + + @Test + public void testOptions() throws Exception { + // Do options request and check response headers + servletRequest.setMethod("OPTIONS"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_MAX_SIZE, "1073741824"); + assertResponseHeader( + HttpHeader.TUS_CHECKSUM_ALGORITHM, "md5", "sha1", "sha256", "sha384", "sha512"); + assertResponseHeader( + HttpHeader.TUS_EXTENSION, + "creation", + "creation-defer-length", + "checksum", + "checksum-trailer", + "termination", + "download", + "expiration", + "concatenation", + "concatenation-unfinished"); + } + + @Test + public void testHeadOnNonExistingUpload() throws Exception { + servletRequest.setMethod("HEAD"); + servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testInvalidTusResumable() throws Exception { + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_PRECONDITION_FAILED); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testMaxUploadLengthExceeded() throws Exception { + tusFileUploadService.withMaxUploadSize(10L); + + String uploadContent = "This is upload is too long"; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + @Test + public void testInvalidMethods() throws Exception { + servletRequest.setMethod("PUT"); + servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + + reset(); + servletRequest.setMethod("CONNECT"); + servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + + reset(); + servletRequest.setMethod("TRACE"); + servletRequest.setRequestURI(UPLOAD_URI + "/" + UUID.randomUUID()); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + } + + protected void assertResponseHeader(final String header, final String value) { + assertThat(servletResponse.getHeader(header), is(value)); + } + + protected void assertResponseHeader(final String header, final String... values) { + assertThat( + Arrays.asList(servletResponse.getHeader(header).split(",")), containsInAnyOrder(values)); + } + + protected void assertResponseHeaderNotBlank(final String header) { + assertTrue(StringUtils.isNotBlank(servletResponse.getHeader(header))); + } + + protected void assertResponseHeaderNull(final String header) { + assertTrue(servletResponse.getHeader(header) == null); + } + + protected void assertResponseStatus(final int httpStatus) { + assertThat(servletResponse.getStatus(), is(httpStatus)); + } +} diff --git a/src/test/java/me/desair/tus/server/ITTusFileUploadServiceCached.java b/src/test/java/me/desair/tus/server/ITTusFileUploadServiceCached.java index 2166aa3..eede1d8 100644 --- a/src/test/java/me/desair/tus/server/ITTusFileUploadServiceCached.java +++ b/src/test/java/me/desair/tus/server/ITTusFileUploadServiceCached.java @@ -4,7 +4,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.upload.TimeBasedUploadIdFactory; import me.desair.tus.server.upload.UploadLockingService; import me.desair.tus.server.upload.UploadStorageService; @@ -18,94 +17,95 @@ public class ITTusFileUploadServiceCached extends ITTusFileUploadService { - @Override - @Before - public void setUp() { - super.setUp(); - tusFileUploadService = tusFileUploadService - .withThreadLocalCache(true) - .withUploadIdFactory(new TimeBasedUploadIdFactory()); - } - - @Test - public void testProcessUploadDoubleCached() throws Exception { - String path = storagePath.toAbsolutePath().toString(); - UploadStorageService uploadStorageService = new DiskStorageService(path); - UploadLockingService uploadLockingService = new DiskLockingService(path); - - ThreadLocalCachedStorageAndLockingService service2 = - new ThreadLocalCachedStorageAndLockingService( - uploadStorageService, - uploadLockingService); - - service2.setUploadConcatenationService(new VirtualConcatenationService(service2)); - - tusFileUploadService.withUploadStorageService(service2); - tusFileUploadService.withUploadLockingService(service2); - - assertThat(service2.getUploadURI(), is(UPLOAD_URI)); - assertThat(uploadStorageService.getUploadURI(), is(UPLOAD_URI)); - - testConcatenationCompleted(); - } - - @Test - public void testCachedUploadDifferentKey() throws Exception { - String uploadContent = "This is an upload of someone else"; - - //Create upload - servletRequest.setMethod("POST"); - servletRequest.setRequestURI(UPLOAD_URI); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeaderNotBlank(HttpHeader.LOCATION); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - String location = UPLOAD_URI + - StringUtils.substringAfter(servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); - - //Upload bytes - reset(); - servletRequest.setMethod("PATCH"); - servletRequest.setRequestURI(location); - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, uploadContent.getBytes().length); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - servletRequest.setContent(uploadContent.getBytes()); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); - assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - - //Download the upload to check content - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + uploadContent.getBytes().length); - assertResponseStatus(HttpServletResponse.SC_OK); - assertThat(servletResponse.getContentAsString(), is("This is an upload of someone else")); - - //Try to download the upload under a different key - reset(); - servletRequest.setMethod("GET"); - servletRequest.setRequestURI(location); - - tusFileUploadService.process(servletRequest, servletResponse, "ALTER-EGO"); - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); - assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); - } + @Override + @Before + public void setUp() { + super.setUp(); + tusFileUploadService = + tusFileUploadService + .withThreadLocalCache(true) + .withUploadIdFactory(new TimeBasedUploadIdFactory()); + } + + @Test + public void testProcessUploadDoubleCached() throws Exception { + String path = storagePath.toAbsolutePath().toString(); + UploadStorageService uploadStorageService = new DiskStorageService(path); + UploadLockingService uploadLockingService = new DiskLockingService(path); + + ThreadLocalCachedStorageAndLockingService service2 = + new ThreadLocalCachedStorageAndLockingService(uploadStorageService, uploadLockingService); + + service2.setUploadConcatenationService(new VirtualConcatenationService(service2)); + + tusFileUploadService.withUploadStorageService(service2); + tusFileUploadService.withUploadLockingService(service2); + + assertThat(service2.getUploadURI(), is(UPLOAD_URI)); + assertThat(uploadStorageService.getUploadURI(), is(UPLOAD_URI)); + + testConcatenationCompleted(); + } + + @Test + public void testCachedUploadDifferentKey() throws Exception { + String uploadContent = "This is an upload of someone else"; + + // Create upload + servletRequest.setMethod("POST"); + servletRequest.setRequestURI(UPLOAD_URI); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeaderNotBlank(HttpHeader.LOCATION); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + String location = + UPLOAD_URI + + StringUtils.substringAfter( + servletResponse.getHeader(HttpHeader.LOCATION), UPLOAD_URI); + + // Upload bytes + reset(); + servletRequest.setMethod("PATCH"); + servletRequest.setRequestURI(location); + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/offset+octet-stream"); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, uploadContent.getBytes().length); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + servletRequest.setContent(uploadContent.getBytes()); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "" + uploadContent.getBytes().length); + assertResponseHeaderNotBlank(HttpHeader.UPLOAD_EXPIRES); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + + // Download the upload to check content + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "" + uploadContent.getBytes().length); + assertResponseStatus(HttpServletResponse.SC_OK); + assertThat(servletResponse.getContentAsString(), is("This is an upload of someone else")); + + // Try to download the upload under a different key + reset(); + servletRequest.setMethod("GET"); + servletRequest.setRequestURI(location); + + tusFileUploadService.process(servletRequest, servletResponse, "ALTER-EGO"); + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.CONTENT_LENGTH, "0"); + assertResponseStatus(HttpServletResponse.SC_NOT_FOUND); + } } diff --git a/src/test/java/me/desair/tus/server/checksum/ChecksumAlgorithmTest.java b/src/test/java/me/desair/tus/server/checksum/ChecksumAlgorithmTest.java index ec12f70..c4dce87 100644 --- a/src/test/java/me/desair/tus/server/checksum/ChecksumAlgorithmTest.java +++ b/src/test/java/me/desair/tus/server/checksum/ChecksumAlgorithmTest.java @@ -7,41 +7,46 @@ public class ChecksumAlgorithmTest { - @Test - public void getMessageDigest() throws Exception { - assertNotNull(ChecksumAlgorithm.MD5.getMessageDigest()); - assertNotNull(ChecksumAlgorithm.SHA1.getMessageDigest()); - assertNotNull(ChecksumAlgorithm.SHA256.getMessageDigest()); - assertNotNull(ChecksumAlgorithm.SHA384.getMessageDigest()); - assertNotNull(ChecksumAlgorithm.SHA512.getMessageDigest()); - } + @Test + public void getMessageDigest() throws Exception { + assertNotNull(ChecksumAlgorithm.MD5.getMessageDigest()); + assertNotNull(ChecksumAlgorithm.SHA1.getMessageDigest()); + assertNotNull(ChecksumAlgorithm.SHA256.getMessageDigest()); + assertNotNull(ChecksumAlgorithm.SHA384.getMessageDigest()); + assertNotNull(ChecksumAlgorithm.SHA512.getMessageDigest()); + } - @Test - public void forTusName() throws Exception { - assertEquals(ChecksumAlgorithm.MD5, ChecksumAlgorithm.forTusName("md5")); - assertEquals(ChecksumAlgorithm.SHA1, ChecksumAlgorithm.forTusName("sha1")); - assertEquals(ChecksumAlgorithm.SHA256, ChecksumAlgorithm.forTusName("sha256")); - assertEquals(ChecksumAlgorithm.SHA384, ChecksumAlgorithm.forTusName("sha384")); - assertEquals(ChecksumAlgorithm.SHA512, ChecksumAlgorithm.forTusName("sha512")); - assertEquals(null, ChecksumAlgorithm.forTusName("test")); - } + @Test + public void forTusName() throws Exception { + assertEquals(ChecksumAlgorithm.MD5, ChecksumAlgorithm.forTusName("md5")); + assertEquals(ChecksumAlgorithm.SHA1, ChecksumAlgorithm.forTusName("sha1")); + assertEquals(ChecksumAlgorithm.SHA256, ChecksumAlgorithm.forTusName("sha256")); + assertEquals(ChecksumAlgorithm.SHA384, ChecksumAlgorithm.forTusName("sha384")); + assertEquals(ChecksumAlgorithm.SHA512, ChecksumAlgorithm.forTusName("sha512")); + assertEquals(null, ChecksumAlgorithm.forTusName("test")); + } - @Test - public void forUploadChecksumHeader() throws Exception { - assertEquals(ChecksumAlgorithm.MD5, ChecksumAlgorithm.forUploadChecksumHeader("md5 1234567890")); - assertEquals(ChecksumAlgorithm.SHA1, ChecksumAlgorithm.forUploadChecksumHeader("sha1 1234567890")); - assertEquals(ChecksumAlgorithm.SHA256, ChecksumAlgorithm.forUploadChecksumHeader("sha256 1234567890")); - assertEquals(ChecksumAlgorithm.SHA384, ChecksumAlgorithm.forUploadChecksumHeader("sha384 1234567890")); - assertEquals(ChecksumAlgorithm.SHA512, ChecksumAlgorithm.forUploadChecksumHeader("sha512 1234567890")); - assertEquals(null, ChecksumAlgorithm.forUploadChecksumHeader("test 1234567890")); - } + @Test + public void forUploadChecksumHeader() throws Exception { + assertEquals( + ChecksumAlgorithm.MD5, ChecksumAlgorithm.forUploadChecksumHeader("md5 1234567890")); + assertEquals( + ChecksumAlgorithm.SHA1, ChecksumAlgorithm.forUploadChecksumHeader("sha1 1234567890")); + assertEquals( + ChecksumAlgorithm.SHA256, ChecksumAlgorithm.forUploadChecksumHeader("sha256 1234567890")); + assertEquals( + ChecksumAlgorithm.SHA384, ChecksumAlgorithm.forUploadChecksumHeader("sha384 1234567890")); + assertEquals( + ChecksumAlgorithm.SHA512, ChecksumAlgorithm.forUploadChecksumHeader("sha512 1234567890")); + assertEquals(null, ChecksumAlgorithm.forUploadChecksumHeader("test 1234567890")); + } - @Test - public void testToString() throws Exception { - assertEquals("md5", ChecksumAlgorithm.MD5.toString()); - assertEquals("sha1", ChecksumAlgorithm.SHA1.toString()); - assertEquals("sha256", ChecksumAlgorithm.SHA256.toString()); - assertEquals("sha384", ChecksumAlgorithm.SHA384.toString()); - assertEquals("sha512", ChecksumAlgorithm.SHA512.toString()); - } -} \ No newline at end of file + @Test + public void testToString() throws Exception { + assertEquals("md5", ChecksumAlgorithm.MD5.toString()); + assertEquals("sha1", ChecksumAlgorithm.SHA1.toString()); + assertEquals("sha256", ChecksumAlgorithm.SHA256.toString()); + assertEquals("sha384", ChecksumAlgorithm.SHA384.toString()); + assertEquals("sha512", ChecksumAlgorithm.SHA512.toString()); + } +} diff --git a/src/test/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandlerTest.java index 84f7fbd..382e344 100644 --- a/src/test/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/checksum/ChecksumOptionsRequestHandlerTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import java.util.Arrays; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.util.TusServletRequest; @@ -17,42 +16,47 @@ public class ChecksumOptionsRequestHandlerTest { - private ChecksumOptionsRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new ChecksumOptionsRequestHandler(); - } - - @Test - public void processListExtensions() throws Exception { - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), null, null); - - assertThat(Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), - containsInAnyOrder("checksum", "checksum-trailer")); - - assertThat(Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_CHECKSUM_ALGORITHM).split(",")), - containsInAnyOrder("md5", "sha1", "sha256", "sha384", "sha512")); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private ChecksumOptionsRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new ChecksumOptionsRequestHandler(); + } + + @Test + public void processListExtensions() throws Exception { + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + null, + null); + + assertThat( + Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), + containsInAnyOrder("checksum", "checksum-trailer")); + + assertThat( + Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_CHECKSUM_ALGORITHM).split(",")), + containsInAnyOrder("md5", "sha1", "sha256", "sha384", "sha512")); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandlerTest.java b/src/test/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandlerTest.java index a20d338..26bd543 100644 --- a/src/test/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/checksum/ChecksumPatchRequestHandlerTest.java @@ -26,72 +26,71 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ChecksumPatchRequestHandlerTest { - private ChecksumPatchRequestHandler handler; - - @Mock - private TusServletRequest servletRequest; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() throws Exception { - handler = new ChecksumPatchRequestHandler(); - - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(true)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void testValidHeaderAndChecksum() throws Exception { - when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("sha1 1234567890"); - when(servletRequest.getCalculatedChecksum(ArgumentMatchers.any(ChecksumAlgorithm.class))) - .thenReturn("1234567890"); - when(servletRequest.hasCalculatedChecksum()).thenReturn(true); - - handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); - - verify(servletRequest, times(1)).getCalculatedChecksum(any(ChecksumAlgorithm.class)); - } - - @Test(expected = UploadChecksumMismatchException.class) - public void testValidHeaderAndInvalidChecksum() throws Exception { - when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("sha1 1234567890"); - when(servletRequest.getCalculatedChecksum(ArgumentMatchers.any(ChecksumAlgorithm.class))) - .thenReturn("0123456789"); - when(servletRequest.hasCalculatedChecksum()).thenReturn(true); - - handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); - } - - @Test - public void testNoHeader() throws Exception { - when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn(null); - - handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); - - verify(servletRequest, never()).getCalculatedChecksum(any(ChecksumAlgorithm.class)); - } - - @Test(expected = ChecksumAlgorithmNotSupportedException.class) - public void testInvalidHeader() throws Exception { - when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("test 1234567890"); - when(servletRequest.hasCalculatedChecksum()).thenReturn(true); - - handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); - } -} \ No newline at end of file + private ChecksumPatchRequestHandler handler; + + @Mock private TusServletRequest servletRequest; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() throws Exception { + handler = new ChecksumPatchRequestHandler(); + + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(true)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void testValidHeaderAndChecksum() throws Exception { + when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("sha1 1234567890"); + when(servletRequest.getCalculatedChecksum(ArgumentMatchers.any(ChecksumAlgorithm.class))) + .thenReturn("1234567890"); + when(servletRequest.hasCalculatedChecksum()).thenReturn(true); + + handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); + + verify(servletRequest, times(1)).getCalculatedChecksum(any(ChecksumAlgorithm.class)); + } + + @Test(expected = UploadChecksumMismatchException.class) + public void testValidHeaderAndInvalidChecksum() throws Exception { + when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("sha1 1234567890"); + when(servletRequest.getCalculatedChecksum(ArgumentMatchers.any(ChecksumAlgorithm.class))) + .thenReturn("0123456789"); + when(servletRequest.hasCalculatedChecksum()).thenReturn(true); + + handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); + } + + @Test + public void testNoHeader() throws Exception { + when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn(null); + + handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); + + verify(servletRequest, never()).getCalculatedChecksum(any(ChecksumAlgorithm.class)); + } + + @Test(expected = ChecksumAlgorithmNotSupportedException.class) + public void testInvalidHeader() throws Exception { + when(servletRequest.getHeader(HttpHeader.UPLOAD_CHECKSUM)).thenReturn("test 1234567890"); + when(servletRequest.hasCalculatedChecksum()).thenReturn(true); + + handler.process(HttpMethod.PATCH, servletRequest, null, uploadStorageService, null); + } +} diff --git a/src/test/java/me/desair/tus/server/checksum/ITChecksumExtension.java b/src/test/java/me/desair/tus/server/checksum/ITChecksumExtension.java index 46b6153..e99ad3f 100644 --- a/src/test/java/me/desair/tus/server/checksum/ITChecksumExtension.java +++ b/src/test/java/me/desair/tus/server/checksum/ITChecksumExtension.java @@ -17,104 +17,107 @@ public class ITChecksumExtension extends AbstractTusExtensionIntegrationTest { - @Before - public void setUp() throws Exception { - servletRequest = spy(new MockHttpServletRequest()); - servletResponse = new MockHttpServletResponse(); - tusFeature = new ChecksumExtension(); - uploadInfo = null; + @Before + public void setUp() throws Exception { + servletRequest = spy(new MockHttpServletRequest()); + servletResponse = new MockHttpServletResponse(); + tusFeature = new ChecksumExtension(); + uploadInfo = null; + } + + @Test + public void testOptions() throws Exception { + setRequestHeaders(); + + executeCall(HttpMethod.OPTIONS, false); + + assertResponseHeader(HttpHeader.TUS_EXTENSION, "checksum", "checksum-trailer"); + assertResponseHeader( + HttpHeader.TUS_CHECKSUM_ALGORITHM, "md5", "sha1", "sha256", "sha384", "sha512"); + } + + @Test(expected = ChecksumAlgorithmNotSupportedException.class) + public void testInvalidAlgorithm() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "test 1234567890"); + servletRequest.setContent("Test content".getBytes()); + + executeCall(HttpMethod.PATCH, false); + } + + @Test + public void testValidChecksumTrailerHeader() throws Exception { + String content = + "8\r\n" + + "Mozilla \r\n" + + "A\r\n" + + "Developer \r\n" + + "7\r\n" + + "Network\r\n" + + "0\r\n" + + "Upload-Checksum: sha1 zYR9iS5Rya+WoH1fEyfKqqdPWWE=\r\n" + + "\r\n"; + + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(content.getBytes()); + + try { + executeCall(HttpMethod.PATCH, true); + } catch (Exception ex) { + fail(); } + } - @Test - public void testOptions() throws Exception { - setRequestHeaders(); + @Test + public void testValidChecksumNormalHeader() throws Exception { + String content = "Mozilla Developer Network"; - executeCall(HttpMethod.OPTIONS, false); + servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 zYR9iS5Rya+WoH1fEyfKqqdPWWE="); + servletRequest.setContent(content.getBytes()); - assertResponseHeader(HttpHeader.TUS_EXTENSION, "checksum", "checksum-trailer"); - assertResponseHeader(HttpHeader.TUS_CHECKSUM_ALGORITHM, "md5", "sha1", "sha256", "sha384", "sha512"); - } - - @Test(expected = ChecksumAlgorithmNotSupportedException.class) - public void testInvalidAlgorithm() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "test 1234567890"); - servletRequest.setContent("Test content".getBytes()); - - executeCall(HttpMethod.PATCH, false); - } - - @Test - public void testValidChecksumTrailerHeader() throws Exception { - String content = "8\r\n" + - "Mozilla \r\n" + - "A\r\n" + - "Developer \r\n" + - "7\r\n" + - "Network\r\n" + - "0\r\n" + - "Upload-Checksum: sha1 zYR9iS5Rya+WoH1fEyfKqqdPWWE=\r\n" + - "\r\n"; - - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(content.getBytes()); - - try { - executeCall(HttpMethod.PATCH, true); - } catch (Exception ex) { - fail(); - } - } + executeCall(HttpMethod.PATCH, true); - @Test - public void testValidChecksumNormalHeader() throws Exception { - String content = "Mozilla Developer Network"; + verify(servletRequest, atLeastOnce()).getHeader(HttpHeader.UPLOAD_CHECKSUM); + } - servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 zYR9iS5Rya+WoH1fEyfKqqdPWWE="); - servletRequest.setContent(content.getBytes()); + @Test(expected = UploadChecksumMismatchException.class) + public void testInvalidChecksumTrailerHeader() throws Exception { + String content = + "8\r\n" + + "Mozilla \r\n" + + "A\r\n" + + "Developer \r\n" + + "7\r\n" + + "Network\r\n" + + "0\r\n" + + "Upload-Checksum: sha1 zYR9iS5Rya+WoH1fEyfKqqdPWW=\r\n" + + "\r\n"; - executeCall(HttpMethod.PATCH, true); + servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); + servletRequest.setContent(content.getBytes()); - verify(servletRequest, atLeastOnce()).getHeader(HttpHeader.UPLOAD_CHECKSUM); - } + executeCall(HttpMethod.PATCH, true); + } - @Test(expected = UploadChecksumMismatchException.class) - public void testInvalidChecksumTrailerHeader() throws Exception { - String content = "8\r\n" + - "Mozilla \r\n" + - "A\r\n" + - "Developer \r\n" + - "7\r\n" + - "Network\r\n" + - "0\r\n" + - "Upload-Checksum: sha1 zYR9iS5Rya+WoH1fEyfKqqdPWW=\r\n" + - "\r\n"; - - servletRequest.addHeader(HttpHeader.TRANSFER_ENCODING, "chunked"); - servletRequest.setContent(content.getBytes()); - - executeCall(HttpMethod.PATCH, true); - } + @Test(expected = UploadChecksumMismatchException.class) + public void testInvalidChecksumNormalHeader() throws Exception { + String content = "Mozilla Developer Network"; - @Test(expected = UploadChecksumMismatchException.class) - public void testInvalidChecksumNormalHeader() throws Exception { - String content = "Mozilla Developer Network"; + servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 zYR9iS5Rya+WoH1fEyfKqqdPWW="); + servletRequest.setContent(content.getBytes()); - servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 zYR9iS5Rya+WoH1fEyfKqqdPWW="); - servletRequest.setContent(content.getBytes()); - - executeCall(HttpMethod.PATCH, true); - } + executeCall(HttpMethod.PATCH, true); + } - @Test - public void testNoChecksum() throws Exception { - String content = "Mozilla Developer Network"; + @Test + public void testNoChecksum() throws Exception { + String content = "Mozilla Developer Network"; - servletRequest.setContent(content.getBytes()); + servletRequest.setContent(content.getBytes()); - try { - executeCall(HttpMethod.PATCH, true); - } catch (Exception ex) { - fail(); - } + try { + executeCall(HttpMethod.PATCH, true); + } catch (Exception ex) { + fail(); } -} \ No newline at end of file + } +} diff --git a/src/test/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidatorTest.java b/src/test/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidatorTest.java index 9c69a27..63f1dff 100644 --- a/src/test/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidatorTest.java +++ b/src/test/java/me/desair/tus/server/checksum/validation/ChecksumAlgorithmValidatorTest.java @@ -21,56 +21,54 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ChecksumAlgorithmValidatorTest { - private ChecksumAlgorithmValidator validator; - - private MockHttpServletRequest servletRequest; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = spy(new MockHttpServletRequest()); - validator = new ChecksumAlgorithmValidator(); - } - - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(false)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(false)); - } - - @Test - public void testValid() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 1234567890"); - - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - - verify(servletRequest, times(1)).getHeader(HttpHeader.UPLOAD_CHECKSUM); + private ChecksumAlgorithmValidator validator; + + private MockHttpServletRequest servletRequest; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = spy(new MockHttpServletRequest()); + validator = new ChecksumAlgorithmValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(false)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void testValid() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "sha1 1234567890"); + + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + + verify(servletRequest, times(1)).getHeader(HttpHeader.UPLOAD_CHECKSUM); + } + + @Test + public void testNoHeader() throws Exception { + // servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, null); + + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } + } - @Test - public void testNoHeader() throws Exception { - //servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, null); - - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - } - - @Test(expected = ChecksumAlgorithmNotSupportedException.class) - public void testInvalidHeader() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "test 1234567890"); - - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } + @Test(expected = ChecksumAlgorithmNotSupportedException.class) + public void testInvalidHeader() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CHECKSUM, "test 1234567890"); -} \ No newline at end of file + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } +} diff --git a/src/test/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandlerTest.java b/src/test/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandlerTest.java index a64b3ec..cff0870 100644 --- a/src/test/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/concatenation/ConcatenationHeadRequestHandlerTest.java @@ -11,7 +11,6 @@ import static org.mockito.Mockito.when; import java.util.UUID; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadId; @@ -32,115 +31,129 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ConcatenationHeadRequestHandlerTest { - private ConcatenationHeadRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Mock - private UploadConcatenationService concatenationService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new ConcatenationHeadRequestHandler(); - when(uploadStorageService.getUploadConcatenationService()).thenReturn(concatenationService); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(true)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void testRegularUpload() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - info1.setUploadConcatHeaderValue("Impossible"); - info1.setUploadType(UploadType.REGULAR); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - servletRequest.setRequestURI(info1.getId().toString()); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is(nullValue())); - } - - @Test - public void testPartialUpload() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - info1.setUploadConcatHeaderValue("partial"); - info1.setUploadType(UploadType.PARTIAL); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - servletRequest.setRequestURI(info1.getId().toString()); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is("partial")); - } - - @Test - public void testConcatenatedUploadWithLength() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - info1.setUploadConcatHeaderValue("final; 123 456"); - info1.setLength(10L); - info1.setOffset(10L); - info1.setUploadType(UploadType.CONCATENATED); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - servletRequest.setRequestURI(info1.getId().toString()); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is("final; 123 456")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is("10")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("10")); - - verify(concatenationService, never()).merge(info1); - } - - @Test - public void testConcatenatedUploadWithoutLength() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - info1.setUploadConcatHeaderValue("final; 123 456"); - info1.setLength(10L); - info1.setOffset(8L); - info1.setUploadType(UploadType.CONCATENATED); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - servletRequest.setRequestURI(info1.getId().toString()); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is("final; 123 456")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is("10")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is(nullValue())); - - verify(concatenationService, times(1)).merge(info1); - } -} \ No newline at end of file + private ConcatenationHeadRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Mock private UploadConcatenationService concatenationService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new ConcatenationHeadRequestHandler(); + when(uploadStorageService.getUploadConcatenationService()).thenReturn(concatenationService); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(true)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void testRegularUpload() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + info1.setUploadConcatHeaderValue("Impossible"); + info1.setUploadType(UploadType.REGULAR); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + servletRequest.setRequestURI(info1.getId().toString()); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is(nullValue())); + } + + @Test + public void testPartialUpload() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + info1.setUploadConcatHeaderValue("partial"); + info1.setUploadType(UploadType.PARTIAL); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + servletRequest.setRequestURI(info1.getId().toString()); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is("partial")); + } + + @Test + public void testConcatenatedUploadWithLength() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + info1.setUploadConcatHeaderValue("final; 123 456"); + info1.setLength(10L); + info1.setOffset(10L); + info1.setUploadType(UploadType.CONCATENATED); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + servletRequest.setRequestURI(info1.getId().toString()); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is("final; 123 456")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is("10")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("10")); + + verify(concatenationService, never()).merge(info1); + } + + @Test + public void testConcatenatedUploadWithoutLength() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + info1.setUploadConcatHeaderValue("final; 123 456"); + info1.setLength(10L); + info1.setOffset(8L); + info1.setUploadType(UploadType.CONCATENATED); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + servletRequest.setRequestURI(info1.getId().toString()); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_CONCAT), is("final; 123 456")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is("10")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is(nullValue())); + + verify(concatenationService, times(1)).merge(info1); + } +} diff --git a/src/test/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandlerTest.java index 7de1e1a..c8c8933 100644 --- a/src/test/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/concatenation/ConcatenationOptionsRequestHandlerTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import java.util.Arrays; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.util.TusServletRequest; @@ -17,39 +16,43 @@ public class ConcatenationOptionsRequestHandlerTest { - private ConcatenationOptionsRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new ConcatenationOptionsRequestHandler(); - } - - @Test - public void processListExtensions() throws Exception { - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), null, null); - - assertThat(Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), - containsInAnyOrder("concatenation", "concatenation-unfinished")); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private ConcatenationOptionsRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new ConcatenationOptionsRequestHandler(); + } + + @Test + public void processListExtensions() throws Exception { + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + null, + null); + + assertThat( + Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), + containsInAnyOrder("concatenation", "concatenation-unfinished")); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandlerTest.java b/src/test/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandlerTest.java index 622efd1..473f226 100644 --- a/src/test/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/concatenation/ConcatenationPostRequestHandlerTest.java @@ -12,7 +12,6 @@ import static org.mockito.Mockito.when; import java.util.UUID; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadId; @@ -33,111 +32,128 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ConcatenationPostRequestHandlerTest { - private ConcatenationPostRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Mock - private UploadConcatenationService concatenationService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new ConcatenationPostRequestHandler(); - when(uploadStorageService.getUploadConcatenationService()).thenReturn(concatenationService); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(true)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void testRegularUpload() throws Exception { - TusServletResponse response = new TusServletResponse(this.servletResponse); - - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - response.setHeader(HttpHeader.LOCATION, info1.getId().toString()); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), response, uploadStorageService, null); - - assertThat(info1.getUploadType(), is(UploadType.REGULAR)); - assertThat(info1.getUploadConcatHeaderValue(), is(nullValue())); - - verify(uploadStorageService, times(1)).update(info1); - verify(concatenationService, never()).merge(info1); - } - - @Test - public void testPartialUpload() throws Exception { - TusServletResponse response = new TusServletResponse(this.servletResponse); - - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - response.setHeader(HttpHeader.LOCATION, info1.getId().toString()); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), response, uploadStorageService, null); - - assertThat(info1.getUploadType(), is(UploadType.PARTIAL)); - assertThat(info1.getUploadConcatHeaderValue(), is("partial")); - - verify(uploadStorageService, times(1)).update(info1); - verify(concatenationService, never()).merge(info1); - } - - @Test - public void testFinalUpload() throws Exception { - TusServletResponse response = new TusServletResponse(this.servletResponse); - - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - response.setHeader(HttpHeader.LOCATION, info1.getId().toString()); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final; 123 456"); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), response, uploadStorageService, null); - - assertThat(info1.getUploadType(), is(UploadType.CONCATENATED)); - assertThat(info1.getUploadConcatHeaderValue(), is("final; 123 456")); - - verify(uploadStorageService, times(1)).update(info1); - verify(concatenationService, times(1)).merge(info1); - } - - @Test - public void testUploadNotFound() throws Exception { - TusServletResponse response = new TusServletResponse(this.servletResponse); - - response.setHeader(HttpHeader.LOCATION, "/test/upload/1234"); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final; 123 456"); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), response, uploadStorageService, null); - - verify(uploadStorageService, never()).update(any(UploadInfo.class)); - verify(concatenationService, never()).merge(any(UploadInfo.class)); - } - -} \ No newline at end of file + private ConcatenationPostRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Mock private UploadConcatenationService concatenationService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new ConcatenationPostRequestHandler(); + when(uploadStorageService.getUploadConcatenationService()).thenReturn(concatenationService); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(true)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void testRegularUpload() throws Exception { + TusServletResponse response = new TusServletResponse(this.servletResponse); + + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + response.setHeader(HttpHeader.LOCATION, info1.getId().toString()); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + response, + uploadStorageService, + null); + + assertThat(info1.getUploadType(), is(UploadType.REGULAR)); + assertThat(info1.getUploadConcatHeaderValue(), is(nullValue())); + + verify(uploadStorageService, times(1)).update(info1); + verify(concatenationService, never()).merge(info1); + } + + @Test + public void testPartialUpload() throws Exception { + TusServletResponse response = new TusServletResponse(this.servletResponse); + + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + response.setHeader(HttpHeader.LOCATION, info1.getId().toString()); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + response, + uploadStorageService, + null); + + assertThat(info1.getUploadType(), is(UploadType.PARTIAL)); + assertThat(info1.getUploadConcatHeaderValue(), is("partial")); + + verify(uploadStorageService, times(1)).update(info1); + verify(concatenationService, never()).merge(info1); + } + + @Test + public void testFinalUpload() throws Exception { + TusServletResponse response = new TusServletResponse(this.servletResponse); + + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + response.setHeader(HttpHeader.LOCATION, info1.getId().toString()); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final; 123 456"); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + response, + uploadStorageService, + null); + + assertThat(info1.getUploadType(), is(UploadType.CONCATENATED)); + assertThat(info1.getUploadConcatHeaderValue(), is("final; 123 456")); + + verify(uploadStorageService, times(1)).update(info1); + verify(concatenationService, times(1)).merge(info1); + } + + @Test + public void testUploadNotFound() throws Exception { + TusServletResponse response = new TusServletResponse(this.servletResponse); + + response.setHeader(HttpHeader.LOCATION, "/test/upload/1234"); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final; 123 456"); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + response, + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(any(UploadInfo.class)); + verify(concatenationService, never()).merge(any(UploadInfo.class)); + } +} diff --git a/src/test/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidatorTest.java b/src/test/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidatorTest.java index 6248949..bfc6c97 100644 --- a/src/test/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidatorTest.java +++ b/src/test/java/me/desair/tus/server/concatenation/validation/NoUploadLengthOnFinalValidatorTest.java @@ -13,80 +13,79 @@ public class NoUploadLengthOnFinalValidatorTest { - private NoUploadLengthOnFinalValidator validator; - - private MockHttpServletRequest servletRequest; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new NoUploadLengthOnFinalValidator(); + private NoUploadLengthOnFinalValidator validator; + + private MockHttpServletRequest servletRequest; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new NoUploadLengthOnFinalValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(false)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void validateFinalUploadValid() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;12345 235235 253523"); + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); + + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(false)); - assertThat(validator.supports(null), is(false)); - } - - @Test - public void validateFinalUploadValid() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;12345 235235 253523"); - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); - - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + // No Exception is thrown + } - //No Exception is thrown - } - - @Test(expected = UploadLengthNotAllowedOnConcatenationException.class) - public void validateFinalUploadInvalid() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;12345 235235 253523"); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); - - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, null, null); - } + @Test(expected = UploadLengthNotAllowedOnConcatenationException.class) + public void validateFinalUploadInvalid() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;12345 235235 253523"); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); - @Test - public void validateNotFinal1() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, null, null); + } - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateNotFinal1() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateNotFinal2() throws Exception { - //servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); + // No Exception is thrown + } - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateNotFinal2() throws Exception { + // servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "partial"); + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "10L"); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } -} \ No newline at end of file + // No Exception is thrown + } +} diff --git a/src/test/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidatorTest.java b/src/test/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidatorTest.java index 1d23023..436d2d5 100644 --- a/src/test/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidatorTest.java +++ b/src/test/java/me/desair/tus/server/concatenation/validation/PartialUploadsExistValidatorTest.java @@ -5,7 +5,6 @@ import static org.mockito.Mockito.when; import java.util.UUID; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.InvalidPartialUploadIdException; @@ -22,94 +21,95 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class PartialUploadsExistValidatorTest { - private PartialUploadsExistValidator validator; + private PartialUploadsExistValidator validator; - private MockHttpServletRequest servletRequest; + private MockHttpServletRequest servletRequest; - @Mock - private UploadStorageService uploadStorageService; + @Mock private UploadStorageService uploadStorageService; - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new PartialUploadsExistValidator(); - } + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new PartialUploadsExistValidator(); + } - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(false)); - assertThat(validator.supports(null), is(false)); - } + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(false)); + assertThat(validator.supports(null), is(false)); + } - @Test - public void testValid() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); + @Test + public void testValid() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); - UploadInfo info2 = new UploadInfo(); - info2.setId(new UploadId(UUID.randomUUID())); + UploadInfo info2 = new UploadInfo(); + info2.setId(new UploadId(UUID.randomUUID())); - when(uploadStorageService.getUploadInfo(info1.getId().toString(), null)).thenReturn(info1); - when(uploadStorageService.getUploadInfo(info2.getId().toString(), null)).thenReturn(info2); + when(uploadStorageService.getUploadInfo(info1.getId().toString(), null)).thenReturn(info1); + when(uploadStorageService.getUploadInfo(info2.getId().toString(), null)).thenReturn(info2); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, String.format("final; %s %s", info1.getId(), info2.getId())); + servletRequest.addHeader( + HttpHeader.UPLOAD_CONCAT, String.format("final; %s %s", info1.getId(), info2.getId())); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - //No exception is thrown - } + // No exception is thrown + } - @Test(expected = InvalidPartialUploadIdException.class) - public void testInvalidUploadNotFound() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); + @Test(expected = InvalidPartialUploadIdException.class) + public void testInvalidUploadNotFound() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); - when(uploadStorageService.getUploadInfo(info1.getId())).thenReturn(info1); + when(uploadStorageService.getUploadInfo(info1.getId())).thenReturn(info1); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, - String.format("final; %s %s", info1.getId(), UUID.randomUUID())); + servletRequest.addHeader( + HttpHeader.UPLOAD_CONCAT, String.format("final; %s %s", info1.getId(), UUID.randomUUID())); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } - @Test(expected = InvalidPartialUploadIdException.class) - public void testInvalidId() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); + @Test(expected = InvalidPartialUploadIdException.class) + public void testInvalidId() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); - when(uploadStorageService.getUploadInfo(info1.getId().toString(), null)).thenReturn(info1); + when(uploadStorageService.getUploadInfo(info1.getId().toString(), null)).thenReturn(info1); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, String.format("final; %s %s", info1.getId(), "test")); + servletRequest.addHeader( + HttpHeader.UPLOAD_CONCAT, String.format("final; %s %s", info1.getId(), "test")); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } - @Test(expected = InvalidPartialUploadIdException.class) - public void testInvalidNoUploads1() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final; "); + @Test(expected = InvalidPartialUploadIdException.class) + public void testInvalidNoUploads1() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final; "); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - //No Exception is thrown - } + // No Exception is thrown + } - @Test(expected = InvalidPartialUploadIdException.class) - public void testInvalidNoUploads2() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;"); + @Test(expected = InvalidPartialUploadIdException.class) + public void testInvalidNoUploads2() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;"); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - //No Exception is thrown - } -} \ No newline at end of file + // No Exception is thrown + } +} diff --git a/src/test/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidatorTest.java b/src/test/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidatorTest.java index 6ba9d85..d04f6bc 100644 --- a/src/test/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidatorTest.java +++ b/src/test/java/me/desair/tus/server/concatenation/validation/PatchFinalUploadValidatorTest.java @@ -8,7 +8,6 @@ import static org.mockito.Mockito.when; import java.util.UUID; - import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.PatchOnFinalUploadNotAllowedException; import me.desair.tus.server.upload.UploadId; @@ -25,91 +24,90 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class PatchFinalUploadValidatorTest { - private PatchFinalUploadValidator validator; - - private MockHttpServletRequest servletRequest; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new PatchFinalUploadValidator(); - } - - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(false)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(false)); + private PatchFinalUploadValidator validator; + + private MockHttpServletRequest servletRequest; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new PatchFinalUploadValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(false)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void testValid() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + info1.setUploadType(UploadType.REGULAR); + + UploadInfo info2 = new UploadInfo(); + info2.setId(new UploadId(UUID.randomUUID())); + info2.setUploadType(UploadType.PARTIAL); + + UploadInfo info3 = new UploadInfo(); + info3.setId(new UploadId(UUID.randomUUID())); + info3.setUploadType(null); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + when(uploadStorageService.getUploadInfo(eq(info2.getId().toString()), nullable(String.class))) + .thenReturn(info2); + when(uploadStorageService.getUploadInfo(eq(info3.getId().toString()), nullable(String.class))) + .thenReturn(info3); + + // When we validate the requests + try { + servletRequest.setRequestURI(info1.getId().toString()); + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + + servletRequest.setRequestURI(info2.getId().toString()); + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + + servletRequest.setRequestURI(info3.getId().toString()); + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void testValid() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - info1.setUploadType(UploadType.REGULAR); - - UploadInfo info2 = new UploadInfo(); - info2.setId(new UploadId(UUID.randomUUID())); - info2.setUploadType(UploadType.PARTIAL); - - UploadInfo info3 = new UploadInfo(); - info3.setId(new UploadId(UUID.randomUUID())); - info3.setUploadType(null); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - when(uploadStorageService.getUploadInfo(eq(info2.getId().toString()), - nullable(String.class))).thenReturn(info2); - when(uploadStorageService.getUploadInfo(eq(info3.getId().toString()), - nullable(String.class))).thenReturn(info3); - - //When we validate the requests - try { - servletRequest.setRequestURI(info1.getId().toString()); - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - - servletRequest.setRequestURI(info2.getId().toString()); - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - - servletRequest.setRequestURI(info3.getId().toString()); - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - - //No exception is thrown - } - - @Test - public void testValidNotFound() throws Exception { - try { - //When we validate the request - servletRequest.setRequestURI("/upload/test"); - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - } - - @Test(expected = PatchOnFinalUploadNotAllowedException.class) - public void testInvalidFinal() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - info1.setUploadType(UploadType.CONCATENATED); - - when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), - nullable(String.class))).thenReturn(info1); - - //When we validate the request - servletRequest.setRequestURI(info1.getId().toString()); - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + // No exception is thrown + } + + @Test + public void testValidNotFound() throws Exception { + try { + // When we validate the request + servletRequest.setRequestURI("/upload/test"); + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } -} \ No newline at end of file + } + + @Test(expected = PatchOnFinalUploadNotAllowedException.class) + public void testInvalidFinal() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + info1.setUploadType(UploadType.CONCATENATED); + + when(uploadStorageService.getUploadInfo(eq(info1.getId().toString()), nullable(String.class))) + .thenReturn(info1); + + // When we validate the request + servletRequest.setRequestURI(info1.getId().toString()); + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } +} diff --git a/src/test/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandlerTest.java b/src/test/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandlerTest.java index c762675..ccac155 100644 --- a/src/test/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandlerTest.java +++ b/src/test/java/me/desair/tus/server/core/CoreDefaultResponseHeadersHandlerTest.java @@ -15,37 +15,43 @@ public class CoreDefaultResponseHeadersHandlerTest { - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - private CoreDefaultResponseHeadersHandler handler; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CoreDefaultResponseHeadersHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(true)); - assertThat(handler.supports(HttpMethod.POST), is(true)); - assertThat(handler.supports(HttpMethod.PUT), is(true)); - assertThat(handler.supports(HttpMethod.DELETE), is(true)); - assertThat(handler.supports(HttpMethod.HEAD), is(true)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(true)); - assertThat(handler.supports(null), is(true)); - } - - @Test - public void process() throws Exception { - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), null, null); - - assertThat(servletResponse.getHeader(HttpHeader.TUS_RESUMABLE), is(TusFileUploadService.TUS_API_VERSION)); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_LENGTH), is("0")); - } -} \ No newline at end of file + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + private CoreDefaultResponseHeadersHandler handler; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CoreDefaultResponseHeadersHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(true)); + assertThat(handler.supports(HttpMethod.POST), is(true)); + assertThat(handler.supports(HttpMethod.PUT), is(true)); + assertThat(handler.supports(HttpMethod.DELETE), is(true)); + assertThat(handler.supports(HttpMethod.HEAD), is(true)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(true)); + assertThat(handler.supports(null), is(true)); + } + + @Test + public void process() throws Exception { + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + null, + null); + + assertThat( + servletResponse.getHeader(HttpHeader.TUS_RESUMABLE), + is(TusFileUploadService.TUS_API_VERSION)); + assertThat(servletResponse.getHeader(HttpHeader.CONTENT_LENGTH), is("0")); + } +} diff --git a/src/test/java/me/desair/tus/server/core/CoreHeadRequestHandlerTest.java b/src/test/java/me/desair/tus/server/core/CoreHeadRequestHandlerTest.java index 84e827e..2316520 100644 --- a/src/test/java/me/desair/tus/server/core/CoreHeadRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/core/CoreHeadRequestHandlerTest.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.when; import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadInfo; @@ -26,80 +25,93 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class CoreHeadRequestHandlerTest { - private CoreHeadRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CoreHeadRequestHandler(); - } - - @Test - public void processWithLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is("10")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("2")); - assertThat(servletResponse.getHeader(HttpHeader.CACHE_CONTROL), is("no-store")); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void processConcatenatedWithLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - info.setUploadType(UploadType.CONCATENATED); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.CACHE_CONTROL), is("no-store")); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void processWithoutLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(0L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("0")); - assertThat(servletResponse.getHeader(HttpHeader.CACHE_CONTROL), is("no-store")); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(true)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private CoreHeadRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CoreHeadRequestHandler(); + } + + @Test + public void processWithLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is("10")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("2")); + assertThat(servletResponse.getHeader(HttpHeader.CACHE_CONTROL), is("no-store")); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void processConcatenatedWithLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + info.setUploadType(UploadType.CONCATENATED); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.CACHE_CONTROL), is("no-store")); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void processWithoutLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(0L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_LENGTH), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("0")); + assertThat(servletResponse.getHeader(HttpHeader.CACHE_CONTROL), is("no-store")); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(true)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/CoreOptionsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/core/CoreOptionsRequestHandlerTest.java index d89754c..8873e57 100644 --- a/src/test/java/me/desair/tus/server/core/CoreOptionsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/core/CoreOptionsRequestHandlerTest.java @@ -6,7 +6,6 @@ import static org.mockito.Mockito.when; import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.TusFileUploadService; @@ -24,56 +23,64 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class CoreOptionsRequestHandlerTest { - private CoreOptionsRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CoreOptionsRequestHandler(); - } - - @Test - public void processWithMaxSize() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(5368709120L); - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.TUS_VERSION), is(TusFileUploadService.TUS_API_VERSION)); - assertThat(servletResponse.getHeader(HttpHeader.TUS_MAX_SIZE), is("5368709120")); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void processWithoutMaxSize() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(0L); - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.TUS_VERSION), is("1.0.0")); - assertThat(servletResponse.getHeader(HttpHeader.TUS_MAX_SIZE), is(nullValue())); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private CoreOptionsRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CoreOptionsRequestHandler(); + } + + @Test + public void processWithMaxSize() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(5368709120L); + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat( + servletResponse.getHeader(HttpHeader.TUS_VERSION), + is(TusFileUploadService.TUS_API_VERSION)); + assertThat(servletResponse.getHeader(HttpHeader.TUS_MAX_SIZE), is("5368709120")); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void processWithoutMaxSize() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(0L); + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.TUS_VERSION), is("1.0.0")); + assertThat(servletResponse.getHeader(HttpHeader.TUS_MAX_SIZE), is(nullValue())); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/CorePatchRequestHandlerTest.java b/src/test/java/me/desair/tus/server/core/CorePatchRequestHandlerTest.java index b8c4ae8..99db90b 100644 --- a/src/test/java/me/desair/tus/server/core/CorePatchRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/core/CorePatchRequestHandlerTest.java @@ -10,10 +10,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import jakarta.servlet.http.HttpServletResponse; import java.io.InputStream; import java.util.UUID; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.UploadNotFoundException; @@ -33,100 +32,119 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class CorePatchRequestHandlerTest { - private CorePatchRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CorePatchRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(true)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void processInProgress() throws Exception { - UploadInfo info = new UploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - UploadInfo updatedInfo = new UploadInfo(); - updatedInfo.setId(info.getId()); - updatedInfo.setOffset(8L); - updatedInfo.setLength(10L); - when(uploadStorageService.append(any(UploadInfo.class), any(InputStream.class))).thenReturn(updatedInfo); - - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - - verify(uploadStorageService, times(1)).append(eq(info), any(InputStream.class)); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("8")); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void processFinished() throws Exception { - UploadInfo info = new UploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(10L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).append(any(UploadInfo.class), any(InputStream.class)); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("10")); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void processNotFound() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); - - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).append(any(UploadInfo.class), any(InputStream.class)); - - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); - } - - @Test - public void processAppendNotFound() throws Exception { - UploadInfo info = new UploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(10L); - info.setLength(8L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.append(any(UploadInfo.class), any(InputStream.class))) - .thenThrow(new UploadNotFoundException("test")); - - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); - } -} \ No newline at end of file + private CorePatchRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CorePatchRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(true)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void processInProgress() throws Exception { + UploadInfo info = new UploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + UploadInfo updatedInfo = new UploadInfo(); + updatedInfo.setId(info.getId()); + updatedInfo.setOffset(8L); + updatedInfo.setLength(10L); + when(uploadStorageService.append(any(UploadInfo.class), any(InputStream.class))) + .thenReturn(updatedInfo); + + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)).append(eq(info), any(InputStream.class)); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("8")); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void processFinished() throws Exception { + UploadInfo info = new UploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(10L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()).append(any(UploadInfo.class), any(InputStream.class)); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_OFFSET), is("10")); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void processNotFound() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); + + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()).append(any(UploadInfo.class), any(InputStream.class)); + + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + } + + @Test + public void processAppendNotFound() throws Exception { + UploadInfo info = new UploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(10L); + info.setLength(8L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.append(any(UploadInfo.class), any(InputStream.class))) + .thenThrow(new UploadNotFoundException("test")); + + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/ITCoreProtocol.java b/src/test/java/me/desair/tus/server/core/ITCoreProtocol.java index 0febcdb..8cf26a7 100644 --- a/src/test/java/me/desair/tus/server/core/ITCoreProtocol.java +++ b/src/test/java/me/desair/tus/server/core/ITCoreProtocol.java @@ -7,9 +7,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.io.InputStream; import jakarta.servlet.http.HttpServletResponse; - +import java.io.InputStream; import me.desair.tus.server.AbstractTusExtensionIntegrationTest; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; @@ -27,160 +26,162 @@ public class ITCoreProtocol extends AbstractTusExtensionIntegrationTest { - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - tusFeature = new CoreProtocol(); - uploadInfo = null; - } - - @Test - public void getName() throws Exception { - assertThat(tusFeature.getName(), is("core")); - } - - @Test(expected = UnsupportedMethodException.class) - public void testUnsupportedHttpMethod() throws Exception { - prepareUploadInfo(2L, 10L); - setRequestHeaders(HttpHeader.TUS_RESUMABLE); - - executeCall(HttpMethod.forName("TEST"), false); - } - - @Test - public void testHeadWithLength() throws Exception { - prepareUploadInfo(2L, 10L); - setRequestHeaders(HttpHeader.TUS_RESUMABLE); - - executeCall(HttpMethod.HEAD, false); - - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "2"); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "10"); - assertResponseHeader(HttpHeader.CACHE_CONTROL, "no-store"); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - } - - @Test - public void testHeadWithoutLength() throws Exception { - prepareUploadInfo(2L, null); - setRequestHeaders(HttpHeader.TUS_RESUMABLE); - - executeCall(HttpMethod.HEAD, false); - - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "2"); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, (String) null); - assertResponseHeader(HttpHeader.CACHE_CONTROL, "no-store"); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - } - - @Test(expected = UploadNotFoundException.class) - public void testHeadNotFound() throws Exception { - //We don't prepare an upload info - setRequestHeaders(HttpHeader.TUS_RESUMABLE); - - executeCall(HttpMethod.HEAD, false); - } - - @Test(expected = InvalidTusResumableException.class) - public void testHeadInvalidVersion() throws Exception { - setRequestHeaders(); - prepareUploadInfo(2L, null); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); - - executeCall(HttpMethod.HEAD, false); - } - - @Test - public void testPatchSuccess() throws Exception { - prepareUploadInfo(2L, 10L); - setRequestHeaders(HttpHeader.TUS_RESUMABLE, HttpHeader.CONTENT_TYPE, HttpHeader.UPLOAD_OFFSET, - HttpHeader.CONTENT_LENGTH); - - executeCall(HttpMethod.PATCH, false); - - verify(uploadStorageService, times(1)) - .append(any(UploadInfo.class), any(InputStream.class)); - - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "2"); - assertResponseHeader(HttpHeader.UPLOAD_LENGTH, (String) null); - assertResponseHeader(HttpHeader.CACHE_CONTROL, (String) null); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - } - - @Test(expected = InvalidContentTypeException.class) - public void testPatchInvalidContentType() throws Exception { - prepareUploadInfo(2L, 10L); - setRequestHeaders(HttpHeader.TUS_RESUMABLE, HttpHeader.UPLOAD_OFFSET, HttpHeader.CONTENT_LENGTH); - - executeCall(HttpMethod.PATCH, false); - } - - @Test(expected = UploadOffsetMismatchException.class) - public void testPatchInvalidUploadOffset() throws Exception { - prepareUploadInfo(2L, 10L); - setRequestHeaders(HttpHeader.TUS_RESUMABLE, HttpHeader.CONTENT_TYPE, HttpHeader.CONTENT_LENGTH); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 5); - - executeCall(HttpMethod.PATCH, false); - } - - @Test(expected = InvalidContentLengthException.class) - public void testPatchInvalidContentLength() throws Exception { - prepareUploadInfo(2L, 10L); - setRequestHeaders(HttpHeader.TUS_RESUMABLE, HttpHeader.CONTENT_TYPE, HttpHeader.UPLOAD_OFFSET); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 9); - - executeCall(HttpMethod.PATCH, false); - } - - @Test - public void testOptionsWithMaxSize() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(107374182400L); - - setRequestHeaders(); - - executeCall(HttpMethod.OPTIONS, false); - - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_MAX_SIZE, "107374182400"); - assertResponseHeader(HttpHeader.TUS_EXTENSION, (String) null); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - } - - @Test - public void testOptionsWithNoMaxSize() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(0L); - - setRequestHeaders(); - - executeCall(HttpMethod.OPTIONS, false); - - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_MAX_SIZE, (String) null); - assertResponseHeader(HttpHeader.TUS_EXTENSION, (String) null); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - } - - - @Test - public void testOptionsIgnoreTusResumable() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(10L); - - setRequestHeaders(); - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); - - executeCall(HttpMethod.OPTIONS, false); - - assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); - assertResponseHeader(HttpHeader.TUS_MAX_SIZE, "10"); - assertResponseHeader(HttpHeader.TUS_EXTENSION, (String) null); - assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); - } -} \ No newline at end of file + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + tusFeature = new CoreProtocol(); + uploadInfo = null; + } + + @Test + public void getName() throws Exception { + assertThat(tusFeature.getName(), is("core")); + } + + @Test(expected = UnsupportedMethodException.class) + public void testUnsupportedHttpMethod() throws Exception { + prepareUploadInfo(2L, 10L); + setRequestHeaders(HttpHeader.TUS_RESUMABLE); + + executeCall(HttpMethod.forName("TEST"), false); + } + + @Test + public void testHeadWithLength() throws Exception { + prepareUploadInfo(2L, 10L); + setRequestHeaders(HttpHeader.TUS_RESUMABLE); + + executeCall(HttpMethod.HEAD, false); + + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "2"); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, "10"); + assertResponseHeader(HttpHeader.CACHE_CONTROL, "no-store"); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + } + + @Test + public void testHeadWithoutLength() throws Exception { + prepareUploadInfo(2L, null); + setRequestHeaders(HttpHeader.TUS_RESUMABLE); + + executeCall(HttpMethod.HEAD, false); + + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "2"); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, (String) null); + assertResponseHeader(HttpHeader.CACHE_CONTROL, "no-store"); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + } + + @Test(expected = UploadNotFoundException.class) + public void testHeadNotFound() throws Exception { + // We don't prepare an upload info + setRequestHeaders(HttpHeader.TUS_RESUMABLE); + + executeCall(HttpMethod.HEAD, false); + } + + @Test(expected = InvalidTusResumableException.class) + public void testHeadInvalidVersion() throws Exception { + setRequestHeaders(); + prepareUploadInfo(2L, null); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); + + executeCall(HttpMethod.HEAD, false); + } + + @Test + public void testPatchSuccess() throws Exception { + prepareUploadInfo(2L, 10L); + setRequestHeaders( + HttpHeader.TUS_RESUMABLE, + HttpHeader.CONTENT_TYPE, + HttpHeader.UPLOAD_OFFSET, + HttpHeader.CONTENT_LENGTH); + + executeCall(HttpMethod.PATCH, false); + + verify(uploadStorageService, times(1)).append(any(UploadInfo.class), any(InputStream.class)); + + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.UPLOAD_OFFSET, "2"); + assertResponseHeader(HttpHeader.UPLOAD_LENGTH, (String) null); + assertResponseHeader(HttpHeader.CACHE_CONTROL, (String) null); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + } + + @Test(expected = InvalidContentTypeException.class) + public void testPatchInvalidContentType() throws Exception { + prepareUploadInfo(2L, 10L); + setRequestHeaders( + HttpHeader.TUS_RESUMABLE, HttpHeader.UPLOAD_OFFSET, HttpHeader.CONTENT_LENGTH); + + executeCall(HttpMethod.PATCH, false); + } + + @Test(expected = UploadOffsetMismatchException.class) + public void testPatchInvalidUploadOffset() throws Exception { + prepareUploadInfo(2L, 10L); + setRequestHeaders(HttpHeader.TUS_RESUMABLE, HttpHeader.CONTENT_TYPE, HttpHeader.CONTENT_LENGTH); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 5); + + executeCall(HttpMethod.PATCH, false); + } + + @Test(expected = InvalidContentLengthException.class) + public void testPatchInvalidContentLength() throws Exception { + prepareUploadInfo(2L, 10L); + setRequestHeaders(HttpHeader.TUS_RESUMABLE, HttpHeader.CONTENT_TYPE, HttpHeader.UPLOAD_OFFSET); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 9); + + executeCall(HttpMethod.PATCH, false); + } + + @Test + public void testOptionsWithMaxSize() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(107374182400L); + + setRequestHeaders(); + + executeCall(HttpMethod.OPTIONS, false); + + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_MAX_SIZE, "107374182400"); + assertResponseHeader(HttpHeader.TUS_EXTENSION, (String) null); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + } + + @Test + public void testOptionsWithNoMaxSize() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(0L); + + setRequestHeaders(); + + executeCall(HttpMethod.OPTIONS, false); + + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_MAX_SIZE, (String) null); + assertResponseHeader(HttpHeader.TUS_EXTENSION, (String) null); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + } + + @Test + public void testOptionsIgnoreTusResumable() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(10L); + + setRequestHeaders(); + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); + + executeCall(HttpMethod.OPTIONS, false); + + assertResponseHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_VERSION, "1.0.0"); + assertResponseHeader(HttpHeader.TUS_MAX_SIZE, "10"); + assertResponseHeader(HttpHeader.TUS_EXTENSION, (String) null); + assertResponseStatus(HttpServletResponse.SC_NO_CONTENT); + } +} diff --git a/src/test/java/me/desair/tus/server/core/validation/ContentLengthValidatorTest.java b/src/test/java/me/desair/tus/server/core/validation/ContentLengthValidatorTest.java index 442f7db..7dd347d 100644 --- a/src/test/java/me/desair/tus/server/core/validation/ContentLengthValidatorTest.java +++ b/src/test/java/me/desair/tus/server/core/validation/ContentLengthValidatorTest.java @@ -21,167 +21,173 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ContentLengthValidatorTest { - private ContentLengthValidator validator; + private ContentLengthValidator validator; - private MockHttpServletRequest servletRequest; + private MockHttpServletRequest servletRequest; - @Mock - private UploadStorageService uploadStorageService; + @Mock private UploadStorageService uploadStorageService; - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new ContentLengthValidator(); - } - - @Test - public void validateValidLengthInitialUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(0L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 10L); - - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - - //No Exception is thrown - } - - @Test - public void validateValidLengthInProgressUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(5L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 5L); - - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - - //No Exception is thrown - } + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new ContentLengthValidator(); + } - @Test - public void validateValidLengthPartialUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + @Test + public void validateValidLengthInitialUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(0L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 10L); - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test(expected = InvalidContentLengthException.class) - public void validateInvalidLengthInitialUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(0L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + // No Exception is thrown + } - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 11L); + @Test + public void validateValidLengthInProgressUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(5L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 5L); - //Then expect a InvalidContentLengthException exception + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test(expected = InvalidContentLengthException.class) - public void validateInvalidLengthInProgressUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(5L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + // No Exception is thrown + } - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 6L); + @Test + public void validateValidLengthPartialUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); - //Then expect a InvalidContentLengthException exception + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test(expected = InvalidContentLengthException.class) - public void validateInvalidLengthPartialUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 10L); - - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - - //Then expect a InvalidContentLengthException exception + // No Exception is thrown + } + + @Test(expected = InvalidContentLengthException.class) + public void validateInvalidLengthInitialUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(0L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 11L); + + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + + // Then expect a InvalidContentLengthException exception + } + + @Test(expected = InvalidContentLengthException.class) + public void validateInvalidLengthInProgressUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(5L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 6L); + + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + + // Then expect a InvalidContentLengthException exception + } + + @Test(expected = InvalidContentLengthException.class) + public void validateInvalidLengthPartialUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 10L); + + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + + // Then expect a InvalidContentLengthException exception + } + + @Test + public void validateMissingContentLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + // We don't set a content length header + // servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); + + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateMissingContentLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - //We don't set a content length header - //servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); + // No Exception is thrown + } - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateMissingUploadInfo() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); - //No Exception is thrown - } - - @Test - public void validateMissingUploadInfo() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); - - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); - - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - - //No Exception is thrown - } + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(false)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(false)); + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } -} \ No newline at end of file + // No Exception is thrown + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(false)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/validation/ContentTypeValidatorTest.java b/src/test/java/me/desair/tus/server/core/validation/ContentTypeValidatorTest.java index 09e6167..8053088 100644 --- a/src/test/java/me/desair/tus/server/core/validation/ContentTypeValidatorTest.java +++ b/src/test/java/me/desair/tus/server/core/validation/ContentTypeValidatorTest.java @@ -11,61 +11,61 @@ import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; - public class ContentTypeValidatorTest { - private ContentTypeValidator validator; - - private MockHttpServletRequest servletRequest; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new ContentTypeValidator(); - } + private ContentTypeValidator validator; - @Test - public void validateValid() throws Exception { - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, ContentTypeValidator.APPLICATION_OFFSET_OCTET_STREAM); - - try { - validator.validate(HttpMethod.PATCH, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } - - //No exception is thrown - } + private MockHttpServletRequest servletRequest; - @Test(expected = InvalidContentTypeException.class) - public void validateInvalidHeader() throws Exception { - servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/octet-stream"); + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new ContentTypeValidator(); + } - validator.validate(HttpMethod.PATCH, servletRequest, null, null); - - //Expect a InvalidContentTypeException exception - } - - @Test(expected = InvalidContentTypeException.class) - public void validateMissingHeader() throws Exception { - //We don't set the header - //servletRequest.addHeader(HttpHeader.CONTENT_TYPE, ContentTypeValidator.APPLICATION_OFFSET_OCTET_STREAM); - - validator.validate(HttpMethod.PATCH, servletRequest, null, null); - - //Expect a InvalidContentTypeException exception - } + @Test + public void validateValid() throws Exception { + servletRequest.addHeader( + HttpHeader.CONTENT_TYPE, ContentTypeValidator.APPLICATION_OFFSET_OCTET_STREAM); - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(false)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(false)); + try { + validator.validate(HttpMethod.PATCH, servletRequest, null, null); + } catch (Exception ex) { + fail(); } -} \ No newline at end of file + // No exception is thrown + } + + @Test(expected = InvalidContentTypeException.class) + public void validateInvalidHeader() throws Exception { + servletRequest.addHeader(HttpHeader.CONTENT_TYPE, "application/octet-stream"); + + validator.validate(HttpMethod.PATCH, servletRequest, null, null); + + // Expect a InvalidContentTypeException exception + } + + @Test(expected = InvalidContentTypeException.class) + public void validateMissingHeader() throws Exception { + // We don't set the header + // servletRequest.addHeader(HttpHeader.CONTENT_TYPE, + // ContentTypeValidator.APPLICATION_OFFSET_OCTET_STREAM); + + validator.validate(HttpMethod.PATCH, servletRequest, null, null); + + // Expect a InvalidContentTypeException exception + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(false)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/validation/HttpMethodValidatorTest.java b/src/test/java/me/desair/tus/server/core/validation/HttpMethodValidatorTest.java index 8a509ca..b305e0e 100644 --- a/src/test/java/me/desair/tus/server/core/validation/HttpMethodValidatorTest.java +++ b/src/test/java/me/desair/tus/server/core/validation/HttpMethodValidatorTest.java @@ -14,40 +14,40 @@ public class HttpMethodValidatorTest { - private MockHttpServletRequest servletRequest; - private HttpMethodValidator validator; - private UploadStorageService uploadStorageService; - private UploadIdFactory idFactory; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new HttpMethodValidator(); + private MockHttpServletRequest servletRequest; + private HttpMethodValidator validator; + private UploadStorageService uploadStorageService; + private UploadIdFactory idFactory; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new HttpMethodValidator(); + } + + @Test + public void validateValid() throws Exception { + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - - @Test - public void validateValid() throws Exception { - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } - } - - @Test(expected = UnsupportedMethodException.class) - public void validateInvalid() throws Exception { - validator.validate(null, servletRequest, uploadStorageService, null); - } - - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(true)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(true)); - assertThat(validator.supports(HttpMethod.DELETE), is(true)); - assertThat(validator.supports(HttpMethod.HEAD), is(true)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(true)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(true)); - } -} \ No newline at end of file + } + + @Test(expected = UnsupportedMethodException.class) + public void validateInvalid() throws Exception { + validator.validate(null, servletRequest, uploadStorageService, null); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(true)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(true)); + assertThat(validator.supports(HttpMethod.DELETE), is(true)); + assertThat(validator.supports(HttpMethod.HEAD), is(true)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(true)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(true)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/validation/IdExistsValidatorTest.java b/src/test/java/me/desair/tus/server/core/validation/IdExistsValidatorTest.java index 0c5abe0..569c38a 100644 --- a/src/test/java/me/desair/tus/server/core/validation/IdExistsValidatorTest.java +++ b/src/test/java/me/desair/tus/server/core/validation/IdExistsValidatorTest.java @@ -20,56 +20,56 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class IdExistsValidatorTest { - private IdExistsValidator validator; + private IdExistsValidator validator; - private MockHttpServletRequest servletRequest; + private MockHttpServletRequest servletRequest; - @Mock - private UploadStorageService uploadStorageService; + @Mock private UploadStorageService uploadStorageService; - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new IdExistsValidator(); - } - - @Test - public void validateValid() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(0L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new IdExistsValidator(); + } - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateValid() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(0L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test(expected = UploadNotFoundException.class) - public void validateInvalid() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); + // No Exception is thrown + } - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + @Test(expected = UploadNotFoundException.class) + public void validateInvalid() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); - //Expect a UploadNotFoundException - } + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(true)); - assertThat(validator.supports(HttpMethod.POST), is(false)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(true)); - assertThat(validator.supports(HttpMethod.HEAD), is(true)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(false)); - } + // Expect a UploadNotFoundException + } -} \ No newline at end of file + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(true)); + assertThat(validator.supports(HttpMethod.POST), is(false)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(true)); + assertThat(validator.supports(HttpMethod.HEAD), is(true)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/validation/TusResumableValidatorTest.java b/src/test/java/me/desair/tus/server/core/validation/TusResumableValidatorTest.java index 9610e0a..7983999 100644 --- a/src/test/java/me/desair/tus/server/core/validation/TusResumableValidatorTest.java +++ b/src/test/java/me/desair/tus/server/core/validation/TusResumableValidatorTest.java @@ -14,56 +14,56 @@ public class TusResumableValidatorTest { - private MockHttpServletRequest servletRequest; - private TusResumableValidator validator; - private UploadStorageService uploadStorageService; + private MockHttpServletRequest servletRequest; + private TusResumableValidator validator; + private UploadStorageService uploadStorageService; - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new TusResumableValidator(); - } + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new TusResumableValidator(); + } - @Test(expected = InvalidTusResumableException.class) - public void validateNoVersion() throws Exception { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } + @Test(expected = InvalidTusResumableException.class) + public void validateNoVersion() throws Exception { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } - @Test(expected = InvalidTusResumableException.class) - public void validateInvalidVersion() throws Exception { - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } + @Test(expected = InvalidTusResumableException.class) + public void validateInvalidVersion() throws Exception { + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "2.0.0"); + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } - @Test - public void validateValid() throws Exception { - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateValid() throws Exception { + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } + } - @Test - public void validateNullMethod() throws Exception { - servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); - try { - validator.validate(null, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateNullMethod() throws Exception { + servletRequest.addHeader(HttpHeader.TUS_RESUMABLE, "1.0.0"); + try { + validator.validate(null, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } + } - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(true)); - assertThat(validator.supports(HttpMethod.DELETE), is(true)); - assertThat(validator.supports(HttpMethod.HEAD), is(true)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(true)); - } -} \ No newline at end of file + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(true)); + assertThat(validator.supports(HttpMethod.DELETE), is(true)); + assertThat(validator.supports(HttpMethod.HEAD), is(true)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(true)); + } +} diff --git a/src/test/java/me/desair/tus/server/core/validation/UploadOffsetValidatorTest.java b/src/test/java/me/desair/tus/server/core/validation/UploadOffsetValidatorTest.java index c807940..f5815d2 100644 --- a/src/test/java/me/desair/tus/server/core/validation/UploadOffsetValidatorTest.java +++ b/src/test/java/me/desair/tus/server/core/validation/UploadOffsetValidatorTest.java @@ -21,129 +21,133 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class UploadOffsetValidatorTest { - private UploadOffsetValidator validator; + private UploadOffsetValidator validator; - private MockHttpServletRequest servletRequest; + private MockHttpServletRequest servletRequest; - @Mock - private UploadStorageService uploadStorageService; + @Mock private UploadStorageService uploadStorageService; - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new UploadOffsetValidator(); - } - - @Test - public void validateValidOffsetInitialUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(0L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new UploadOffsetValidator(); + } - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0L); + @Test + public void validateValidOffsetInitialUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(0L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 0L); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateValidOffsetInProgressUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(5L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + // No Exception is thrown + } - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 5L); + @Test + public void validateValidOffsetInProgressUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(5L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 5L); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test(expected = UploadOffsetMismatchException.class) - public void validateInvalidOffsetInitialUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(0L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + // No Exception is thrown + } - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 3L); + @Test(expected = UploadOffsetMismatchException.class) + public void validateInvalidOffsetInitialUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(0L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 3L); - //Then expect a UploadOffsetMismatchException exception - } + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - @Test(expected = UploadOffsetMismatchException.class) - public void validateInvalidOffsetInProgressUpload() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(5L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + // Then expect a UploadOffsetMismatchException exception + } - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 6L); + @Test(expected = UploadOffsetMismatchException.class) + public void validateInvalidOffsetInProgressUpload() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(5L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 6L); - //Then expect a UploadOffsetMismatchException exception - } + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - @Test(expected = UploadOffsetMismatchException.class) - public void validateMissingUploadOffset() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); + // Then expect a UploadOffsetMismatchException exception + } - //We don't set a content length header - //servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 3L); + @Test(expected = UploadOffsetMismatchException.class) + public void validateMissingUploadOffset() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); - //When we validate the request - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + // We don't set a content length header + // servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 3L); - //Then expect a UploadOffsetMismatchException exception - } - - @Test - public void validateMissingUploadInfo() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); + // When we validate the request + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 3L); + // Then expect a UploadOffsetMismatchException exception + } - //When we validate the request - try { - validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateMissingUploadInfo() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); - //No Exception is thrown - } + servletRequest.addHeader(HttpHeader.UPLOAD_OFFSET, 3L); - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(false)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(true)); - assertThat(validator.supports(null), is(false)); + // When we validate the request + try { + validator.validate(HttpMethod.PATCH, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } -} \ No newline at end of file + // No Exception is thrown + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(false)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(true)); + assertThat(validator.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/creation/CreationHeadRequestHandlerTest.java b/src/test/java/me/desair/tus/server/creation/CreationHeadRequestHandlerTest.java index cc2c5d5..1ed367d 100644 --- a/src/test/java/me/desair/tus/server/creation/CreationHeadRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/creation/CreationHeadRequestHandlerTest.java @@ -23,91 +23,110 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class CreationHeadRequestHandlerTest { - private CreationHeadRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CreationHeadRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(true)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void processWithLengthAndMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - info.setEncodedMetadata("encoded-metadata"); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("encoded-metadata")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - } - - @Test - public void processWithLengthAndNoMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - info.setEncodedMetadata(null); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - } - - @Test - public void processWithNoLengthAndMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(null); - info.setEncodedMetadata("encoded-metadata"); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("encoded-metadata")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is("1")); - } - - @Test - public void processWithNoLengthAndNoMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(null); - info.setEncodedMetadata(null); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is("1")); - } -} \ No newline at end of file + private CreationHeadRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CreationHeadRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(true)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void processWithLengthAndMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + info.setEncodedMetadata("encoded-metadata"); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("encoded-metadata")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + } + + @Test + public void processWithLengthAndNoMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + info.setEncodedMetadata(null); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + } + + @Test + public void processWithNoLengthAndMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(null); + info.setEncodedMetadata("encoded-metadata"); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("encoded-metadata")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is("1")); + } + + @Test + public void processWithNoLengthAndNoMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(null); + info.setEncodedMetadata(null); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is("1")); + } +} diff --git a/src/test/java/me/desair/tus/server/creation/CreationOptionsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/creation/CreationOptionsRequestHandlerTest.java index 14eb754..4476595 100644 --- a/src/test/java/me/desair/tus/server/creation/CreationOptionsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/creation/CreationOptionsRequestHandlerTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import java.util.Arrays; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.util.TusServletRequest; @@ -17,39 +16,43 @@ public class CreationOptionsRequestHandlerTest { - private CreationOptionsRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CreationOptionsRequestHandler(); - } - - @Test - public void processListExtensions() throws Exception { - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), null, null); - - assertThat(Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), - containsInAnyOrder("creation", "creation-defer-length")); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private CreationOptionsRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CreationOptionsRequestHandler(); + } + + @Test + public void processListExtensions() throws Exception { + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + null, + null); + + assertThat( + Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), + containsInAnyOrder("creation", "creation-defer-length")); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/creation/CreationPatchRequestHandlerTest.java b/src/test/java/me/desair/tus/server/creation/CreationPatchRequestHandlerTest.java index 2490a0b..be1f912 100644 --- a/src/test/java/me/desair/tus/server/creation/CreationPatchRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/creation/CreationPatchRequestHandlerTest.java @@ -10,9 +10,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.UUID; import jakarta.servlet.http.HttpServletResponse; - +import java.util.UUID; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.UploadNotFoundException; @@ -32,118 +31,148 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class CreationPatchRequestHandlerTest { - private CreationPatchRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CreationPatchRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(true)); - assertThat(handler.supports(null), is(false)); - } - - - @Test - public void processWithLengthAndHeader() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).update(info); - } - - @Test - public void processWithLengthAndNoHeader() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).update(info); - } - - @Test - public void processWithoutLengthAndHeader() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(null); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)).update(info); - assertThat(info.getLength(), is(10L)); - } - - @Test - public void processWithoutLengthAndNoHeader() throws Exception { - UploadInfo info = new UploadInfo(); - info.setOffset(2L); - info.setLength(null); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - - handler.process(HttpMethod.HEAD, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).update(info); - } - - @Test - public void processNotFound() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); - - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - } - - @Test - public void processAppendNotFound() throws Exception { - UploadInfo info = new UploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - - doThrow(new UploadNotFoundException("test")).when(uploadStorageService).update(any(UploadInfo.class)); - - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); - } -} \ No newline at end of file + private CreationPatchRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CreationPatchRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(true)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void processWithLengthAndHeader() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(info); + } + + @Test + public void processWithLengthAndNoHeader() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(info); + } + + @Test + public void processWithoutLengthAndHeader() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(null); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)).update(info); + assertThat(info.getLength(), is(10L)); + } + + @Test + public void processWithoutLengthAndNoHeader() throws Exception { + UploadInfo info = new UploadInfo(); + info.setOffset(2L); + info.setLength(null); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + + handler.process( + HttpMethod.HEAD, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(info); + } + + @Test + public void processNotFound() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); + + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + } + + @Test + public void processAppendNotFound() throws Exception { + UploadInfo info = new UploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + + doThrow(new UploadNotFoundException("test")) + .when(uploadStorageService) + .update(any(UploadInfo.class)); + + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + } +} diff --git a/src/test/java/me/desair/tus/server/creation/CreationPostRequestHandlerTest.java b/src/test/java/me/desair/tus/server/creation/CreationPostRequestHandlerTest.java index 0ff35e0..0e28bb9 100644 --- a/src/test/java/me/desair/tus/server/creation/CreationPostRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/creation/CreationPostRequestHandlerTest.java @@ -9,9 +9,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.UUID; import jakarta.servlet.http.HttpServletResponse; - +import java.util.UUID; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadId; @@ -31,157 +30,185 @@ import org.springframework.mock.web.MockHttpServletResponse; /** - * The Server MUST acknowledge a successful upload creation with the 201 Created status. - * The Server MUST set the Location header to the URL of the created resource. This URL MAY be absolute or relative. + * The Server MUST acknowledge a successful upload creation with the 201 Created status. The Server + * MUST set the Location header to the URL of the created resource. This URL MAY be absolute or + * relative. */ @RunWith(MockitoJUnitRunner.Silent.class) public class CreationPostRequestHandlerTest { - private CreationPostRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new CreationPostRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(true)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void processWithLengthAndMetadata() throws Exception { - servletRequest.setRequestURI("/test/upload"); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "encoded-metadata"); - - final UploadId id = new UploadId(UUID.randomUUID()); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - assertThat(upload.getLength(), is(10L)); - assertThat(upload.getEncodedMetadata(), is("encoded-metadata")); - - upload.setId(id); - - return upload; - } - }); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)).create(ArgumentMatchers.any(UploadInfo.class), - nullable(String.class)); - assertThat(servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); - } - - @Test - public void processWithLengthAndNoMetadata() throws Exception { - servletRequest.setRequestURI("/test/upload"); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); - //servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, null); - - final UploadId id = new UploadId(UUID.randomUUID()); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - assertThat(upload.getLength(), is(10L)); - assertThat(upload.getEncodedMetadata(), is(nullValue())); - - upload.setId(id); - - return upload; - } - }); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)).create(ArgumentMatchers.any(UploadInfo.class), - nullable(String.class)); - assertThat(servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); - } - - @Test - public void processWithNoLengthAndMetadata() throws Exception { - servletRequest.setRequestURI("/test/upload"); - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, null); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "encoded-metadata"); - - final UploadId id = new UploadId(UUID.randomUUID()); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - assertThat(upload.getLength(), is(nullValue())); - assertThat(upload.getEncodedMetadata(), is("encoded-metadata")); - - upload.setId(id); - - return upload; - } - }); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)).create(ArgumentMatchers.any(UploadInfo.class), - nullable(String.class)); - assertThat(servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); - } - - @Test - public void processWithNoLengthAndNoMetadata() throws Exception { - servletRequest.setRequestURI("/test/upload"); - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, null); - //servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, null); - - final UploadId id = new UploadId(UUID.randomUUID()); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - assertThat(upload.getLength(), is(nullValue())); - assertThat(upload.getEncodedMetadata(), is(nullValue())); - - upload.setId(id); - - return upload; - } - }); - - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)).create(ArgumentMatchers.any(UploadInfo.class), - nullable(String.class)); - assertThat(servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); - } -} \ No newline at end of file + private CreationPostRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new CreationPostRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(true)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void processWithLengthAndMetadata() throws Exception { + servletRequest.setRequestURI("/test/upload"); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "encoded-metadata"); + + final UploadId id = new UploadId(UUID.randomUUID()); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + assertThat(upload.getLength(), is(10L)); + assertThat(upload.getEncodedMetadata(), is("encoded-metadata")); + + upload.setId(id); + + return upload; + } + }); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)) + .create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class)); + assertThat( + servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); + } + + @Test + public void processWithLengthAndNoMetadata() throws Exception { + servletRequest.setRequestURI("/test/upload"); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 10L); + // servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, null); + + final UploadId id = new UploadId(UUID.randomUUID()); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + assertThat(upload.getLength(), is(10L)); + assertThat(upload.getEncodedMetadata(), is(nullValue())); + + upload.setId(id); + + return upload; + } + }); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)) + .create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class)); + assertThat( + servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); + } + + @Test + public void processWithNoLengthAndMetadata() throws Exception { + servletRequest.setRequestURI("/test/upload"); + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, null); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "encoded-metadata"); + + final UploadId id = new UploadId(UUID.randomUUID()); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + assertThat(upload.getLength(), is(nullValue())); + assertThat(upload.getEncodedMetadata(), is("encoded-metadata")); + + upload.setId(id); + + return upload; + } + }); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)) + .create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class)); + assertThat( + servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); + } + + @Test + public void processWithNoLengthAndNoMetadata() throws Exception { + servletRequest.setRequestURI("/test/upload"); + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, null); + // servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, null); + + final UploadId id = new UploadId(UUID.randomUUID()); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + assertThat(upload.getLength(), is(nullValue())); + assertThat(upload.getEncodedMetadata(), is(nullValue())); + + upload.setId(id); + + return upload; + } + }); + + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)) + .create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class)); + assertThat( + servletResponse.getHeader(HttpHeader.LOCATION), endsWith("/test/upload/" + id.toString())); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_CREATED)); + } +} diff --git a/src/test/java/me/desair/tus/server/creation/ITCreationExtension.java b/src/test/java/me/desair/tus/server/creation/ITCreationExtension.java index f5d4a4e..2991deb 100644 --- a/src/test/java/me/desair/tus/server/creation/ITCreationExtension.java +++ b/src/test/java/me/desair/tus/server/creation/ITCreationExtension.java @@ -9,9 +9,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.UUID; import jakarta.servlet.http.HttpServletResponse; - +import java.util.UUID; import me.desair.tus.server.AbstractTusExtensionIntegrationTest; import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; @@ -30,243 +29,253 @@ public class ITCreationExtension extends AbstractTusExtensionIntegrationTest { - private static final String UPLOAD_URI = "/test/upload"; - - //It's important to return relative UPLOAD URLs in the Location header in order to support HTTPS proxies - //that sit in front of the web app - private static final String UPLOAD_URL = UPLOAD_URI + "/"; - - private UploadId id; - - @Before - public void setUp() throws Exception { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - tusFeature = new CreationExtension(); - uploadInfo = null; - - id = new UploadId(UUID.randomUUID()); - servletRequest.setRequestURI(UPLOAD_URI); - reset(uploadStorageService); - when(uploadStorageService.getUploadURI()).thenReturn(UPLOAD_URI); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - upload.setId(id); - - when(uploadStorageService.getUploadInfo(UPLOAD_URL + id.toString(), - (String) invocation.getArgument(1))).thenReturn(upload); - return upload; - } - }); - } - - @Test - public void testOptions() throws Exception { - setRequestHeaders(); - - executeCall(HttpMethod.OPTIONS, false); - - //If the Server supports this extension, it MUST add creation to the Tus-Extension header. - //If the Server supports deferring length, it MUST add creation-defer-length to the Tus-Extension header. - assertResponseHeader(HttpHeader.TUS_EXTENSION, "creation", "creation-defer-length"); - } - - @Test - public void testPostWithLength() throws Exception { - //Create upload - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - - executeCall(HttpMethod.POST, false); - - verify(uploadStorageService, times(1)).create(any(UploadInfo.class), - nullable(String.class)); - assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - //Check data with head request - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.HEAD, false); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - - //Test Patch request - servletRequest = new MockHttpServletRequest(); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.PATCH, false); - } - - @Test - public void testPostWithDeferredLength() throws Exception { - //Create upload - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - - executeCall(HttpMethod.POST, false); - - verify(uploadStorageService, times(1)).create(any(UploadInfo.class), - nullable(String.class)); - assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - //Check data with head request - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.HEAD, false); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is("1")); - - //Test Patch request - servletRequest = new MockHttpServletRequest(); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.PATCH, false); - - //Re-check head request - servletRequest = new MockHttpServletRequest(); - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.HEAD, false); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - } - - @Test(expected = InvalidUploadLengthException.class) - public void testPostWithoutLength() throws Exception { - //Create upload without any length header - executeCall(HttpMethod.POST, false); - } - - @Test - public void testPostWithMetadata() throws Exception { - //Create upload - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "encoded metadata"); - - executeCall(HttpMethod.POST, false); - - verify(uploadStorageService, times(1)).create(any(UploadInfo.class), - nullable(String.class)); - assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - //Check data with head request - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.HEAD, false); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("encoded metadata")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - } - - @Test - public void testPostWithAllowedMaxSize() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(100L); - - //Create upload - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 90); - executeCall(HttpMethod.POST, false); - - verify(uploadStorageService, times(1)).create(any(UploadInfo.class), - nullable(String.class)); - assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - //Check data with head request - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.HEAD, false); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - } - - @Test(expected = MaxUploadLengthExceededException.class) - public void testPostWithExceededMaxSize() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(100L); - - //Create upload - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 110); - executeCall(HttpMethod.POST, false); - } - - @Test(expected = PostOnInvalidRequestURIException.class) - public void testPostOnInvalidUrl() throws Exception { - //Create upload - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - servletRequest.setRequestURI(UPLOAD_URL + id.toString()); - - executeCall(HttpMethod.POST, false); - } - - @Test - public void testPostWithValidRegexURI() throws Exception { - reset(uploadStorageService); - when(uploadStorageService.getUploadURI()).thenReturn("/submission/([a-z0-9]+)/files/upload"); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - upload.setId(id); - - when(uploadStorageService.getUploadInfo("/submission/0ae5f8vv4s8c/files/upload/" - + id.toString(), (String) invocation.getArgument(1))).thenReturn(upload); - return upload; - } - }); - - //Create upload - servletRequest.setRequestURI("/submission/0ae5f8vv4s8c/files/upload"); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "submission metadata"); - - executeCall(HttpMethod.POST, false); - - verify(uploadStorageService, times(1)).create(any(UploadInfo.class), - nullable(String.class)); - assertResponseHeader(HttpHeader.LOCATION, "/submission/0ae5f8vv4s8c/files/upload/" + id.toString()); - assertResponseStatus(HttpServletResponse.SC_CREATED); - - //Check data with head request - servletRequest.setRequestURI("/submission/0ae5f8vv4s8c/files/upload/" + id.toString()); - servletResponse = new MockHttpServletResponse(); - executeCall(HttpMethod.HEAD, false); - - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("submission metadata")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); - } - - @Test(expected = PostOnInvalidRequestURIException.class) - public void testPostWithInvalidRegexURI() throws Exception { - reset(uploadStorageService); - when(uploadStorageService.getUploadURI()).thenReturn("/submission/([a-z0-9]+)/files/upload"); - when(uploadStorageService.create(ArgumentMatchers.any(UploadInfo.class), nullable(String.class))).then( - new Answer() { - @Override - public UploadInfo answer(InvocationOnMock invocation) throws Throwable { - UploadInfo upload = invocation.getArgument(0); - upload.setId(id); - - when(uploadStorageService.getUploadInfo("/submission/0ae5f8vv4s8c/files/upload/" - + id.toString(), (String) invocation.getArgument(1))).thenReturn(upload); - return upload; - } - }); - - //Create upload - servletRequest.setRequestURI("/submission/a+b/files/upload"); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); - servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "submission metadata"); - - executeCall(HttpMethod.POST, false); - } -} \ No newline at end of file + private static final String UPLOAD_URI = "/test/upload"; + + // It's important to return relative UPLOAD URLs in the Location header in order to support + // HTTPS + // proxies + // that sit in front of the web app + private static final String UPLOAD_URL = UPLOAD_URI + "/"; + + private UploadId id; + + @Before + public void setUp() throws Exception { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + tusFeature = new CreationExtension(); + uploadInfo = null; + + id = new UploadId(UUID.randomUUID()); + servletRequest.setRequestURI(UPLOAD_URI); + reset(uploadStorageService); + when(uploadStorageService.getUploadURI()).thenReturn(UPLOAD_URI); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + upload.setId(id); + + when(uploadStorageService.getUploadInfo( + UPLOAD_URL + id.toString(), (String) invocation.getArgument(1))) + .thenReturn(upload); + return upload; + } + }); + } + + @Test + public void testOptions() throws Exception { + setRequestHeaders(); + + executeCall(HttpMethod.OPTIONS, false); + + // If the Server supports this extension, it MUST add creation to the Tus-Extension header. + // If the Server supports deferring length, it MUST add creation-defer-length to the + // Tus-Extension header. + assertResponseHeader(HttpHeader.TUS_EXTENSION, "creation", "creation-defer-length"); + } + + @Test + public void testPostWithLength() throws Exception { + // Create upload + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + + executeCall(HttpMethod.POST, false); + + verify(uploadStorageService, times(1)).create(any(UploadInfo.class), nullable(String.class)); + assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + // Check data with head request + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.HEAD, false); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + + // Test Patch request + servletRequest = new MockHttpServletRequest(); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.PATCH, false); + } + + @Test + public void testPostWithDeferredLength() throws Exception { + // Create upload + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + + executeCall(HttpMethod.POST, false); + + verify(uploadStorageService, times(1)).create(any(UploadInfo.class), nullable(String.class)); + assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + // Check data with head request + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.HEAD, false); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is("1")); + + // Test Patch request + servletRequest = new MockHttpServletRequest(); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.PATCH, false); + + // Re-check head request + servletRequest = new MockHttpServletRequest(); + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.HEAD, false); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + } + + @Test(expected = InvalidUploadLengthException.class) + public void testPostWithoutLength() throws Exception { + // Create upload without any length header + executeCall(HttpMethod.POST, false); + } + + @Test + public void testPostWithMetadata() throws Exception { + // Create upload + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "encoded metadata"); + + executeCall(HttpMethod.POST, false); + + verify(uploadStorageService, times(1)).create(any(UploadInfo.class), nullable(String.class)); + assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + // Check data with head request + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.HEAD, false); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("encoded metadata")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + } + + @Test + public void testPostWithAllowedMaxSize() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(100L); + + // Create upload + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 90); + executeCall(HttpMethod.POST, false); + + verify(uploadStorageService, times(1)).create(any(UploadInfo.class), nullable(String.class)); + assertResponseHeader(HttpHeader.LOCATION, UPLOAD_URL + id.toString()); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + // Check data with head request + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.HEAD, false); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is(nullValue())); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + } + + @Test(expected = MaxUploadLengthExceededException.class) + public void testPostWithExceededMaxSize() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(100L); + + // Create upload + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 110); + executeCall(HttpMethod.POST, false); + } + + @Test(expected = PostOnInvalidRequestURIException.class) + public void testPostOnInvalidUrl() throws Exception { + // Create upload + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + servletRequest.setRequestURI(UPLOAD_URL + id.toString()); + + executeCall(HttpMethod.POST, false); + } + + @Test + public void testPostWithValidRegexURI() throws Exception { + reset(uploadStorageService); + when(uploadStorageService.getUploadURI()).thenReturn("/submission/([a-z0-9]+)/files/upload"); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + upload.setId(id); + + when(uploadStorageService.getUploadInfo( + "/submission/0ae5f8vv4s8c/files/upload/" + id.toString(), + (String) invocation.getArgument(1))) + .thenReturn(upload); + return upload; + } + }); + + // Create upload + servletRequest.setRequestURI("/submission/0ae5f8vv4s8c/files/upload"); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "submission metadata"); + + executeCall(HttpMethod.POST, false); + + verify(uploadStorageService, times(1)).create(any(UploadInfo.class), nullable(String.class)); + assertResponseHeader( + HttpHeader.LOCATION, "/submission/0ae5f8vv4s8c/files/upload/" + id.toString()); + assertResponseStatus(HttpServletResponse.SC_CREATED); + + // Check data with head request + servletRequest.setRequestURI("/submission/0ae5f8vv4s8c/files/upload/" + id.toString()); + servletResponse = new MockHttpServletResponse(); + executeCall(HttpMethod.HEAD, false); + + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), is("submission metadata")); + assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_DEFER_LENGTH), is(nullValue())); + } + + @Test(expected = PostOnInvalidRequestURIException.class) + public void testPostWithInvalidRegexURI() throws Exception { + reset(uploadStorageService); + when(uploadStorageService.getUploadURI()).thenReturn("/submission/([a-z0-9]+)/files/upload"); + when(uploadStorageService.create( + ArgumentMatchers.any(UploadInfo.class), nullable(String.class))) + .then( + new Answer() { + @Override + public UploadInfo answer(InvocationOnMock invocation) throws Throwable { + UploadInfo upload = invocation.getArgument(0); + upload.setId(id); + + when(uploadStorageService.getUploadInfo( + "/submission/0ae5f8vv4s8c/files/upload/" + id.toString(), + (String) invocation.getArgument(1))) + .thenReturn(upload); + return upload; + } + }); + + // Create upload + servletRequest.setRequestURI("/submission/a+b/files/upload"); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 9); + servletRequest.addHeader(HttpHeader.UPLOAD_METADATA, "submission metadata"); + + executeCall(HttpMethod.POST, false); + } +} diff --git a/src/test/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidatorTest.java b/src/test/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidatorTest.java index 0b20c9e..b749559 100644 --- a/src/test/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidatorTest.java +++ b/src/test/java/me/desair/tus/server/creation/validation/PostEmptyRequestValidatorTest.java @@ -13,64 +13,64 @@ public class PostEmptyRequestValidatorTest { - private PostEmptyRequestValidator validator; - - private MockHttpServletRequest servletRequest; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new PostEmptyRequestValidator(); - } - - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(false)); - assertThat(validator.supports(null), is(false)); + private PostEmptyRequestValidator validator; + + private MockHttpServletRequest servletRequest; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new PostEmptyRequestValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(false)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void validateMissingContentLength() throws Exception { + // We don't set a content length header + // servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); + + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateMissingContentLength() throws Exception { - //We don't set a content length header - //servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 3L); + // No Exception is thrown + } - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateContentLengthZero() throws Exception { + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0L); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateContentLengthZero() throws Exception { - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 0L); + // No Exception is thrown + } - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + @Test(expected = InvalidContentLengthException.class) + public void validateContentLengthNotZero() throws Exception { + servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 10L); - //No Exception is thrown - } - - @Test(expected = InvalidContentLengthException.class) - public void validateContentLengthNotZero() throws Exception { - servletRequest.addHeader(HttpHeader.CONTENT_LENGTH, 10L); + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, null, null); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, null, null); - - //Expect a InvalidContentLengthException - } -} \ No newline at end of file + // Expect a InvalidContentLengthException + } +} diff --git a/src/test/java/me/desair/tus/server/creation/validation/PostURIValidatorTest.java b/src/test/java/me/desair/tus/server/creation/validation/PostURIValidatorTest.java index dce773d..6edb8e8 100644 --- a/src/test/java/me/desair/tus/server/creation/validation/PostURIValidatorTest.java +++ b/src/test/java/me/desair/tus/server/creation/validation/PostURIValidatorTest.java @@ -18,86 +18,85 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class PostURIValidatorTest { - private PostURIValidator validator; - - private MockHttpServletRequest servletRequest; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new PostURIValidator(); + private PostURIValidator validator; + + private MockHttpServletRequest servletRequest; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new PostURIValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(false)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void validateMatchingUrl() throws Exception { + servletRequest.setRequestURI("/test/upload"); + when(uploadStorageService.getUploadURI()).thenReturn("/test/upload"); + + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(false)); - assertThat(validator.supports(null), is(false)); - } - - @Test - public void validateMatchingUrl() throws Exception { - servletRequest.setRequestURI("/test/upload"); - when(uploadStorageService.getUploadURI()).thenReturn("/test/upload"); + // No Exception is thrown + } - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test(expected = PostOnInvalidRequestURIException.class) + public void validateInvalidUrl() throws Exception { + servletRequest.setRequestURI("/test/upload/12"); + when(uploadStorageService.getUploadURI()).thenReturn("/test/upload"); - //No Exception is thrown - } + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - @Test(expected = PostOnInvalidRequestURIException.class) - public void validateInvalidUrl() throws Exception { - servletRequest.setRequestURI("/test/upload/12"); - when(uploadStorageService.getUploadURI()).thenReturn("/test/upload"); + // Expect PostOnInvalidRequestURIException + } - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + @Test + public void validateMatchingRegexUrl() throws Exception { + servletRequest.setRequestURI("/users/1234/files/upload"); + when(uploadStorageService.getUploadURI()).thenReturn("/users/[0-9]+/files/upload"); - //Expect PostOnInvalidRequestURIException + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateMatchingRegexUrl() throws Exception { - servletRequest.setRequestURI("/users/1234/files/upload"); - when(uploadStorageService.getUploadURI()).thenReturn("/users/[0-9]+/files/upload"); + // No Exception is thrown + } - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test(expected = PostOnInvalidRequestURIException.class) + public void validateInvalidRegexUrl() throws Exception { + servletRequest.setRequestURI("/users/abc123/files/upload"); + when(uploadStorageService.getUploadURI()).thenReturn("/users/[0-9]+/files/upload"); - //No Exception is thrown - } - - @Test(expected = PostOnInvalidRequestURIException.class) - public void validateInvalidRegexUrl() throws Exception { - servletRequest.setRequestURI("/users/abc123/files/upload"); - when(uploadStorageService.getUploadURI()).thenReturn("/users/[0-9]+/files/upload"); + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + // Expect PostOnInvalidRequestURIException + } - //Expect PostOnInvalidRequestURIException - } + @Test(expected = PostOnInvalidRequestURIException.class) + public void validateInvalidRegexUrlPatchUrl() throws Exception { + servletRequest.setRequestURI("/users/1234/files/upload/7669c72a-3f2a-451f-a3b9-9210e7a4c02f"); + when(uploadStorageService.getUploadURI()).thenReturn("/users/[0-9]+/files/upload"); - @Test(expected = PostOnInvalidRequestURIException.class) - public void validateInvalidRegexUrlPatchUrl() throws Exception { - servletRequest.setRequestURI("/users/1234/files/upload/7669c72a-3f2a-451f-a3b9-9210e7a4c02f"); - when(uploadStorageService.getUploadURI()).thenReturn("/users/[0-9]+/files/upload"); + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - - //Expect PostOnInvalidRequestURIException - } -} \ No newline at end of file + // Expect PostOnInvalidRequestURIException + } +} diff --git a/src/test/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidatorTest.java b/src/test/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidatorTest.java index aa43a6d..a4dc42b 100644 --- a/src/test/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidatorTest.java +++ b/src/test/java/me/desair/tus/server/creation/validation/UploadDeferLengthValidatorTest.java @@ -12,115 +12,114 @@ import org.springframework.mock.web.MockHttpServletRequest; /** - * The request MUST include one of the following headers: - * a) Upload-Length to indicate the size of an entire upload in bytes. - * b) Upload-Defer-Length: 1 if upload size is not known at the time. + * The request MUST include one of the following headers: a) Upload-Length to indicate the size of + * an entire upload in bytes. b) Upload-Defer-Length: 1 if upload size is not known at the time. */ public class UploadDeferLengthValidatorTest { - private UploadDeferLengthValidator validator; - - private MockHttpServletRequest servletRequest; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new UploadDeferLengthValidator(); - } - - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(false)); - assertThat(validator.supports(null), is(false)); + private UploadDeferLengthValidator validator; + + private MockHttpServletRequest servletRequest; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new UploadDeferLengthValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(false)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void validateUploadLengthPresent() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateUploadLengthPresent() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // No Exception is thrown + } - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateUploadDeferLength1Present() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - //No Exception is thrown + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateUploadDeferLength1Present() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + // No Exception is thrown + } - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + @Test(expected = InvalidUploadLengthException.class) + public void validateUploadLengthAndUploadDeferLength1Present() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - //No Exception is thrown - } + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, null, null); - @Test(expected = InvalidUploadLengthException.class) - public void validateUploadLengthAndUploadDeferLength1Present() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 1); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // Expect an InvalidUploadLengthException + } - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, null, null); + @Test(expected = InvalidUploadLengthException.class) + public void validateUploadDeferLengthNot1() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 2); - //Expect an InvalidUploadLengthException - } + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, null, null); - @Test(expected = InvalidUploadLengthException.class) - public void validateUploadDeferLengthNot1() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_DEFER_LENGTH, 2); + // Expect an InvalidUploadLengthException + } - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, null, null); + @Test(expected = InvalidUploadLengthException.class) + public void validateUploadLengthNotPresent() throws Exception { + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - //Expect an InvalidUploadLengthException - } + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, null, null); - @Test(expected = InvalidUploadLengthException.class) - public void validateUploadLengthNotPresent() throws Exception { - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // Expect an InvalidUploadLengthException + } - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, null, null); + @Test + public void validateUploadLengthNotPresentOnFinal() throws Exception { + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;1234 5678"); - //Expect an InvalidUploadLengthException + // When we validate the request + try { + validator.validate(HttpMethod.POST, servletRequest, null, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateUploadLengthNotPresentOnFinal() throws Exception { - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - servletRequest.addHeader(HttpHeader.UPLOAD_CONCAT, "final;1234 5678"); - - //When we validate the request - try { - validator.validate(HttpMethod.POST, servletRequest, null, null); - } catch (Exception ex) { - fail(); - } + // No Exception is thrown + } - //No Exception is thrown - } + @Test(expected = InvalidUploadLengthException.class) + public void validateUploadLengthNotNumeric() throws Exception { + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "TEST"); - @Test(expected = InvalidUploadLengthException.class) - public void validateUploadLengthNotNumeric() throws Exception { - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, "TEST"); + // When we validate the request + validator.validate(HttpMethod.POST, servletRequest, null, null); - //When we validate the request - validator.validate(HttpMethod.POST, servletRequest, null, null); - - //Expect an InvalidUploadLengthException - } -} \ No newline at end of file + // Expect an InvalidUploadLengthException + } +} diff --git a/src/test/java/me/desair/tus/server/creation/validation/UploadLengthValidatorTest.java b/src/test/java/me/desair/tus/server/creation/validation/UploadLengthValidatorTest.java index d3f9a54..ace0d25 100644 --- a/src/test/java/me/desair/tus/server/creation/validation/UploadLengthValidatorTest.java +++ b/src/test/java/me/desair/tus/server/creation/validation/UploadLengthValidatorTest.java @@ -19,94 +19,93 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class UploadLengthValidatorTest { - private UploadLengthValidator validator; - - private MockHttpServletRequest servletRequest; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - validator = new UploadLengthValidator(); - } - - @Test - public void supports() throws Exception { - assertThat(validator.supports(HttpMethod.GET), is(false)); - assertThat(validator.supports(HttpMethod.POST), is(true)); - assertThat(validator.supports(HttpMethod.PUT), is(false)); - assertThat(validator.supports(HttpMethod.DELETE), is(false)); - assertThat(validator.supports(HttpMethod.HEAD), is(false)); - assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); - assertThat(validator.supports(HttpMethod.PATCH), is(false)); - assertThat(validator.supports(null), is(false)); + private UploadLengthValidator validator; + + private MockHttpServletRequest servletRequest; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + validator = new UploadLengthValidator(); + } + + @Test + public void supports() throws Exception { + assertThat(validator.supports(HttpMethod.GET), is(false)); + assertThat(validator.supports(HttpMethod.POST), is(true)); + assertThat(validator.supports(HttpMethod.PUT), is(false)); + assertThat(validator.supports(HttpMethod.DELETE), is(false)); + assertThat(validator.supports(HttpMethod.HEAD), is(false)); + assertThat(validator.supports(HttpMethod.OPTIONS), is(false)); + assertThat(validator.supports(HttpMethod.PATCH), is(false)); + assertThat(validator.supports(null), is(false)); + } + + @Test + public void validateNoMaxUploadLength() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(0L); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateNoMaxUploadLength() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(0L); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // No Exception is thrown + } - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateBelowMaxUploadLength() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(400L); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - //No Exception is thrown + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateBelowMaxUploadLength() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(400L); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // No Exception is thrown + } - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateEqualMaxUploadLength() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(300L); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - //No Exception is thrown + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateEqualMaxUploadLength() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(300L); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // No Exception is thrown + } - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test + public void validateNoUploadLength() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(300L); + // servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - //No Exception is thrown + try { + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); + } catch (Exception ex) { + fail(); } - @Test - public void validateNoUploadLength() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(300L); - //servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + // No Exception is thrown + } - try { - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - } catch (Exception ex) { - fail(); - } + @Test(expected = MaxUploadLengthExceededException.class) + public void validateAboveMaxUploadLength() throws Exception { + when(uploadStorageService.getMaxUploadSize()).thenReturn(200L); + servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); - //No Exception is thrown - } - - @Test(expected = MaxUploadLengthExceededException.class) - public void validateAboveMaxUploadLength() throws Exception { - when(uploadStorageService.getMaxUploadSize()).thenReturn(200L); - servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 300L); + validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - validator.validate(HttpMethod.POST, servletRequest, uploadStorageService, null); - - //Expect a MaxUploadLengthExceededException - } -} \ No newline at end of file + // Expect a MaxUploadLengthExceededException + } +} diff --git a/src/test/java/me/desair/tus/server/download/DownloadGetRequestHandlerTest.java b/src/test/java/me/desair/tus/server/download/DownloadGetRequestHandlerTest.java index 8750e60..35ff3ea 100644 --- a/src/test/java/me/desair/tus/server/download/DownloadGetRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/download/DownloadGetRequestHandlerTest.java @@ -9,10 +9,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import jakarta.servlet.http.HttpServletResponse; import java.io.OutputStream; import java.util.UUID; -import jakarta.servlet.http.HttpServletResponse; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.exception.UploadInProgressException; @@ -32,112 +31,138 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class DownloadGetRequestHandlerTest { - private DownloadGetRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new DownloadGetRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(true)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void testWithCompletedUploadWithMetadata() throws Exception { - final UploadId id = new UploadId(UUID.randomUUID()); - - UploadInfo info = new UploadInfo(); - info.setId(id); - info.setOffset(10L); - info.setLength(10L); - info.setEncodedMetadata("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw=="); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.GET, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)) - .copyUploadTo(any(UploadInfo.class), any(OutputStream.class)); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_OK)); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_LENGTH), is("10")); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_DISPOSITION), - is("attachment; filename=\"test.jpg\"; filename*=UTF-8''test.jpg")); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_TYPE), - is("image/jpeg")); - assertThat(servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), - is("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw==")); - - info.setEncodedMetadata("name TmHDr3ZlIGZpbGUudHh0,type dGV4dC9wbGFpbg=="); - handler.process(HttpMethod.GET, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_DISPOSITION), - is("attachment; filename=\"Naïve file.txt\"; filename*=UTF-8''Na%C3%AFve%20file.txt")); - } - - @Test - public void testWithCompletedUploadWithoutMetadata() throws Exception { - final UploadId id = new UploadId(UUID.randomUUID()); - - UploadInfo info = new UploadInfo(); - info.setId(id); - info.setOffset(10L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.GET, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)) - .copyUploadTo(any(UploadInfo.class), any(OutputStream.class)); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_OK)); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_LENGTH), is("10")); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_DISPOSITION), - is("attachment; filename=\"" + id.toString() + "\"; filename*=UTF-8''" + id.toString())); - assertThat(servletResponse.getHeader(HttpHeader.CONTENT_TYPE), is("application/octet-stream")); - } - - @Test(expected = UploadInProgressException.class) - public void testWithInProgressUpload() throws Exception { - final UploadId id = new UploadId(UUID.randomUUID()); - - UploadInfo info = new UploadInfo(); - info.setId(id); - info.setOffset(8L); - info.setLength(10L); - info.setEncodedMetadata("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw=="); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.GET, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - } - - @Test(expected = UploadInProgressException.class) - public void testWithUnknownUpload() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); - - handler.process(HttpMethod.GET, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).copyUploadTo(any(UploadInfo.class), any(OutputStream.class)); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - -} \ No newline at end of file + private DownloadGetRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new DownloadGetRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(true)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void testWithCompletedUploadWithMetadata() throws Exception { + final UploadId id = new UploadId(UUID.randomUUID()); + + UploadInfo info = new UploadInfo(); + info.setId(id); + info.setOffset(10L); + info.setLength(10L); + info.setEncodedMetadata("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw=="); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.GET, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)) + .copyUploadTo(any(UploadInfo.class), any(OutputStream.class)); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_OK)); + assertThat(servletResponse.getHeader(HttpHeader.CONTENT_LENGTH), is("10")); + assertThat( + servletResponse.getHeader(HttpHeader.CONTENT_DISPOSITION), + is("attachment; filename=\"test.jpg\"; filename*=UTF-8''test.jpg")); + assertThat(servletResponse.getHeader(HttpHeader.CONTENT_TYPE), is("image/jpeg")); + assertThat( + servletResponse.getHeader(HttpHeader.UPLOAD_METADATA), + is("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw==")); + + info.setEncodedMetadata("name TmHDr3ZlIGZpbGUudHh0,type dGV4dC9wbGFpbg=="); + handler.process( + HttpMethod.GET, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + assertThat( + servletResponse.getHeader(HttpHeader.CONTENT_DISPOSITION), + is("attachment; filename=\"Naïve file.txt\"; filename*=UTF-8''Na%C3%AFve%20file.txt")); + } + + @Test + public void testWithCompletedUploadWithoutMetadata() throws Exception { + final UploadId id = new UploadId(UUID.randomUUID()); + + UploadInfo info = new UploadInfo(); + info.setId(id); + info.setOffset(10L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.GET, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)) + .copyUploadTo(any(UploadInfo.class), any(OutputStream.class)); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_OK)); + assertThat(servletResponse.getHeader(HttpHeader.CONTENT_LENGTH), is("10")); + assertThat( + servletResponse.getHeader(HttpHeader.CONTENT_DISPOSITION), + is("attachment; filename=\"" + id.toString() + "\"; filename*=UTF-8''" + id.toString())); + assertThat(servletResponse.getHeader(HttpHeader.CONTENT_TYPE), is("application/octet-stream")); + } + + @Test(expected = UploadInProgressException.class) + public void testWithInProgressUpload() throws Exception { + final UploadId id = new UploadId(UUID.randomUUID()); + + UploadInfo info = new UploadInfo(); + info.setId(id); + info.setOffset(8L); + info.setLength(10L); + info.setEncodedMetadata("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw=="); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.GET, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + } + + @Test(expected = UploadInProgressException.class) + public void testWithUnknownUpload() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); + + handler.process( + HttpMethod.GET, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()) + .copyUploadTo(any(UploadInfo.class), any(OutputStream.class)); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } +} diff --git a/src/test/java/me/desair/tus/server/download/DownloadOptionsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/download/DownloadOptionsRequestHandlerTest.java index 4c6f86a..38c9475 100644 --- a/src/test/java/me/desair/tus/server/download/DownloadOptionsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/download/DownloadOptionsRequestHandlerTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import java.util.Arrays; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.util.TusServletRequest; @@ -17,39 +16,43 @@ public class DownloadOptionsRequestHandlerTest { - private DownloadOptionsRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new DownloadOptionsRequestHandler(); - } - - @Test - public void processListExtensions() throws Exception { - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), null, null); - - assertThat(Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), - containsInAnyOrder("download")); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private DownloadOptionsRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new DownloadOptionsRequestHandler(); + } + + @Test + public void processListExtensions() throws Exception { + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + null, + null); + + assertThat( + Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), + containsInAnyOrder("download")); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandlerTest.java index adf182c..6a366e7 100644 --- a/src/test/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/expiration/ExpirationOptionsRequestHandlerTest.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import java.util.Arrays; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.util.TusServletRequest; @@ -15,42 +14,45 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; - public class ExpirationOptionsRequestHandlerTest { - private ExpirationOptionsRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new ExpirationOptionsRequestHandler(); - } - - @Test - public void processListExtensions() throws Exception { - - handler.process(HttpMethod.OPTIONS, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), null, null); - - assertThat(Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), - containsInAnyOrder("expiration")); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - -} \ No newline at end of file + private ExpirationOptionsRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new ExpirationOptionsRequestHandler(); + } + + @Test + public void processListExtensions() throws Exception { + + handler.process( + HttpMethod.OPTIONS, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + null, + null); + + assertThat( + Arrays.asList(servletResponse.getHeader(HttpHeader.TUS_EXTENSION).split(",")), + containsInAnyOrder("expiration")); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(true)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } +} diff --git a/src/test/java/me/desair/tus/server/expiration/ExpirationRequestHandlerTest.java b/src/test/java/me/desair/tus/server/expiration/ExpirationRequestHandlerTest.java index a3a9477..662e05a 100644 --- a/src/test/java/me/desair/tus/server/expiration/ExpirationRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/expiration/ExpirationRequestHandlerTest.java @@ -13,7 +13,6 @@ import java.text.ParseException; import java.util.Locale; import java.util.TimeZone; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadInfo; @@ -33,157 +32,191 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ExpirationRequestHandlerTest { - private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", - TimeZone.getTimeZone(TimeZones.GMT_ID), Locale.US); - - private ExpirationRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new ExpirationRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(true)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(false)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(true)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void testCreatedUpload() throws Exception { - UploadInfo info = createUploadInfo(); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - tusResponse.setHeader(HttpHeader.LOCATION, "/tus/upload/12345"); - handler.process(HttpMethod.POST, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - verify(uploadStorageService, times(1)).update(info); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is("1516617791000")); - } - - @Test - public void testInProgressUpload() throws Exception { - UploadInfo info = createUploadInfo(); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - verify(uploadStorageService, times(1)).update(info); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is("1516617791000")); - } - - @Test - public void testNoUpload() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - verify(uploadStorageService, never()).update(any(UploadInfo.class)); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); - } - - @Test - public void testFinishedUpload() throws Exception { - UploadInfo info = createUploadInfo(); - info.setOffset(10L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - //Upload Expires header must always be set - verify(uploadStorageService, times(1)).update(info); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is("1516617791000")); - } - - @Test - public void testNullExpiration() throws Exception { - UploadInfo info = createUploadInfo(); - info.setOffset(8L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(null); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - verify(uploadStorageService, never()).update(any(UploadInfo.class)); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); - } - - @Test - public void testZeroExpiration() throws Exception { - UploadInfo info = createUploadInfo(); - info.setOffset(8L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(0L); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - verify(uploadStorageService, never()).update(any(UploadInfo.class)); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); - } - - @Test - public void testNegativeExpiration() throws Exception { - UploadInfo info = createUploadInfo(); - info.setOffset(8L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(-10L); - - TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); - handler.process(HttpMethod.PATCH, new TusServletRequest(servletRequest), - tusResponse, uploadStorageService, null); - - verify(uploadStorageService, never()).update(any(UploadInfo.class)); - assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); - } - - private UploadInfo createUploadInfo() { - return new UploadInfo() { - @Override - protected long getCurrentTime() { - try { - return DATE_FORMAT.parse("2018-01-20 10:43:11").getTime(); - } catch (ParseException e) { - return 0L; - } - } - }; - } - -} \ No newline at end of file + private static final FastDateFormat DATE_FORMAT = + FastDateFormat.getInstance( + "yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone(TimeZones.GMT_ID), Locale.US); + + private ExpirationRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new ExpirationRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(true)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(false)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(true)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void testCreatedUpload() throws Exception { + UploadInfo info = createUploadInfo(); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + tusResponse.setHeader(HttpHeader.LOCATION, "/tus/upload/12345"); + handler.process( + HttpMethod.POST, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + verify(uploadStorageService, times(1)).update(info); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is("1516617791000")); + } + + @Test + public void testInProgressUpload() throws Exception { + UploadInfo info = createUploadInfo(); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + verify(uploadStorageService, times(1)).update(info); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is("1516617791000")); + } + + @Test + public void testNoUpload() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(any(UploadInfo.class)); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); + } + + @Test + public void testFinishedUpload() throws Exception { + UploadInfo info = createUploadInfo(); + info.setOffset(10L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(172800000L); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + // Upload Expires header must always be set + verify(uploadStorageService, times(1)).update(info); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is("1516617791000")); + } + + @Test + public void testNullExpiration() throws Exception { + UploadInfo info = createUploadInfo(); + info.setOffset(8L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(null); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(any(UploadInfo.class)); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); + } + + @Test + public void testZeroExpiration() throws Exception { + UploadInfo info = createUploadInfo(); + info.setOffset(8L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(0L); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(any(UploadInfo.class)); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); + } + + @Test + public void testNegativeExpiration() throws Exception { + UploadInfo info = createUploadInfo(); + info.setOffset(8L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(-10L); + + TusServletResponse tusResponse = new TusServletResponse(this.servletResponse); + handler.process( + HttpMethod.PATCH, + new TusServletRequest(servletRequest), + tusResponse, + uploadStorageService, + null); + + verify(uploadStorageService, never()).update(any(UploadInfo.class)); + assertThat(tusResponse.getHeader(HttpHeader.UPLOAD_EXPIRES), is(nullValue())); + } + + private UploadInfo createUploadInfo() { + return new UploadInfo() { + @Override + protected long getCurrentTime() { + try { + return DATE_FORMAT.parse("2018-01-20 10:43:11").getTime(); + } catch (ParseException e) { + return 0L; + } + } + }; + } +} diff --git a/src/test/java/me/desair/tus/server/termination/TerminationDeleteRequestHandlerTest.java b/src/test/java/me/desair/tus/server/termination/TerminationDeleteRequestHandlerTest.java index d52f547..54521e9 100644 --- a/src/test/java/me/desair/tus/server/termination/TerminationDeleteRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/termination/TerminationDeleteRequestHandlerTest.java @@ -9,9 +9,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.UUID; import jakarta.servlet.http.HttpServletResponse; - +import java.util.UUID; import me.desair.tus.server.HttpMethod; import me.desair.tus.server.upload.UploadId; import me.desair.tus.server.upload.UploadInfo; @@ -29,60 +28,68 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class TerminationDeleteRequestHandlerTest { - private TerminationDeleteRequestHandler handler; - - private MockHttpServletRequest servletRequest; - - private MockHttpServletResponse servletResponse; - - @Mock - private UploadStorageService uploadStorageService; - - @Before - public void setUp() { - servletRequest = new MockHttpServletRequest(); - servletResponse = new MockHttpServletResponse(); - handler = new TerminationDeleteRequestHandler(); - } - - @Test - public void supports() throws Exception { - assertThat(handler.supports(HttpMethod.GET), is(false)); - assertThat(handler.supports(HttpMethod.POST), is(false)); - assertThat(handler.supports(HttpMethod.PUT), is(false)); - assertThat(handler.supports(HttpMethod.DELETE), is(true)); - assertThat(handler.supports(HttpMethod.HEAD), is(false)); - assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); - assertThat(handler.supports(HttpMethod.PATCH), is(false)); - assertThat(handler.supports(null), is(false)); - } - - @Test - public void testWithNotExistingUpload() throws Exception { - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(null); - - handler.process(HttpMethod.DELETE, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, never()).terminateUpload(any(UploadInfo.class)); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - - @Test - public void testWithExistingUpload() throws Exception { - final UploadId id = new UploadId(UUID.randomUUID()); - - UploadInfo info = new UploadInfo(); - info.setId(id); - info.setOffset(2L); - info.setLength(10L); - when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))).thenReturn(info); - - handler.process(HttpMethod.DELETE, new TusServletRequest(servletRequest), - new TusServletResponse(servletResponse), uploadStorageService, null); - - verify(uploadStorageService, times(1)).terminateUpload(info); - assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); - } - -} \ No newline at end of file + private TerminationDeleteRequestHandler handler; + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + @Mock private UploadStorageService uploadStorageService; + + @Before + public void setUp() { + servletRequest = new MockHttpServletRequest(); + servletResponse = new MockHttpServletResponse(); + handler = new TerminationDeleteRequestHandler(); + } + + @Test + public void supports() throws Exception { + assertThat(handler.supports(HttpMethod.GET), is(false)); + assertThat(handler.supports(HttpMethod.POST), is(false)); + assertThat(handler.supports(HttpMethod.PUT), is(false)); + assertThat(handler.supports(HttpMethod.DELETE), is(true)); + assertThat(handler.supports(HttpMethod.HEAD), is(false)); + assertThat(handler.supports(HttpMethod.OPTIONS), is(false)); + assertThat(handler.supports(HttpMethod.PATCH), is(false)); + assertThat(handler.supports(null), is(false)); + } + + @Test + public void testWithNotExistingUpload() throws Exception { + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(null); + + handler.process( + HttpMethod.DELETE, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, never()).terminateUpload(any(UploadInfo.class)); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } + + @Test + public void testWithExistingUpload() throws Exception { + final UploadId id = new UploadId(UUID.randomUUID()); + + UploadInfo info = new UploadInfo(); + info.setId(id); + info.setOffset(2L); + info.setLength(10L); + when(uploadStorageService.getUploadInfo(nullable(String.class), nullable(String.class))) + .thenReturn(info); + + handler.process( + HttpMethod.DELETE, + new TusServletRequest(servletRequest), + new TusServletResponse(servletResponse), + uploadStorageService, + null); + + verify(uploadStorageService, times(1)).terminateUpload(info); + assertThat(servletResponse.getStatus(), is(HttpServletResponse.SC_NO_CONTENT)); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/TimeBasedUploadIdFactoryTest.java b/src/test/java/me/desair/tus/server/upload/TimeBasedUploadIdFactoryTest.java index 0904455..8d2c6ae 100644 --- a/src/test/java/me/desair/tus/server/upload/TimeBasedUploadIdFactoryTest.java +++ b/src/test/java/me/desair/tus/server/upload/TimeBasedUploadIdFactoryTest.java @@ -14,100 +14,100 @@ public class TimeBasedUploadIdFactoryTest { - private UploadIdFactory idFactory; - - @Before - public void setUp() { - idFactory = new TimeBasedUploadIdFactory(); - } - - @Test(expected = NullPointerException.class) - public void setUploadURINull() throws Exception { - idFactory.setUploadURI(null); - } - - @Test - public void setUploadURINoTrailingSlash() throws Exception { - idFactory.setUploadURI("/test/upload"); - assertThat(idFactory.getUploadURI(), is("/test/upload")); - } - - @Test - public void setUploadURIWithTrailingSlash() throws Exception { - idFactory.setUploadURI("/test/upload/"); - assertThat(idFactory.getUploadURI(), is("/test/upload/")); - } - - @Test(expected = IllegalArgumentException.class) - public void setUploadURIBlank() throws Exception { - idFactory.setUploadURI(" "); - } - - @Test(expected = IllegalArgumentException.class) - public void setUploadURINoStartingSlash() throws Exception { - idFactory.setUploadURI("test/upload/"); - } - - @Test(expected = IllegalArgumentException.class) - public void setUploadURIEndsWithDollar() throws Exception { - idFactory.setUploadURI("/test/upload$"); - } - - @Test - public void readUploadId() throws Exception { - idFactory.setUploadURI("/test/upload"); - - assertThat(idFactory.readUploadId("/test/upload/1546152320043"), - hasToString("1546152320043")); - } - - @Test - public void readUploadIdRegex() throws Exception { - idFactory.setUploadURI("/users/[0-9]+/files/upload"); - - assertThat(idFactory.readUploadId("/users/1337/files/upload/1546152320043"), - hasToString("1546152320043")); - } - - @Test - public void readUploadIdTrailingSlash() throws Exception { - idFactory.setUploadURI("/test/upload/"); - - assertThat(idFactory.readUploadId("/test/upload/1546152320043"), - hasToString("1546152320043")); - } - - @Test - public void readUploadIdRegexTrailingSlash() throws Exception { - idFactory.setUploadURI("/users/[0-9]+/files/upload/"); - - assertThat(idFactory.readUploadId("/users/123456789/files/upload/1546152320043"), - hasToString("1546152320043")); - } - - @Test - public void readUploadIdNoUUID() throws Exception { - idFactory.setUploadURI("/test/upload"); - - assertThat(idFactory.readUploadId("/test/upload/not-a-time-value"), is(nullValue())); - } - - @Test - public void readUploadIdRegexNoMatch() throws Exception { - idFactory.setUploadURI("/users/[0-9]+/files/upload"); - - assertThat(idFactory.readUploadId("/users/files/upload/1546152320043"), - is(nullValue())); - } - - @Test - public void createId() throws Exception { - UploadId id = idFactory.createId(); - assertThat(id, not(nullValue())); - Utils.sleep(10); - assertThat(Long.parseLong(id.getOriginalObject().toString()), - greaterThan(System.currentTimeMillis() - 1000L)); - assertThat(Long.parseLong(id.getOriginalObject().toString()), - lessThan(System.currentTimeMillis())); - } -} \ No newline at end of file + private UploadIdFactory idFactory; + + @Before + public void setUp() { + idFactory = new TimeBasedUploadIdFactory(); + } + + @Test(expected = NullPointerException.class) + public void setUploadURINull() throws Exception { + idFactory.setUploadURI(null); + } + + @Test + public void setUploadURINoTrailingSlash() throws Exception { + idFactory.setUploadURI("/test/upload"); + assertThat(idFactory.getUploadURI(), is("/test/upload")); + } + + @Test + public void setUploadURIWithTrailingSlash() throws Exception { + idFactory.setUploadURI("/test/upload/"); + assertThat(idFactory.getUploadURI(), is("/test/upload/")); + } + + @Test(expected = IllegalArgumentException.class) + public void setUploadURIBlank() throws Exception { + idFactory.setUploadURI(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void setUploadURINoStartingSlash() throws Exception { + idFactory.setUploadURI("test/upload/"); + } + + @Test(expected = IllegalArgumentException.class) + public void setUploadURIEndsWithDollar() throws Exception { + idFactory.setUploadURI("/test/upload$"); + } + + @Test + public void readUploadId() throws Exception { + idFactory.setUploadURI("/test/upload"); + + assertThat(idFactory.readUploadId("/test/upload/1546152320043"), hasToString("1546152320043")); + } + + @Test + public void readUploadIdRegex() throws Exception { + idFactory.setUploadURI("/users/[0-9]+/files/upload"); + + assertThat( + idFactory.readUploadId("/users/1337/files/upload/1546152320043"), + hasToString("1546152320043")); + } + + @Test + public void readUploadIdTrailingSlash() throws Exception { + idFactory.setUploadURI("/test/upload/"); + + assertThat(idFactory.readUploadId("/test/upload/1546152320043"), hasToString("1546152320043")); + } + + @Test + public void readUploadIdRegexTrailingSlash() throws Exception { + idFactory.setUploadURI("/users/[0-9]+/files/upload/"); + + assertThat( + idFactory.readUploadId("/users/123456789/files/upload/1546152320043"), + hasToString("1546152320043")); + } + + @Test + public void readUploadIdNoUUID() throws Exception { + idFactory.setUploadURI("/test/upload"); + + assertThat(idFactory.readUploadId("/test/upload/not-a-time-value"), is(nullValue())); + } + + @Test + public void readUploadIdRegexNoMatch() throws Exception { + idFactory.setUploadURI("/users/[0-9]+/files/upload"); + + assertThat(idFactory.readUploadId("/users/files/upload/1546152320043"), is(nullValue())); + } + + @Test + public void createId() throws Exception { + UploadId id = idFactory.createId(); + assertThat(id, not(nullValue())); + Utils.sleep(10); + assertThat( + Long.parseLong(id.getOriginalObject().toString()), + greaterThan(System.currentTimeMillis() - 1000L)); + assertThat( + Long.parseLong(id.getOriginalObject().toString()), lessThan(System.currentTimeMillis())); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/UUIDUploadIdFactoryTest.java b/src/test/java/me/desair/tus/server/upload/UUIDUploadIdFactoryTest.java index e7dd0a5..6f00470 100644 --- a/src/test/java/me/desair/tus/server/upload/UUIDUploadIdFactoryTest.java +++ b/src/test/java/me/desair/tus/server/upload/UUIDUploadIdFactoryTest.java @@ -11,95 +11,100 @@ public class UUIDUploadIdFactoryTest { - private UploadIdFactory idFactory; - - @Before - public void setUp() { - idFactory = new UUIDUploadIdFactory(); - } - - @Test(expected = NullPointerException.class) - public void setUploadURINull() throws Exception { - idFactory.setUploadURI(null); - } - - @Test - public void setUploadURINoTrailingSlash() throws Exception { - idFactory.setUploadURI("/test/upload"); - assertThat(idFactory.getUploadURI(), is("/test/upload")); - } - - @Test - public void setUploadURIWithTrailingSlash() throws Exception { - idFactory.setUploadURI("/test/upload/"); - assertThat(idFactory.getUploadURI(), is("/test/upload/")); - } - - @Test(expected = IllegalArgumentException.class) - public void setUploadURIBlank() throws Exception { - idFactory.setUploadURI(" "); - } - - @Test(expected = IllegalArgumentException.class) - public void setUploadURINoStartingSlash() throws Exception { - idFactory.setUploadURI("test/upload/"); - } - - @Test(expected = IllegalArgumentException.class) - public void setUploadURIEndsWithDollar() throws Exception { - idFactory.setUploadURI("/test/upload$"); - } - - @Test - public void readUploadId() throws Exception { - idFactory.setUploadURI("/test/upload"); - - assertThat(idFactory.readUploadId("/test/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), - hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - } - - @Test - public void readUploadIdRegex() throws Exception { - idFactory.setUploadURI("/users/[0-9]+/files/upload"); - - assertThat(idFactory.readUploadId("/users/1337/files/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), - hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - } - - @Test - public void readUploadIdTrailingSlash() throws Exception { - idFactory.setUploadURI("/test/upload/"); - - assertThat(idFactory.readUploadId("/test/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), - hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - } - - @Test - public void readUploadIdRegexTrailingSlash() throws Exception { - idFactory.setUploadURI("/users/[0-9]+/files/upload/"); - - assertThat(idFactory.readUploadId("/users/123456789/files/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), - hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - } - - @Test - public void readUploadIdNoUUID() throws Exception { - idFactory.setUploadURI("/test/upload"); - - assertThat(idFactory.readUploadId("/test/upload/not-a-uuid-value"), is(nullValue())); - } - - @Test - public void readUploadIdRegexNoMatch() throws Exception { - idFactory.setUploadURI("/users/[0-9]+/files/upload"); - - assertThat(idFactory.readUploadId("/users/files/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), - is(nullValue())); - } - - @Test - public void createId() throws Exception { - assertThat(idFactory.createId(), not(nullValue())); - } - -} \ No newline at end of file + private UploadIdFactory idFactory; + + @Before + public void setUp() { + idFactory = new UUIDUploadIdFactory(); + } + + @Test(expected = NullPointerException.class) + public void setUploadURINull() throws Exception { + idFactory.setUploadURI(null); + } + + @Test + public void setUploadURINoTrailingSlash() throws Exception { + idFactory.setUploadURI("/test/upload"); + assertThat(idFactory.getUploadURI(), is("/test/upload")); + } + + @Test + public void setUploadURIWithTrailingSlash() throws Exception { + idFactory.setUploadURI("/test/upload/"); + assertThat(idFactory.getUploadURI(), is("/test/upload/")); + } + + @Test(expected = IllegalArgumentException.class) + public void setUploadURIBlank() throws Exception { + idFactory.setUploadURI(" "); + } + + @Test(expected = IllegalArgumentException.class) + public void setUploadURINoStartingSlash() throws Exception { + idFactory.setUploadURI("test/upload/"); + } + + @Test(expected = IllegalArgumentException.class) + public void setUploadURIEndsWithDollar() throws Exception { + idFactory.setUploadURI("/test/upload$"); + } + + @Test + public void readUploadId() throws Exception { + idFactory.setUploadURI("/test/upload"); + + assertThat( + idFactory.readUploadId("/test/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), + hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + } + + @Test + public void readUploadIdRegex() throws Exception { + idFactory.setUploadURI("/users/[0-9]+/files/upload"); + + assertThat( + idFactory.readUploadId("/users/1337/files/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), + hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + } + + @Test + public void readUploadIdTrailingSlash() throws Exception { + idFactory.setUploadURI("/test/upload/"); + + assertThat( + idFactory.readUploadId("/test/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), + hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + } + + @Test + public void readUploadIdRegexTrailingSlash() throws Exception { + idFactory.setUploadURI("/users/[0-9]+/files/upload/"); + + assertThat( + idFactory.readUploadId( + "/users/123456789/files/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), + hasToString("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + } + + @Test + public void readUploadIdNoUUID() throws Exception { + idFactory.setUploadURI("/test/upload"); + + assertThat(idFactory.readUploadId("/test/upload/not-a-uuid-value"), is(nullValue())); + } + + @Test + public void readUploadIdRegexNoMatch() throws Exception { + idFactory.setUploadURI("/users/[0-9]+/files/upload"); + + assertThat( + idFactory.readUploadId("/users/files/upload/1911e8a4-6939-490c-b58b-a5d70f8d91fb"), + is(nullValue())); + } + + @Test + public void createId() throws Exception { + assertThat(idFactory.createId(), not(nullValue())); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/UploadIdTest.java b/src/test/java/me/desair/tus/server/upload/UploadIdTest.java index 6253cd9..00458c6 100644 --- a/src/test/java/me/desair/tus/server/upload/UploadIdTest.java +++ b/src/test/java/me/desair/tus/server/upload/UploadIdTest.java @@ -5,81 +5,80 @@ import static org.junit.Assert.assertNotEquals; import java.util.UUID; - import org.junit.Test; public class UploadIdTest { - @Test - public void getOriginalObjectUUID() { - UUID id = UUID.randomUUID(); - UploadId uploadId = new UploadId(id); - assertEquals(id.toString(), uploadId.toString()); - assertEquals(id, uploadId.getOriginalObject()); - } - - @Test - public void getOriginalObjectLong() { - UploadId uploadId = new UploadId(1337L); - assertEquals("1337", uploadId.toString()); - assertEquals(1337L, uploadId.getOriginalObject()); - } - - @Test(expected = NullPointerException.class) - public void testNullConstructor() { - new UploadId(null); - } - - @Test(expected = IllegalArgumentException.class) - public void testBlankConstructor() { - new UploadId(" \t"); - } - - @Test - public void toStringNotYetUrlSafe() { - UploadId uploadId = new UploadId("my test id/1"); - assertEquals("my+test+id%2F1", uploadId.toString()); - } - - @Test - public void toStringNotYetUrlSafe2() { - UploadId uploadId = new UploadId("id+%2F1+/+1"); - assertEquals("id+%2F1+/+1", uploadId.toString()); - } - - @Test - public void toStringAlreadyUrlSafe() { - UploadId uploadId = new UploadId("my+test+id%2F1"); - assertEquals("my+test+id%2F1", uploadId.toString()); - } - - @Test - public void toStringWithInternalDecoderException() { - String test = "Invalid % value"; - UploadId id = new UploadId(test); - assertEquals("Invalid % value", id.toString()); - } - - @Test - public void equalsSameUrlSafeValue() { - UploadId id1 = new UploadId("id%2F1"); - UploadId id2 = new UploadId("id/1"); - UploadId id3 = new UploadId("id/1"); - - assertEquals(id1, id2); - assertEquals(id2, id3); - assertEquals(id1, id1); - assertNotEquals(id1, null); - assertFalse(id1.equals(UUID.randomUUID())); - } - - @Test - public void hashCodeSameUrlSafeValue() { - UploadId id1 = new UploadId("id%2F1"); - UploadId id2 = new UploadId("id/1"); - UploadId id3 = new UploadId("id/1"); - - assertEquals(id1.hashCode(), id2.hashCode()); - assertEquals(id2.hashCode(), id3.hashCode()); - } -} \ No newline at end of file + @Test + public void getOriginalObjectUUID() { + UUID id = UUID.randomUUID(); + UploadId uploadId = new UploadId(id); + assertEquals(id.toString(), uploadId.toString()); + assertEquals(id, uploadId.getOriginalObject()); + } + + @Test + public void getOriginalObjectLong() { + UploadId uploadId = new UploadId(1337L); + assertEquals("1337", uploadId.toString()); + assertEquals(1337L, uploadId.getOriginalObject()); + } + + @Test(expected = NullPointerException.class) + public void testNullConstructor() { + new UploadId(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testBlankConstructor() { + new UploadId(" \t"); + } + + @Test + public void toStringNotYetUrlSafe() { + UploadId uploadId = new UploadId("my test id/1"); + assertEquals("my+test+id%2F1", uploadId.toString()); + } + + @Test + public void toStringNotYetUrlSafe2() { + UploadId uploadId = new UploadId("id+%2F1+/+1"); + assertEquals("id+%2F1+/+1", uploadId.toString()); + } + + @Test + public void toStringAlreadyUrlSafe() { + UploadId uploadId = new UploadId("my+test+id%2F1"); + assertEquals("my+test+id%2F1", uploadId.toString()); + } + + @Test + public void toStringWithInternalDecoderException() { + String test = "Invalid % value"; + UploadId id = new UploadId(test); + assertEquals("Invalid % value", id.toString()); + } + + @Test + public void equalsSameUrlSafeValue() { + UploadId id1 = new UploadId("id%2F1"); + UploadId id2 = new UploadId("id/1"); + UploadId id3 = new UploadId("id/1"); + + assertEquals(id1, id2); + assertEquals(id2, id3); + assertEquals(id1, id1); + assertNotEquals(id1, null); + assertFalse(id1.equals(UUID.randomUUID())); + } + + @Test + public void hashCodeSameUrlSafeValue() { + UploadId id1 = new UploadId("id%2F1"); + UploadId id2 = new UploadId("id/1"); + UploadId id3 = new UploadId("id/1"); + + assertEquals(id1.hashCode(), id2.hashCode()); + assertEquals(id2.hashCode(), id3.hashCode()); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/UploadInfoTest.java b/src/test/java/me/desair/tus/server/upload/UploadInfoTest.java index ca695e4..8c36548 100644 --- a/src/test/java/me/desair/tus/server/upload/UploadInfoTest.java +++ b/src/test/java/me/desair/tus/server/upload/UploadInfoTest.java @@ -14,7 +14,6 @@ import java.text.ParseException; import java.util.Stack; import java.util.UUID; - import me.desair.tus.server.HttpHeader; import me.desair.tus.server.util.Utils; import org.apache.commons.lang3.time.DateFormatUtils; @@ -23,253 +22,261 @@ public class UploadInfoTest { - @Test - public void hasMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - info.setEncodedMetadata("Encoded Metadata"); - assertTrue(info.hasMetadata()); - } - - @Test - public void hasMetadataFalse() throws Exception { - UploadInfo info = new UploadInfo(); - info.setEncodedMetadata(null); - assertFalse(info.hasMetadata()); - } - - @Test - public void testGetMetadataMultipleValues() throws Exception { - UploadInfo info = new UploadInfo(); - info.setEncodedMetadata( - "filename d29ybGRfZG9taW5hdGlvbiBwbGFuLnBkZg==," + - "filesize MTEya2I=, " + - "mimetype \tYXBwbGljYXRpb24vcGRm , " + - "scanned , ,, " + - "user\t546L5LqU \t "); - - assertThat(info.getMetadata(), allOf(hasSize(5), - hasEntry("filename", "world_domination plan.pdf"), - hasEntry("filesize", "112kb"), - hasEntry("mimetype", "application/pdf"), - hasEntry("scanned", null), - hasEntry("user", "王五")) - ); - } - - @Test - public void testGetMetadataSingleValues() throws Exception { - UploadInfo info = new UploadInfo(); - info.setEncodedMetadata("filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); - - assertThat(info.getMetadata(), allOf(hasSize(1), - hasEntry("filename", "world_domination_plan.pdf")) - ); - } - - @Test - public void testGetMetadataNull() throws Exception { - UploadInfo info = new UploadInfo(); - info.setEncodedMetadata(null); - assertTrue(info.getMetadata().isEmpty()); - } - - @Test - public void hasLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - assertTrue(info.hasLength()); - } - - @Test - public void hasLengthFalse() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(null); - assertFalse(info.hasLength()); - } - - @Test - public void isUploadInProgressNoLengthNoOffset() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(null); - info.setOffset(null); - assertTrue(info.isUploadInProgress()); - } - - @Test - public void isUploadInProgressNoLengthWithOffset() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(null); - info.setOffset(10L); - assertTrue(info.isUploadInProgress()); - } - - @Test - public void isUploadInProgressOffsetDoesNotMatchLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - info.setOffset(8L); - assertTrue(info.isUploadInProgress()); - } - - @Test - public void isUploadInProgressOffsetMatchesLength() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - info.setOffset(10L); - assertFalse(info.isUploadInProgress()); - } - - @Test - public void testEquals() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setLength(10L); - info1.setOffset(5L); - info1.setEncodedMetadata("Encoded-Metadata"); - info1.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - UploadInfo info2 = new UploadInfo(); - info2.setLength(10L); - info2.setOffset(5L); - info2.setEncodedMetadata("Encoded-Metadata"); - info2.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - UploadInfo info3 = new UploadInfo(); - info3.setLength(9L); - info3.setOffset(5L); - info3.setEncodedMetadata("Encoded-Metadata"); - info3.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - UploadInfo info4 = new UploadInfo(); - info4.setLength(10L); - info4.setOffset(6L); - info4.setEncodedMetadata("Encoded-Metadata"); - info4.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - UploadInfo info5 = new UploadInfo(); - info5.setLength(10L); - info5.setOffset(5L); - info5.setEncodedMetadata("Encoded-Metadatas"); - info5.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - UploadInfo info6 = new UploadInfo(); - info6.setLength(10L); - info6.setOffset(5L); - info6.setEncodedMetadata("Encoded-Metadata"); - info6.setId(new UploadId("1911e8a4-6939-490c-c58b-a5d70f8d91fb")); - - assertTrue(info1.equals(info1)); - assertTrue(info1.equals(info2)); - assertFalse(info1.equals(null)); - assertFalse(info1.equals(new Object())); - assertFalse(info1.equals(info3)); - assertFalse(info1.equals(info4)); - assertFalse(info1.equals(info5)); - assertFalse(info1.equals(info6)); - } - - @Test - public void testHashCode() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setLength(10L); - info1.setOffset(5L); - info1.setEncodedMetadata("Encoded-Metadata"); - info1.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - UploadInfo info2 = new UploadInfo(); - info2.setLength(10L); - info2.setOffset(5L); - info2.setEncodedMetadata("Encoded-Metadata"); - info2.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); - - assertTrue(info1.hashCode() == info2.hashCode()); - } - - @Test - public void testGetNameAndTypeWithMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - info.setEncodedMetadata("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw=="); - - assertThat(info.getFileName(), is("test.jpg")); - assertThat(info.getFileMimeType(), is("image/jpeg")); - } - - @Test - public void testGetNameAndTypeWithoutMetadata() throws Exception { - UploadInfo info = new UploadInfo(); - final UploadId id = new UploadId(UUID.randomUUID()); - info.setId(id); - - assertThat(info.getFileName(), is(id.toString())); - assertThat(info.getFileMimeType(), is("application/octet-stream")); - } - - @Test - public void testExpiration() throws Exception { - UploadInfo info1 = new UploadInfo(); - assertFalse(info1.isExpired()); - - UploadInfo info2 = new UploadInfo() { - @Override - protected long getCurrentTime() { - try { - return DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:43:11").getTime(); - } catch (ParseException e) { - return 0L; - } + @Test + public void hasMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + info.setEncodedMetadata("Encoded Metadata"); + assertTrue(info.hasMetadata()); + } + + @Test + public void hasMetadataFalse() throws Exception { + UploadInfo info = new UploadInfo(); + info.setEncodedMetadata(null); + assertFalse(info.hasMetadata()); + } + + @Test + public void testGetMetadataMultipleValues() throws Exception { + UploadInfo info = new UploadInfo(); + info.setEncodedMetadata( + "filename d29ybGRfZG9taW5hdGlvbiBwbGFuLnBkZg==," + + "filesize MTEya2I=, " + + "mimetype \tYXBwbGljYXRpb24vcGRm , " + + "scanned , ,, " + + "user\t546L5LqU \t "); + + assertThat( + info.getMetadata(), + allOf( + hasSize(5), + hasEntry("filename", "world_domination plan.pdf"), + hasEntry("filesize", "112kb"), + hasEntry("mimetype", "application/pdf"), + hasEntry("scanned", null), + hasEntry("user", "王五"))); + } + + @Test + public void testGetMetadataSingleValues() throws Exception { + UploadInfo info = new UploadInfo(); + info.setEncodedMetadata("filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg=="); + + assertThat( + info.getMetadata(), allOf(hasSize(1), hasEntry("filename", "world_domination_plan.pdf"))); + } + + @Test + public void testGetMetadataNull() throws Exception { + UploadInfo info = new UploadInfo(); + info.setEncodedMetadata(null); + assertTrue(info.getMetadata().isEmpty()); + } + + @Test + public void hasLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + assertTrue(info.hasLength()); + } + + @Test + public void hasLengthFalse() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(null); + assertFalse(info.hasLength()); + } + + @Test + public void isUploadInProgressNoLengthNoOffset() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(null); + info.setOffset(null); + assertTrue(info.isUploadInProgress()); + } + + @Test + public void isUploadInProgressNoLengthWithOffset() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(null); + info.setOffset(10L); + assertTrue(info.isUploadInProgress()); + } + + @Test + public void isUploadInProgressOffsetDoesNotMatchLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + info.setOffset(8L); + assertTrue(info.isUploadInProgress()); + } + + @Test + public void isUploadInProgressOffsetMatchesLength() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + info.setOffset(10L); + assertFalse(info.isUploadInProgress()); + } + + @Test + public void testEquals() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setLength(10L); + info1.setOffset(5L); + info1.setEncodedMetadata("Encoded-Metadata"); + info1.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + UploadInfo info2 = new UploadInfo(); + info2.setLength(10L); + info2.setOffset(5L); + info2.setEncodedMetadata("Encoded-Metadata"); + info2.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + UploadInfo info3 = new UploadInfo(); + info3.setLength(9L); + info3.setOffset(5L); + info3.setEncodedMetadata("Encoded-Metadata"); + info3.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + UploadInfo info4 = new UploadInfo(); + info4.setLength(10L); + info4.setOffset(6L); + info4.setEncodedMetadata("Encoded-Metadata"); + info4.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + UploadInfo info5 = new UploadInfo(); + info5.setLength(10L); + info5.setOffset(5L); + info5.setEncodedMetadata("Encoded-Metadatas"); + info5.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + UploadInfo info6 = new UploadInfo(); + info6.setLength(10L); + info6.setOffset(5L); + info6.setEncodedMetadata("Encoded-Metadata"); + info6.setId(new UploadId("1911e8a4-6939-490c-c58b-a5d70f8d91fb")); + + assertTrue(info1.equals(info1)); + assertTrue(info1.equals(info2)); + assertFalse(info1.equals(null)); + assertFalse(info1.equals(new Object())); + assertFalse(info1.equals(info3)); + assertFalse(info1.equals(info4)); + assertFalse(info1.equals(info5)); + assertFalse(info1.equals(info6)); + } + + @Test + public void testHashCode() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setLength(10L); + info1.setOffset(5L); + info1.setEncodedMetadata("Encoded-Metadata"); + info1.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + UploadInfo info2 = new UploadInfo(); + info2.setLength(10L); + info2.setOffset(5L); + info2.setEncodedMetadata("Encoded-Metadata"); + info2.setId(new UploadId("1911e8a4-6939-490c-b58b-a5d70f8d91fb")); + + assertTrue(info1.hashCode() == info2.hashCode()); + } + + @Test + public void testGetNameAndTypeWithMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + info.setEncodedMetadata("name dGVzdC5qcGc=,type aW1hZ2UvanBlZw=="); + + assertThat(info.getFileName(), is("test.jpg")); + assertThat(info.getFileMimeType(), is("image/jpeg")); + } + + @Test + public void testGetNameAndTypeWithoutMetadata() throws Exception { + UploadInfo info = new UploadInfo(); + final UploadId id = new UploadId(UUID.randomUUID()); + info.setId(id); + + assertThat(info.getFileName(), is(id.toString())); + assertThat(info.getFileMimeType(), is("application/octet-stream")); + } + + @Test + public void testExpiration() throws Exception { + UploadInfo info1 = new UploadInfo(); + assertFalse(info1.isExpired()); + + UploadInfo info2 = + new UploadInfo() { + @Override + protected long getCurrentTime() { + try { + return DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT + .parse("2018-01-20T10:43:11") + .getTime(); + } catch (ParseException e) { + return 0L; } + } }; - info2.updateExpiration(172800000L); - assertFalse(info2.isExpired()); - - final Stack dateStack = new Stack<>(); - //Current time stamp to check expiration - dateStack.push(DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-23T10:43:11").getTime()); - //Current time stamp to calculate expiration - dateStack.push(DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:43:11").getTime()); - //Creation time stamp - dateStack.push(DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:40:39").getTime()); - - UploadInfo info3 = new UploadInfo() { - @Override - protected long getCurrentTime() { - return dateStack.pop(); - } + info2.updateExpiration(172800000L); + assertFalse(info2.isExpired()); + + final Stack dateStack = new Stack<>(); + // Current time stamp to check expiration + dateStack.push( + DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-23T10:43:11").getTime()); + // Current time stamp to calculate expiration + dateStack.push( + DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:43:11").getTime()); + // Creation time stamp + dateStack.push( + DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:40:39").getTime()); + + UploadInfo info3 = + new UploadInfo() { + @Override + protected long getCurrentTime() { + return dateStack.pop(); + } }; - info3.updateExpiration(172800000L); - assertTrue(info3.isExpired()); - } - - @Test - public void testGetCreationTimestamp() throws Exception { - UploadInfo info = new UploadInfo(); - Utils.sleep(10); - - assertThat(info.getCreationTimestamp(), greaterThan(System.currentTimeMillis() - 500L)); - assertThat(info.getCreationTimestamp(), lessThan(System.currentTimeMillis())); - } - - @Test - public void testGetCreatorIpAddressesNull() throws Exception { - UploadInfo info = new UploadInfo(); - assertThat(info.getCreatorIpAddresses(), nullValue()); - } - - @Test - public void testGetCreatorIpAddressesWithoutXForwardedFor() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setRemoteAddr("10.11.12.13"); - - UploadInfo info = new UploadInfo(servletRequest); - assertThat(info.getCreatorIpAddresses(), is("10.11.12.13")); - } - - @Test - public void testGetCreatorIpAddressesWithXForwardedFor() throws Exception { - MockHttpServletRequest servletRequest = new MockHttpServletRequest(); - servletRequest.setRemoteAddr("10.11.12.13"); - servletRequest.addHeader(HttpHeader.X_FORWARDED_FOR, "24.23.22.21, 192.168.1.1"); - - UploadInfo info = new UploadInfo(servletRequest); - assertThat(info.getCreatorIpAddresses(), is("24.23.22.21, 192.168.1.1, 10.11.12.13")); - } -} \ No newline at end of file + info3.updateExpiration(172800000L); + assertTrue(info3.isExpired()); + } + + @Test + public void testGetCreationTimestamp() throws Exception { + UploadInfo info = new UploadInfo(); + Utils.sleep(10); + + assertThat(info.getCreationTimestamp(), greaterThan(System.currentTimeMillis() - 500L)); + assertThat(info.getCreationTimestamp(), lessThan(System.currentTimeMillis())); + } + + @Test + public void testGetCreatorIpAddressesNull() throws Exception { + UploadInfo info = new UploadInfo(); + assertThat(info.getCreatorIpAddresses(), nullValue()); + } + + @Test + public void testGetCreatorIpAddressesWithoutXForwardedFor() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setRemoteAddr("10.11.12.13"); + + UploadInfo info = new UploadInfo(servletRequest); + assertThat(info.getCreatorIpAddresses(), is("10.11.12.13")); + } + + @Test + public void testGetCreatorIpAddressesWithXForwardedFor() throws Exception { + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setRemoteAddr("10.11.12.13"); + servletRequest.addHeader(HttpHeader.X_FORWARDED_FOR, "24.23.22.21, 192.168.1.1"); + + UploadInfo info = new UploadInfo(servletRequest); + assertThat(info.getCreatorIpAddresses(), is("24.23.22.21, 192.168.1.1, 10.11.12.13")); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumerationTest.java b/src/test/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumerationTest.java index 0fee31a..abec39f 100644 --- a/src/test/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumerationTest.java +++ b/src/test/java/me/desair/tus/server/upload/concatenation/UploadInputStreamEnumerationTest.java @@ -10,7 +10,6 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.UUID; - import me.desair.tus.server.upload.UploadId; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.upload.UploadStorageService; @@ -23,125 +22,134 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class UploadInputStreamEnumerationTest { - @Mock - private UploadStorageService uploadStorageService; - - @Test - public void hasMoreElements() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info2 = new UploadInfo(); - info2.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info3 = new UploadInfo(); - info3.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadedBytes(info1.getId())) - .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(info2.getId())) - .thenReturn(IOUtils.toInputStream("Upload 2", StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(info3.getId())) - .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); - - UploadInputStreamEnumeration uploadInputStreamEnumeration - = new UploadInputStreamEnumeration(Arrays.asList(info1, info2, info3), uploadStorageService); - - assertTrue(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals("Upload 1", IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); - assertTrue(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals("Upload 2", IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); - assertTrue(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals("Upload 3", IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - } - - @Test - public void hasMoreElementsException() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info2 = new UploadInfo(); - info2.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info3 = new UploadInfo(); - info3.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadedBytes(info1.getId())) - .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(info2.getId())) - .thenThrow(new IOException("Test")); - when(uploadStorageService.getUploadedBytes(info3.getId())) - .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); - - UploadInputStreamEnumeration uploadInputStreamEnumeration - = new UploadInputStreamEnumeration(Arrays.asList(info1, info2, info3), uploadStorageService); - - assertTrue(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals("Upload 1", IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals(null, uploadInputStreamEnumeration.nextElement()); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - } - - @Test - public void hasMoreElementsNotFound() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info2 = new UploadInfo(); - info2.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info3 = new UploadInfo(); - info3.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadedBytes(info1.getId())) - .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(info2.getId())) - .thenReturn(null); - when(uploadStorageService.getUploadedBytes(info3.getId())) - .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); - - UploadInputStreamEnumeration uploadInputStreamEnumeration - = new UploadInputStreamEnumeration(Arrays.asList(info1, info2, info3), uploadStorageService); - - assertTrue(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals("Upload 1", IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals(null, uploadInputStreamEnumeration.nextElement()); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - } - - @Test - public void hasMoreElementsNullElement() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setId(new UploadId(UUID.randomUUID())); - - UploadInfo info3 = new UploadInfo(); - info3.setId(new UploadId(UUID.randomUUID())); - - when(uploadStorageService.getUploadedBytes(info1.getId())) - .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(info3.getId())) - .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); - - UploadInputStreamEnumeration uploadInputStreamEnumeration - = new UploadInputStreamEnumeration(Arrays.asList(info1, null, info3), uploadStorageService); - - assertTrue(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals("Upload 1", IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals(null, uploadInputStreamEnumeration.nextElement()); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - } - - @Test - public void hasMoreElementsEmptyList() throws Exception { - UploadInputStreamEnumeration uploadInputStreamEnumeration - = new UploadInputStreamEnumeration(new LinkedList(), uploadStorageService); - - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - assertEquals(null, uploadInputStreamEnumeration.nextElement()); - assertFalse(uploadInputStreamEnumeration.hasMoreElements()); - } -} \ No newline at end of file + @Mock private UploadStorageService uploadStorageService; + + @Test + public void hasMoreElements() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info2 = new UploadInfo(); + info2.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info3 = new UploadInfo(); + info3.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadedBytes(info1.getId())) + .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(info2.getId())) + .thenReturn(IOUtils.toInputStream("Upload 2", StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(info3.getId())) + .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); + + UploadInputStreamEnumeration uploadInputStreamEnumeration = + new UploadInputStreamEnumeration(Arrays.asList(info1, info2, info3), uploadStorageService); + + assertTrue(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals( + "Upload 1", + IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); + assertTrue(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals( + "Upload 2", + IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); + assertTrue(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals( + "Upload 3", + IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + } + + @Test + public void hasMoreElementsException() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info2 = new UploadInfo(); + info2.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info3 = new UploadInfo(); + info3.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadedBytes(info1.getId())) + .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(info2.getId())).thenThrow(new IOException("Test")); + when(uploadStorageService.getUploadedBytes(info3.getId())) + .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); + + UploadInputStreamEnumeration uploadInputStreamEnumeration = + new UploadInputStreamEnumeration(Arrays.asList(info1, info2, info3), uploadStorageService); + + assertTrue(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals( + "Upload 1", + IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals(null, uploadInputStreamEnumeration.nextElement()); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + } + + @Test + public void hasMoreElementsNotFound() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info2 = new UploadInfo(); + info2.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info3 = new UploadInfo(); + info3.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadedBytes(info1.getId())) + .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(info2.getId())).thenReturn(null); + when(uploadStorageService.getUploadedBytes(info3.getId())) + .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); + + UploadInputStreamEnumeration uploadInputStreamEnumeration = + new UploadInputStreamEnumeration(Arrays.asList(info1, info2, info3), uploadStorageService); + + assertTrue(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals( + "Upload 1", + IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals(null, uploadInputStreamEnumeration.nextElement()); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + } + + @Test + public void hasMoreElementsNullElement() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setId(new UploadId(UUID.randomUUID())); + + UploadInfo info3 = new UploadInfo(); + info3.setId(new UploadId(UUID.randomUUID())); + + when(uploadStorageService.getUploadedBytes(info1.getId())) + .thenReturn(IOUtils.toInputStream("Upload 1", StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(info3.getId())) + .thenReturn(IOUtils.toInputStream("Upload 3", StandardCharsets.UTF_8)); + + UploadInputStreamEnumeration uploadInputStreamEnumeration = + new UploadInputStreamEnumeration(Arrays.asList(info1, null, info3), uploadStorageService); + + assertTrue(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals( + "Upload 1", + IOUtils.toString(uploadInputStreamEnumeration.nextElement(), StandardCharsets.UTF_8)); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals(null, uploadInputStreamEnumeration.nextElement()); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + } + + @Test + public void hasMoreElementsEmptyList() throws Exception { + UploadInputStreamEnumeration uploadInputStreamEnumeration = + new UploadInputStreamEnumeration(new LinkedList(), uploadStorageService); + + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + assertEquals(null, uploadInputStreamEnumeration.nextElement()); + assertFalse(uploadInputStreamEnumeration.hasMoreElements()); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationServiceTest.java b/src/test/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationServiceTest.java index c6676ef..d75b78c 100644 --- a/src/test/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationServiceTest.java +++ b/src/test/java/me/desair/tus/server/upload/concatenation/VirtualConcatenationServiceTest.java @@ -12,7 +12,6 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.UUID; - import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.UploadId; import me.desair.tus.server.upload.UploadInfo; @@ -28,300 +27,318 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class VirtualConcatenationServiceTest { - @Mock - private UploadStorageService uploadStorageService; - - private VirtualConcatenationService concatenationService; - - @Before - public void setUp() { - concatenationService = new VirtualConcatenationService(uploadStorageService); - } - - @Test - public void merge() throws Exception { - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength(5L); - child1.setOffset(5L); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength(10L); - child2.setOffset(10L); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child2); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - concatenationService.merge(infoParent); - - assertThat(infoParent.getLength(), is(15L)); - assertThat(infoParent.getOffset(), is(15L)); - assertThat(infoParent.isUploadInProgress(), is(false)); - - verify(uploadStorageService, times(1)).update(infoParent); - } - - @Test - public void mergeNotCompleted() throws Exception { - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength(5L); - child1.setOffset(5L); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength(10L); - child2.setOffset(8L); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child2); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - concatenationService.merge(infoParent); - - assertThat(infoParent.getLength(), is(15L)); - assertThat(infoParent.getOffset(), is(0L)); - assertThat(infoParent.isUploadInProgress(), is(true)); - - verify(uploadStorageService, times(1)).update(infoParent); - } - - @Test - public void mergeWithoutLength() throws Exception { - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength(null); - child1.setOffset(5L); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength(null); - child2.setOffset(8L); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child2); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - concatenationService.merge(infoParent); - - assertThat(infoParent.getLength(), is(nullValue())); - assertThat(infoParent.getOffset(), is(0L)); - assertThat(infoParent.isUploadInProgress(), is(true)); - - verify(uploadStorageService, never()).update(infoParent); - } - - @Test(expected = UploadNotFoundException.class) - public void mergeNotFound() throws Exception { - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength(5L); - child1.setOffset(5L); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength(10L); - child2.setOffset(10L); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(null); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - concatenationService.merge(infoParent); - } - - @Test - public void mergeWithExpiration() throws Exception { - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength(5L); - child1.setOffset(5L); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength(10L); - child2.setOffset(8L); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child2); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(500L); - - concatenationService.merge(infoParent); - - assertThat(infoParent.getLength(), is(15L)); - assertThat(infoParent.getOffset(), is(0L)); - assertThat(infoParent.isUploadInProgress(), is(true)); - - assertThat(infoParent.getExpirationTimestamp(), is(notNullValue())); - assertThat(child1.getExpirationTimestamp(), is(notNullValue())); - //We should not update uploads that are still in progress (as they might still being written) - assertThat(child2.getExpirationTimestamp(), is(nullValue())); - - verify(uploadStorageService, times(1)).update(infoParent); - verify(uploadStorageService, times(1)).update(child1); - //We should not update uploads that are still in progress (as they might still being written) - verify(uploadStorageService, never()).update(child2); - } - - @Test - public void getUploadsEmptyFinal() throws Exception { - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(null); - - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - assertThat(concatenationService.getPartialUploads(infoParent), Matchers.empty()); - - assertThat(infoParent.getLength(), is(nullValue())); - assertThat(infoParent.getOffset(), is(0L)); - assertThat(infoParent.isUploadInProgress(), is(true)); - - verify(uploadStorageService, never()).update(infoParent); - } - - @Test - public void getConcatenatedBytes() throws Exception { - String upload1 = "This is a "; - String upload2 = "concatenated upload!"; - - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength((long) upload1.getBytes().length); - child1.setOffset((long) upload1.getBytes().length); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength((long) upload2.getBytes().length); - child2.setOffset((long) upload2.getBytes().length); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child2); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - when(uploadStorageService.getUploadedBytes(child1.getId())) - .thenReturn(IOUtils.toInputStream(upload1, StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(child2.getId())) - .thenReturn(IOUtils.toInputStream(upload2, StandardCharsets.UTF_8)); - - assertThat(IOUtils.toString(concatenationService.getConcatenatedBytes(infoParent), StandardCharsets.UTF_8), - is("This is a concatenated upload!")); - } - - @Test - public void getConcatenatedBytesNotComplete() throws Exception { - String upload1 = "This is a "; - String upload2 = "concatenated upload!"; - - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength((long) upload1.getBytes().length); - child1.setOffset((long) upload1.getBytes().length - 2); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength((long) upload2.getBytes().length); - child2.setOffset((long) upload2.getBytes().length - 2); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child2); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - when(uploadStorageService.getUploadedBytes(child1.getId())) - .thenReturn(IOUtils.toInputStream(upload1, StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(child2.getId())) - .thenReturn(IOUtils.toInputStream(upload2, StandardCharsets.UTF_8)); - - assertThat(concatenationService.getConcatenatedBytes(infoParent), - is(nullValue())); - } - - @Test(expected = UploadNotFoundException.class) - public void getConcatenatedBytesNotFound() throws Exception { - String upload1 = "This is a "; - String upload2 = "concatenated upload!"; - - UploadInfo child1 = new UploadInfo(); - child1.setId(new UploadId(UUID.randomUUID())); - child1.setLength((long) upload1.getBytes().length); - child1.setOffset((long) upload1.getBytes().length - 2); - - UploadInfo child2 = new UploadInfo(); - child2.setId(new UploadId(UUID.randomUUID())); - child2.setLength((long) upload2.getBytes().length); - child2.setOffset((long) upload2.getBytes().length - 2); - - UploadInfo infoParent = new UploadInfo(); - infoParent.setId(new UploadId(UUID.randomUUID())); - infoParent.setConcatenationPartIds(Arrays.asList(child1.getId().toString(), child2.getId().toString())); - - when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(child1); - when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(null); - when(uploadStorageService.getUploadInfo(infoParent.getId().toString(), infoParent.getOwnerKey())) - .thenReturn(infoParent); - - when(uploadStorageService.getUploadedBytes(child1.getId())) - .thenReturn(IOUtils.toInputStream(upload1, StandardCharsets.UTF_8)); - when(uploadStorageService.getUploadedBytes(child2.getId())) - .thenReturn(IOUtils.toInputStream(upload2, StandardCharsets.UTF_8)); - - concatenationService.getConcatenatedBytes(infoParent); - } - -} \ No newline at end of file + @Mock private UploadStorageService uploadStorageService; + + private VirtualConcatenationService concatenationService; + + @Before + public void setUp() { + concatenationService = new VirtualConcatenationService(uploadStorageService); + } + + @Test + public void merge() throws Exception { + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength(5L); + child1.setOffset(5L); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength(10L); + child2.setOffset(10L); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child2); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + concatenationService.merge(infoParent); + + assertThat(infoParent.getLength(), is(15L)); + assertThat(infoParent.getOffset(), is(15L)); + assertThat(infoParent.isUploadInProgress(), is(false)); + + verify(uploadStorageService, times(1)).update(infoParent); + } + + @Test + public void mergeNotCompleted() throws Exception { + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength(5L); + child1.setOffset(5L); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength(10L); + child2.setOffset(8L); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child2); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + concatenationService.merge(infoParent); + + assertThat(infoParent.getLength(), is(15L)); + assertThat(infoParent.getOffset(), is(0L)); + assertThat(infoParent.isUploadInProgress(), is(true)); + + verify(uploadStorageService, times(1)).update(infoParent); + } + + @Test + public void mergeWithoutLength() throws Exception { + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength(null); + child1.setOffset(5L); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength(null); + child2.setOffset(8L); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child2); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + concatenationService.merge(infoParent); + + assertThat(infoParent.getLength(), is(nullValue())); + assertThat(infoParent.getOffset(), is(0L)); + assertThat(infoParent.isUploadInProgress(), is(true)); + + verify(uploadStorageService, never()).update(infoParent); + } + + @Test(expected = UploadNotFoundException.class) + public void mergeNotFound() throws Exception { + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength(5L); + child1.setOffset(5L); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength(10L); + child2.setOffset(10L); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(null); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + concatenationService.merge(infoParent); + } + + @Test + public void mergeWithExpiration() throws Exception { + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength(5L); + child1.setOffset(5L); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength(10L); + child2.setOffset(8L); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child2); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + when(uploadStorageService.getUploadExpirationPeriod()).thenReturn(500L); + + concatenationService.merge(infoParent); + + assertThat(infoParent.getLength(), is(15L)); + assertThat(infoParent.getOffset(), is(0L)); + assertThat(infoParent.isUploadInProgress(), is(true)); + + assertThat(infoParent.getExpirationTimestamp(), is(notNullValue())); + assertThat(child1.getExpirationTimestamp(), is(notNullValue())); + // We should not update uploads that are still in progress (as they might still being + // written) + assertThat(child2.getExpirationTimestamp(), is(nullValue())); + + verify(uploadStorageService, times(1)).update(infoParent); + verify(uploadStorageService, times(1)).update(child1); + // We should not update uploads that are still in progress (as they might still being + // written) + verify(uploadStorageService, never()).update(child2); + } + + @Test + public void getUploadsEmptyFinal() throws Exception { + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds(null); + + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + assertThat(concatenationService.getPartialUploads(infoParent), Matchers.empty()); + + assertThat(infoParent.getLength(), is(nullValue())); + assertThat(infoParent.getOffset(), is(0L)); + assertThat(infoParent.isUploadInProgress(), is(true)); + + verify(uploadStorageService, never()).update(infoParent); + } + + @Test + public void getConcatenatedBytes() throws Exception { + String upload1 = "This is a "; + String upload2 = "concatenated upload!"; + + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength((long) upload1.getBytes().length); + child1.setOffset((long) upload1.getBytes().length); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength((long) upload2.getBytes().length); + child2.setOffset((long) upload2.getBytes().length); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child2); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + when(uploadStorageService.getUploadedBytes(child1.getId())) + .thenReturn(IOUtils.toInputStream(upload1, StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(child2.getId())) + .thenReturn(IOUtils.toInputStream(upload2, StandardCharsets.UTF_8)); + + assertThat( + IOUtils.toString( + concatenationService.getConcatenatedBytes(infoParent), StandardCharsets.UTF_8), + is("This is a concatenated upload!")); + } + + @Test + public void getConcatenatedBytesNotComplete() throws Exception { + String upload1 = "This is a "; + String upload2 = "concatenated upload!"; + + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength((long) upload1.getBytes().length); + child1.setOffset((long) upload1.getBytes().length - 2); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength((long) upload2.getBytes().length); + child2.setOffset((long) upload2.getBytes().length - 2); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child2); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + when(uploadStorageService.getUploadedBytes(child1.getId())) + .thenReturn(IOUtils.toInputStream(upload1, StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(child2.getId())) + .thenReturn(IOUtils.toInputStream(upload2, StandardCharsets.UTF_8)); + + assertThat(concatenationService.getConcatenatedBytes(infoParent), is(nullValue())); + } + + @Test(expected = UploadNotFoundException.class) + public void getConcatenatedBytesNotFound() throws Exception { + String upload1 = "This is a "; + String upload2 = "concatenated upload!"; + + UploadInfo child1 = new UploadInfo(); + child1.setId(new UploadId(UUID.randomUUID())); + child1.setLength((long) upload1.getBytes().length); + child1.setOffset((long) upload1.getBytes().length - 2); + + UploadInfo child2 = new UploadInfo(); + child2.setId(new UploadId(UUID.randomUUID())); + child2.setLength((long) upload2.getBytes().length); + child2.setOffset((long) upload2.getBytes().length - 2); + + UploadInfo infoParent = new UploadInfo(); + infoParent.setId(new UploadId(UUID.randomUUID())); + infoParent.setConcatenationPartIds( + Arrays.asList(child1.getId().toString(), child2.getId().toString())); + + when(uploadStorageService.getUploadInfo(child1.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(child1); + when(uploadStorageService.getUploadInfo(child2.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(null); + when(uploadStorageService.getUploadInfo( + infoParent.getId().toString(), infoParent.getOwnerKey())) + .thenReturn(infoParent); + + when(uploadStorageService.getUploadedBytes(child1.getId())) + .thenReturn(IOUtils.toInputStream(upload1, StandardCharsets.UTF_8)); + when(uploadStorageService.getUploadedBytes(child2.getId())) + .thenReturn(IOUtils.toInputStream(upload2, StandardCharsets.UTF_8)); + + concatenationService.getConcatenatedBytes(infoParent); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/disk/DiskLockingServiceTest.java b/src/test/java/me/desair/tus/server/upload/disk/DiskLockingServiceTest.java index a8c6b81..97ac75c 100644 --- a/src/test/java/me/desair/tus/server/upload/disk/DiskLockingServiceTest.java +++ b/src/test/java/me/desair/tus/server/upload/disk/DiskLockingServiceTest.java @@ -16,7 +16,6 @@ import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.util.UUID; - import me.desair.tus.server.upload.UploadId; import me.desair.tus.server.upload.UploadIdFactory; import me.desair.tus.server.upload.UploadLock; @@ -35,108 +34,115 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class DiskLockingServiceTest { - public static final String UPLOAD_URL = "/upload/test"; - private DiskLockingService lockingService; - - @Mock - private UploadIdFactory idFactory; - - private static Path storagePath; - - @BeforeClass - public static void setupDataFolder() throws IOException { - storagePath = Paths.get("target", "tus", "data").toAbsolutePath(); - Files.createDirectories(storagePath); - } + public static final String UPLOAD_URL = "/upload/test"; + private DiskLockingService lockingService; - @AfterClass - public static void destroyDataFolder() throws IOException { - FileUtils.deleteDirectory(storagePath.toFile()); - } + @Mock private UploadIdFactory idFactory; - @Before - public void setUp() { - reset(idFactory); - when(idFactory.getUploadURI()).thenReturn(UPLOAD_URL); - when(idFactory.createId()).thenReturn(new UploadId(UUID.randomUUID())); - when(idFactory.readUploadId(nullable(String.class))).then(new Answer() { - @Override - public UploadId answer(InvocationOnMock invocation) throws Throwable { - return new UploadId(StringUtils.substringAfter(invocation.getArguments()[0].toString(), - UPLOAD_URL + "/")); - } - }); + private static Path storagePath; - lockingService = new DiskLockingService(idFactory, storagePath.toString()); - } + @BeforeClass + public static void setupDataFolder() throws IOException { + storagePath = Paths.get("target", "tus", "data").toAbsolutePath(); + Files.createDirectories(storagePath); + } - @Test - public void lockUploadByUri() throws Exception { - UploadLock uploadLock = lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); + @AfterClass + public static void destroyDataFolder() throws IOException { + FileUtils.deleteDirectory(storagePath.toFile()); + } - assertThat(uploadLock, not(nullValue())); + @Before + public void setUp() { + reset(idFactory); + when(idFactory.getUploadURI()).thenReturn(UPLOAD_URL); + when(idFactory.createId()).thenReturn(new UploadId(UUID.randomUUID())); + when(idFactory.readUploadId(nullable(String.class))) + .then( + new Answer() { + @Override + public UploadId answer(InvocationOnMock invocation) throws Throwable { + return new UploadId( + StringUtils.substringAfter( + invocation.getArguments()[0].toString(), UPLOAD_URL + "/")); + } + }); - uploadLock.release(); - } + lockingService = new DiskLockingService(idFactory, storagePath.toString()); + } - @Test - public void isLockedTrue() throws Exception { - UploadLock uploadLock = lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); + @Test + public void lockUploadByUri() throws Exception { + UploadLock uploadLock = + lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); - assertThat(lockingService.isLocked(new UploadId("000003f1-a850-49de-af03-997272d834c9")), is(true)); + assertThat(uploadLock, not(nullValue())); - uploadLock.release(); - } + uploadLock.release(); + } - @Test - public void isLockedFalse() throws Exception { - UploadLock uploadLock = lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); - uploadLock.release(); + @Test + public void isLockedTrue() throws Exception { + UploadLock uploadLock = + lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); - assertThat(lockingService.isLocked(new UploadId("000003f1-a850-49de-af03-997272d834c9")), is(false)); - } + assertThat( + lockingService.isLocked(new UploadId("000003f1-a850-49de-af03-997272d834c9")), is(true)); - @Test - public void lockUploadNotExists() throws Exception { - reset(idFactory); - when(idFactory.readUploadId(nullable(String.class))).thenReturn(null); + uploadLock.release(); + } - UploadLock uploadLock = lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); + @Test + public void isLockedFalse() throws Exception { + UploadLock uploadLock = + lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); + uploadLock.release(); - assertThat(uploadLock, nullValue()); - } + assertThat( + lockingService.isLocked(new UploadId("000003f1-a850-49de-af03-997272d834c9")), is(false)); + } - @Test - public void cleanupStaleLocks() throws Exception { - Path locksPath = storagePath.resolve("locks"); + @Test + public void lockUploadNotExists() throws Exception { + reset(idFactory); + when(idFactory.readUploadId(nullable(String.class))).thenReturn(null); - String activeLock = "000003f1-a850-49de-af03-997272d834c9"; - UploadLock uploadLock = lockingService.lockUploadByUri("/upload/test/" + activeLock); + UploadLock uploadLock = + lockingService.lockUploadByUri("/upload/test/000003f1-a850-49de-af03-997272d834c9"); - assertThat(uploadLock, not(nullValue())); + assertThat(uploadLock, nullValue()); + } - String staleLock = UUID.randomUUID().toString(); - Files.createFile(locksPath.resolve(staleLock)); + @Test + public void cleanupStaleLocks() throws Exception { + Path locksPath = storagePath.resolve("locks"); - String recentLock = UUID.randomUUID().toString(); - Files.createFile(locksPath.resolve(recentLock)); + String activeLock = "000003f1-a850-49de-af03-997272d834c9"; + UploadLock uploadLock = lockingService.lockUploadByUri("/upload/test/" + activeLock); - Files.setLastModifiedTime(locksPath.resolve(staleLock), - FileTime.fromMillis(System.currentTimeMillis() - 20000)); - Files.setLastModifiedTime(locksPath.resolve(activeLock), - FileTime.fromMillis(System.currentTimeMillis() - 20000)); + assertThat(uploadLock, not(nullValue())); - assertTrue(Files.exists(locksPath.resolve(staleLock))); - assertTrue(Files.exists(locksPath.resolve(activeLock))); - assertTrue(Files.exists(locksPath.resolve(recentLock))); + String staleLock = UUID.randomUUID().toString(); + Files.createFile(locksPath.resolve(staleLock)); - lockingService.cleanupStaleLocks(); + String recentLock = UUID.randomUUID().toString(); + Files.createFile(locksPath.resolve(recentLock)); - assertFalse(Files.exists(locksPath.resolve(staleLock))); - assertTrue(Files.exists(locksPath.resolve(activeLock))); - assertTrue(Files.exists(locksPath.resolve(recentLock))); + Files.setLastModifiedTime( + locksPath.resolve(staleLock), FileTime.fromMillis(System.currentTimeMillis() - 20000)); + Files.setLastModifiedTime( + locksPath.resolve(activeLock), FileTime.fromMillis(System.currentTimeMillis() - 20000)); - uploadLock.release(); - } + assertTrue(Files.exists(locksPath.resolve(staleLock))); + assertTrue(Files.exists(locksPath.resolve(activeLock))); + assertTrue(Files.exists(locksPath.resolve(recentLock))); -} \ No newline at end of file + lockingService.cleanupStaleLocks(); + + assertFalse(Files.exists(locksPath.resolve(staleLock))); + assertTrue(Files.exists(locksPath.resolve(activeLock))); + assertTrue(Files.exists(locksPath.resolve(recentLock))); + + uploadLock.release(); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/disk/DiskStorageServiceTest.java b/src/test/java/me/desair/tus/server/upload/disk/DiskStorageServiceTest.java index f9ed2cd..85e59ab 100644 --- a/src/test/java/me/desair/tus/server/upload/disk/DiskStorageServiceTest.java +++ b/src/test/java/me/desair/tus/server/upload/disk/DiskStorageServiceTest.java @@ -24,7 +24,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; - import me.desair.tus.server.exception.InvalidUploadOffsetException; import me.desair.tus.server.exception.UploadNotFoundException; import me.desair.tus.server.upload.UploadId; @@ -48,462 +47,473 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class DiskStorageServiceTest { - public static final String UPLOAD_URL = "/upload/test"; - private DiskStorageService storageService; - - @Mock - private UploadIdFactory idFactory; - - @Mock - private UploadLockingService uploadLockingService; - - private static Path storagePath; - - @BeforeClass - public static void setupDataFolder() throws IOException { - storagePath = Paths.get("target", "tus", "data").toAbsolutePath(); - Files.createDirectories(storagePath); - } - - @AfterClass - public static void destroyDataFolder() throws IOException { - FileUtils.deleteDirectory(storagePath.toFile()); - } - - @Before - public void setUp() { - reset(idFactory); - when(idFactory.getUploadURI()).thenReturn(UPLOAD_URL); - when(idFactory.createId()).thenReturn(new UploadId(UUID.randomUUID())); - when(idFactory.readUploadId(nullable(String.class))).then(new Answer() { - @Override - public UploadId answer(InvocationOnMock invocation) throws Throwable { - return new UploadId(StringUtils.substringAfter(invocation.getArguments()[0].toString(), - UPLOAD_URL + "/")); - } - }); - - storageService = new DiskStorageService(idFactory, storagePath.toString()); - } - - @Test - public void getMaxUploadSize() throws Exception { - storageService.setMaxUploadSize(null); - assertThat(storageService.getMaxUploadSize(), is(0L)); - - storageService.setMaxUploadSize(0L); - assertThat(storageService.getMaxUploadSize(), is(0L)); - - storageService.setMaxUploadSize(-10L); - assertThat(storageService.getMaxUploadSize(), is(0L)); - - storageService.setMaxUploadSize(372036854775807L); - assertThat(storageService.getMaxUploadSize(), is(372036854775807L)); - } - - @Test - public void getUploadURI() throws Exception { - assertThat(storageService.getUploadURI(), is(UPLOAD_URL)); - } - - @Test - public void create() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - info.setEncodedMetadata("Encoded Metadata"); - - info = storageService.create(info, null); - - assertThat(info.getId(), is(notNullValue())); - assertThat(info.getOffset(), is(0L)); - assertThat(info.getLength(), is(10L)); - assertThat(info.getEncodedMetadata(), is("Encoded Metadata")); - - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - } - - @Test - public void getUploadInfoById() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - info.setEncodedMetadata("Encoded Metadata"); - - info = storageService.create(info, "John"); - - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - - UploadInfo readInfo = storageService.getUploadInfo(info.getId()); - - assertTrue(readInfo != info); - assertThat(readInfo.getId(), is(info.getId())); - assertThat(readInfo.getOffset(), is(0L)); - assertThat(readInfo.getLength(), is(10L)); - assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); - assertThat(readInfo.getCreationTimestamp(), is(info.getCreationTimestamp())); - assertThat(readInfo.getUploadType(), is(info.getUploadType())); - assertThat(readInfo.getOwnerKey(), is(info.getOwnerKey())); - } - - @Test - public void getUploadInfoByFakeId() throws Exception { - UploadInfo readInfo = storageService.getUploadInfo(new UploadId(UUID.randomUUID())); - assertThat(readInfo, is(nullValue())); - } - - @Test - public void getUploadInfoByUrl() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - info.setEncodedMetadata("Encoded Metadata"); + public static final String UPLOAD_URL = "/upload/test"; + private DiskStorageService storageService; + + @Mock private UploadIdFactory idFactory; + + @Mock private UploadLockingService uploadLockingService; + + private static Path storagePath; + + @BeforeClass + public static void setupDataFolder() throws IOException { + storagePath = Paths.get("target", "tus", "data").toAbsolutePath(); + Files.createDirectories(storagePath); + } + + @AfterClass + public static void destroyDataFolder() throws IOException { + FileUtils.deleteDirectory(storagePath.toFile()); + } + + @Before + public void setUp() { + reset(idFactory); + when(idFactory.getUploadURI()).thenReturn(UPLOAD_URL); + when(idFactory.createId()).thenReturn(new UploadId(UUID.randomUUID())); + when(idFactory.readUploadId(nullable(String.class))) + .then( + new Answer() { + @Override + public UploadId answer(InvocationOnMock invocation) throws Throwable { + return new UploadId( + StringUtils.substringAfter( + invocation.getArguments()[0].toString(), UPLOAD_URL + "/")); + } + }); + + storageService = new DiskStorageService(idFactory, storagePath.toString()); + } + + @Test + public void getMaxUploadSize() throws Exception { + storageService.setMaxUploadSize(null); + assertThat(storageService.getMaxUploadSize(), is(0L)); + + storageService.setMaxUploadSize(0L); + assertThat(storageService.getMaxUploadSize(), is(0L)); + + storageService.setMaxUploadSize(-10L); + assertThat(storageService.getMaxUploadSize(), is(0L)); + + storageService.setMaxUploadSize(372036854775807L); + assertThat(storageService.getMaxUploadSize(), is(372036854775807L)); + } - info = storageService.create(info, null); + @Test + public void getUploadURI() throws Exception { + assertThat(storageService.getUploadURI(), is(UPLOAD_URL)); + } + + @Test + public void create() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + info.setEncodedMetadata("Encoded Metadata"); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - - UploadInfo readInfo = storageService.getUploadInfo(UPLOAD_URL + "/" + info.getId(), null); - - assertTrue(readInfo != info); - assertThat(readInfo.getId(), is(info.getId())); - assertThat(readInfo.getOffset(), is(0L)); - assertThat(readInfo.getLength(), is(10L)); - assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); - } + info = storageService.create(info, null); + + assertThat(info.getId(), is(notNullValue())); + assertThat(info.getOffset(), is(0L)); + assertThat(info.getLength(), is(10L)); + assertThat(info.getEncodedMetadata(), is("Encoded Metadata")); + + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + } + + @Test + public void getUploadInfoById() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + info.setEncodedMetadata("Encoded Metadata"); + + info = storageService.create(info, "John"); + + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + + UploadInfo readInfo = storageService.getUploadInfo(info.getId()); + + assertTrue(readInfo != info); + assertThat(readInfo.getId(), is(info.getId())); + assertThat(readInfo.getOffset(), is(0L)); + assertThat(readInfo.getLength(), is(10L)); + assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); + assertThat(readInfo.getCreationTimestamp(), is(info.getCreationTimestamp())); + assertThat(readInfo.getUploadType(), is(info.getUploadType())); + assertThat(readInfo.getOwnerKey(), is(info.getOwnerKey())); + } + + @Test + public void getUploadInfoByFakeId() throws Exception { + UploadInfo readInfo = storageService.getUploadInfo(new UploadId(UUID.randomUUID())); + assertThat(readInfo, is(nullValue())); + } + + @Test + public void getUploadInfoByUrl() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + info.setEncodedMetadata("Encoded Metadata"); + + info = storageService.create(info, null); + + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + + UploadInfo readInfo = storageService.getUploadInfo(UPLOAD_URL + "/" + info.getId(), null); + + assertTrue(readInfo != info); + assertThat(readInfo.getId(), is(info.getId())); + assertThat(readInfo.getOffset(), is(0L)); + assertThat(readInfo.getLength(), is(10L)); + assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); + } + + @Test + public void getUploadInfoOtherOwner() throws Exception { + UploadInfo info = new UploadInfo(); + info.setLength(10L); + info.setEncodedMetadata("Encoded Metadata"); + + info = storageService.create(info, "foo"); + + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - @Test - public void getUploadInfoOtherOwner() throws Exception { - UploadInfo info = new UploadInfo(); - info.setLength(10L); - info.setEncodedMetadata("Encoded Metadata"); + UploadInfo readInfo = storageService.getUploadInfo(UPLOAD_URL + "/" + info.getId(), "foo"); - info = storageService.create(info, "foo"); + assertTrue(readInfo != info); + assertThat(readInfo.getId(), is(info.getId())); + assertThat(readInfo.getOffset(), is(0L)); + assertThat(readInfo.getLength(), is(10L)); + assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + assertThat( + storageService.getUploadInfo(UPLOAD_URL + "/" + info.getId(), "bar"), is(nullValue())); + } - UploadInfo readInfo = storageService.getUploadInfo(UPLOAD_URL + "/" + info.getId(), "foo"); + @Test + public void update() throws Exception { + UploadInfo info1 = new UploadInfo(); + info1.setLength(10L); + info1.setEncodedMetadata("Encoded Metadata"); - assertTrue(readInfo != info); - assertThat(readInfo.getId(), is(info.getId())); - assertThat(readInfo.getOffset(), is(0L)); - assertThat(readInfo.getLength(), is(10L)); - assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); + info1 = storageService.create(info1, null); - assertThat(storageService.getUploadInfo(UPLOAD_URL + "/" + info.getId(), "bar"), is(nullValue())); - } + assertTrue(Files.exists(getUploadInfoPath(info1.getId()))); - @Test - public void update() throws Exception { - UploadInfo info1 = new UploadInfo(); - info1.setLength(10L); - info1.setEncodedMetadata("Encoded Metadata"); + UploadInfo info2 = new UploadInfo(); + info2.setId(info1.getId()); + info2.setLength(10L); + info2.setOffset(8L); + info2.setEncodedMetadata("Updated Encoded Metadata"); - info1 = storageService.create(info1, null); + storageService.update(info2); - assertTrue(Files.exists(getUploadInfoPath(info1.getId()))); + UploadInfo readInfo = storageService.getUploadInfo(info1.getId()); - UploadInfo info2 = new UploadInfo(); - info2.setId(info1.getId()); - info2.setLength(10L); - info2.setOffset(8L); - info2.setEncodedMetadata("Updated Encoded Metadata"); + assertTrue(readInfo != info1); + assertTrue(readInfo != info2); + assertThat(info2.getId(), is(info1.getId())); + assertThat(readInfo.getId(), is(info1.getId())); + assertThat(readInfo.getOffset(), is(8L)); + assertThat(readInfo.getLength(), is(10L)); + assertThat(readInfo.getEncodedMetadata(), is("Updated Encoded Metadata")); + } - storageService.update(info2); + @Test + public void append() throws Exception { + String part1 = "This is part 1"; + String part2 = "This is the second part of my upload"; - UploadInfo readInfo = storageService.getUploadInfo(info1.getId()); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) (part1.getBytes().length + part2.getBytes().length)); + info.setEncodedMetadata("Encoded Metadata"); - assertTrue(readInfo != info1); - assertTrue(readInfo != info2); - assertThat(info2.getId(), is(info1.getId())); - assertThat(readInfo.getId(), is(info1.getId())); - assertThat(readInfo.getOffset(), is(8L)); - assertThat(readInfo.getLength(), is(10L)); - assertThat(readInfo.getEncodedMetadata(), is("Updated Encoded Metadata")); - } + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - @Test - public void append() throws Exception { - String part1 = "This is part 1"; - String part2 = "This is the second part of my upload"; + // Write the first part of the upload + storageService.append(info, IOUtils.toInputStream(part1, StandardCharsets.UTF_8)); + assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is(part1)); - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) (part1.getBytes().length + part2.getBytes().length)); - info.setEncodedMetadata("Encoded Metadata"); + UploadInfo readInfo = storageService.getUploadInfo(info.getId()); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + assertThat(readInfo.getId(), is(info.getId())); + assertThat(readInfo.getOffset(), is((long) part1.getBytes().length)); + assertThat(readInfo.getLength(), is(info.getLength())); + assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); - //Write the first part of the upload - storageService.append(info, IOUtils.toInputStream(part1, StandardCharsets.UTF_8)); - assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is(part1)); + // Write the second part of the upload + storageService.append(info, IOUtils.toInputStream(part2, StandardCharsets.UTF_8)); + assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is(part1 + part2)); - UploadInfo readInfo = storageService.getUploadInfo(info.getId()); + readInfo = storageService.getUploadInfo(info.getId()); - assertThat(readInfo.getId(), is(info.getId())); - assertThat(readInfo.getOffset(), is((long) part1.getBytes().length)); - assertThat(readInfo.getLength(), is(info.getLength())); - assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); + assertThat(readInfo.getId(), is(info.getId())); + assertThat(readInfo.getOffset(), is(info.getLength())); + assertThat(readInfo.getLength(), is(info.getLength())); + assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); + } - //Write the second part of the upload - storageService.append(info, IOUtils.toInputStream(part2, StandardCharsets.UTF_8)); - assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is(part1 + part2)); + @Test + public void appendExceedingMaxSingleUpload() throws Exception { + String content = "This is an upload that is too large"; - readInfo = storageService.getUploadInfo(info.getId()); + storageService.setMaxUploadSize(17L); - assertThat(readInfo.getId(), is(info.getId())); - assertThat(readInfo.getOffset(), is(info.getLength())); - assertThat(readInfo.getLength(), is(info.getLength())); - assertThat(readInfo.getEncodedMetadata(), is("Encoded Metadata")); - } + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength(17L); - @Test - public void appendExceedingMaxSingleUpload() throws Exception { - String content = "This is an upload that is too large"; + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - storageService.setMaxUploadSize(17L); + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength(17L); + // The storage service should protect itself an only write until the maximum number of bytes + // allowed + assertThat( + new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is("This is an upload")); + } - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + @Test + public void appendExceedingMaxMultiUpload() throws Exception { + String part1 = "This is an "; + String part2 = "upload that is too large"; + + storageService.setMaxUploadSize(17L); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength(17L); - //The storage service should protect itself an only write until the maximum number of bytes allowed - assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is("This is an upload")); - } + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - @Test - public void appendExceedingMaxMultiUpload() throws Exception { - String part1 = "This is an "; - String part2 = "upload that is too large"; + // Write the content of the upload in two parts + storageService.append(info, IOUtils.toInputStream(part1, StandardCharsets.UTF_8)); + info = storageService.getUploadInfo(info.getId()); + storageService.append(info, IOUtils.toInputStream(part2, StandardCharsets.UTF_8)); - storageService.setMaxUploadSize(17L); + // The storage service should protect itself an only write until the maximum number of bytes + // allowed + assertThat( + new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is("This is an upload")); + } + + @Test(expected = UploadNotFoundException.class) + public void appendOnFakeUpload() throws Exception { + String content = "This upload was not created before"; - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength(17L); + // Create our fake upload + UploadInfo info = new UploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setLength((long) (content.getBytes().length)); + + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + } - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + @Test(expected = InvalidUploadOffsetException.class) + public void appendOnInvalidOffset() throws Exception { + String content = "This is an upload that is too large"; - //Write the content of the upload in two parts - storageService.append(info, IOUtils.toInputStream(part1, StandardCharsets.UTF_8)); - info = storageService.getUploadInfo(info.getId()); - storageService.append(info, IOUtils.toInputStream(part2, StandardCharsets.UTF_8)); + storageService.setMaxUploadSize(17L); - //The storage service should protect itself an only write until the maximum number of bytes allowed - assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is("This is an upload")); - } + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength(17L); - @Test(expected = UploadNotFoundException.class) - public void appendOnFakeUpload() throws Exception { - String content = "This upload was not created before"; + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - //Create our fake upload - UploadInfo info = new UploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setLength((long) (content.getBytes().length)); + info.setOffset(3L); + storageService.update(info); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - } + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + } - @Test(expected = InvalidUploadOffsetException.class) - public void appendOnInvalidOffset() throws Exception { - String content = "This is an upload that is too large"; + @Test + public void appendInterrupted() throws Exception { + String content = "This is an upload that will be interrupted"; - storageService.setMaxUploadSize(17L); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) content.getBytes().length); - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength(17L); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + InputStream exceptionStream = mock(InputStream.class); + doThrow(new RuntimeException()) + .when(exceptionStream) + .read(org.mockito.Mockito.any(byte[].class), anyInt(), anyInt()); - info.setOffset(3L); - storageService.update(info); + InputStream sequenceStream = + new SequenceInputStream( + IOUtils.toInputStream(content, StandardCharsets.UTF_8), exceptionStream); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + try { + // Write the content of the upload + storageService.append(info, sequenceStream); + fail(); + } catch (Exception ex) { + // ignore } - @Test - public void appendInterrupted() throws Exception { - String content = "This is an upload that will be interrupted"; + info = storageService.getUploadInfo(info.getId()); + assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is(content)); + assertThat(info.getOffset(), is((long) content.getBytes().length)); + } - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) content.getBytes().length); + @Test + public void testRemoveLastNumberOfBytes() throws Exception { + String content = "This is an upload that will be truncated"; - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength(50L); - InputStream exceptionStream = mock(InputStream.class); - doThrow(new RuntimeException()).when(exceptionStream) - .read(org.mockito.Mockito.any(byte[].class), anyInt(), anyInt()); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - InputStream sequenceStream = new SequenceInputStream(IOUtils.toInputStream(content, StandardCharsets.UTF_8), - exceptionStream); + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - try { - //Write the content of the upload - storageService.append(info, sequenceStream); - fail(); - } catch (Exception ex) { - //ignore - } + // Now truncate + storageService.removeLastNumberOfBytes(info, 23); - info = storageService.getUploadInfo(info.getId()); - assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), - is(content)); - assertThat(info.getOffset(), is((long) content.getBytes().length)); - } + assertThat( + new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is("This is an upload")); + } - @Test - public void testRemoveLastNumberOfBytes() throws Exception { - String content = "This is an upload that will be truncated"; + @Test + public void getUploadedBytes() throws Exception { + String content = "This is the content of my upload"; - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength(50L); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) content.getBytes().length); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + assertTrue(Files.exists(getUploadDataPath(info.getId()))); - //Now truncate - storageService.removeLastNumberOfBytes(info, 23); + try (InputStream uploadedBytes = + storageService.getUploadedBytes(UPLOAD_URL + "/" + info.getId(), null)) { - assertThat(new String(Files.readAllBytes(getUploadDataPath(info.getId()))), is("This is an upload")); + assertThat( + IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), + is("This is the content of my upload")); } + } - @Test - public void getUploadedBytes() throws Exception { - String content = "This is the content of my upload"; + @Test + public void copyUploadedBytes() throws Exception { + String content = "This is the content of my upload"; - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) content.getBytes().length); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) content.getBytes().length); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - assertTrue(Files.exists(getUploadDataPath(info.getId()))); + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + assertTrue(Files.exists(getUploadDataPath(info.getId()))); - try (InputStream uploadedBytes = storageService.getUploadedBytes(UPLOAD_URL + "/" + info.getId(), null)) { - - assertThat(IOUtils.toString(uploadedBytes, StandardCharsets.UTF_8), - is("This is the content of my upload")); - } + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + storageService.copyUploadTo(info, output); + assertThat( + new String(output.toByteArray(), StandardCharsets.UTF_8), + is("This is the content of my upload")); } + } - @Test - public void copyUploadedBytes() throws Exception { - String content = "This is the content of my upload"; + @Test + public void terminateCompletedUpload() throws Exception { + String content = "This is the content of my upload"; - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) content.getBytes().length); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) content.getBytes().length); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - assertTrue(Files.exists(getUploadDataPath(info.getId()))); + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + assertTrue(Files.exists(getUploadDataPath(info.getId()))); - try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { - storageService.copyUploadTo(info, output); - assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8), - is("This is the content of my upload")); - } - } + // Now delete the upload and check the files are gone + storageService.terminateUpload(info); + assertFalse(Files.exists(getUploadInfoPath(info.getId()))); + assertFalse(Files.exists(getUploadDataPath(info.getId()))); + assertFalse(Files.exists(getStoragePath(info.getId()))); + } - @Test - public void terminateCompletedUpload() throws Exception { - String content = "This is the content of my upload"; + @Test + public void terminateInProgressUpload() throws Exception { + String content = "This is the content of my upload"; - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) content.getBytes().length); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) content.getBytes().length + 20); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - assertTrue(Files.exists(getUploadDataPath(info.getId()))); + // Write the content of the upload + storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + assertTrue(Files.exists(getUploadDataPath(info.getId()))); - //Now delete the upload and check the files are gone - storageService.terminateUpload(info); - assertFalse(Files.exists(getUploadInfoPath(info.getId()))); - assertFalse(Files.exists(getUploadDataPath(info.getId()))); - assertFalse(Files.exists(getStoragePath(info.getId()))); - } + // Now delete the upload and check the files are gone + storageService.terminateUpload(info); + assertFalse(Files.exists(getUploadInfoPath(info.getId()))); + assertFalse(Files.exists(getUploadDataPath(info.getId()))); + assertFalse(Files.exists(getStoragePath(info.getId()))); - @Test - public void terminateInProgressUpload() throws Exception { - String content = "This is the content of my upload"; + // Call with null should not result in an error + storageService.terminateUpload(null); + } - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) content.getBytes().length + 20); + @Test + public void cleanupExpiredUploads() throws Exception { + when(uploadLockingService.isLocked(any(UploadId.class))).thenReturn(false); - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); + String content = "This is the content of my upload"; - //Write the content of the upload - storageService.append(info, IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - assertTrue(Files.exists(getUploadDataPath(info.getId()))); + // Create our upload with the correct length + UploadInfo info = new UploadInfo(); + info.setLength((long) content.getBytes().length + 20); + info.updateExpiration(100L); - //Now delete the upload and check the files are gone - storageService.terminateUpload(info); - assertFalse(Files.exists(getUploadInfoPath(info.getId()))); - assertFalse(Files.exists(getUploadDataPath(info.getId()))); - assertFalse(Files.exists(getStoragePath(info.getId()))); + info = storageService.create(info, null); + assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - //Call with null should not result in an error - storageService.terminateUpload(null); - } + Utils.sleep(500L); + storageService.cleanupExpiredUploads(uploadLockingService); - @Test - public void cleanupExpiredUploads() throws Exception { - when(uploadLockingService.isLocked(any(UploadId.class))).thenReturn(false); + assertFalse(Files.exists(getUploadInfoPath(info.getId()))); + assertFalse(Files.exists(getStoragePath(info.getId()))); + } - String content = "This is the content of my upload"; + private Path getUploadInfoPath(UploadId id) { + return getStoragePath(id).resolve("info"); + } - //Create our upload with the correct length - UploadInfo info = new UploadInfo(); - info.setLength((long) content.getBytes().length + 20); - info.updateExpiration(100L); + private Path getUploadDataPath(UploadId id) { + return getStoragePath(id).resolve("data"); + } - info = storageService.create(info, null); - assertTrue(Files.exists(getUploadInfoPath(info.getId()))); - - Utils.sleep(500L); - storageService.cleanupExpiredUploads(uploadLockingService); - - assertFalse(Files.exists(getUploadInfoPath(info.getId()))); - assertFalse(Files.exists(getStoragePath(info.getId()))); - } - - private Path getUploadInfoPath(UploadId id) { - return getStoragePath(id).resolve("info"); - } - - private Path getUploadDataPath(UploadId id) { - return getStoragePath(id).resolve("data"); - } - - private Path getStoragePath(UploadId id) { - return storagePath.resolve("uploads").resolve(id.toString()); - } -} \ No newline at end of file + private Path getStoragePath(UploadId id) { + return storagePath.resolve("uploads").resolve(id.toString()); + } +} diff --git a/src/test/java/me/desair/tus/server/upload/disk/ExpiredUploadFilterTest.java b/src/test/java/me/desair/tus/server/upload/disk/ExpiredUploadFilterTest.java index e10cb6b..7d0f878 100644 --- a/src/test/java/me/desair/tus/server/upload/disk/ExpiredUploadFilterTest.java +++ b/src/test/java/me/desair/tus/server/upload/disk/ExpiredUploadFilterTest.java @@ -10,7 +10,6 @@ import java.nio.file.Paths; import java.text.ParseException; import java.util.UUID; - import me.desair.tus.server.upload.UploadId; import me.desair.tus.server.upload.UploadInfo; import me.desair.tus.server.upload.UploadLockingService; @@ -24,106 +23,105 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class ExpiredUploadFilterTest { - @Mock - private DiskStorageService diskStorageService; - - @Mock - private UploadLockingService uploadLockingService; - - private ExpiredUploadFilter uploadFilter; - - @Before - public void setUp() { - uploadFilter = new ExpiredUploadFilter(diskStorageService, uploadLockingService); - } - - @Test - public void accept() throws Exception { - UploadInfo info = createExpiredUploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(2L); - info.setLength(10L); - info.updateExpiration(100L); - - when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); - when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); - - assertTrue(uploadFilter.accept(Paths.get(info.getId().toString()))); - } - - @Test - public void acceptNotFound() throws Exception { - when(diskStorageService.getUploadInfo(any(UploadId.class))).thenReturn(null); - when(uploadLockingService.isLocked(any(UploadId.class))).thenReturn(false); - - assertFalse(uploadFilter.accept(Paths.get(UUID.randomUUID().toString()))); - } - - @Test - public void acceptCompletedUpload() throws Exception { - UploadInfo info = createExpiredUploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(10L); - info.setLength(10L); - info.updateExpiration(100L); - - when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); - when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); - - //Completed uploads also expire - assertTrue(uploadFilter.accept(Paths.get(info.getId().toString()))); - } - - @Test - public void acceptInProgressButNotExpired() throws Exception { - UploadInfo info = new UploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(2L); - info.setLength(10L); - info.updateExpiration(172800000L); - - when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); - when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); - - assertFalse(uploadFilter.accept(Paths.get(info.getId().toString()))); - } - - @Test - public void acceptLocked() throws Exception { - UploadInfo info = createExpiredUploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(8L); - info.setLength(10L); - info.updateExpiration(100L); - - when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); - when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(true); - - assertFalse(uploadFilter.accept(Paths.get(info.getId().toString()))); - } - - @Test - public void acceptException() throws Exception { - UploadInfo info = createExpiredUploadInfo(); - info.setId(new UploadId(UUID.randomUUID())); - info.setOffset(8L); - info.setLength(10L); - info.updateExpiration(100L); - - when(diskStorageService.getUploadInfo(eq(info.getId()))).thenThrow(new IOException()); - when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); - - assertFalse(uploadFilter.accept(Paths.get(info.getId().toString()))); - } - - private UploadInfo createExpiredUploadInfo() throws ParseException { - final long time = DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:43:11").getTime(); - - return new UploadInfo() { - @Override - protected long getCurrentTime() { - return getExpirationTimestamp() == null ? time : time + 10000L; - } - }; - } -} \ No newline at end of file + @Mock private DiskStorageService diskStorageService; + + @Mock private UploadLockingService uploadLockingService; + + private ExpiredUploadFilter uploadFilter; + + @Before + public void setUp() { + uploadFilter = new ExpiredUploadFilter(diskStorageService, uploadLockingService); + } + + @Test + public void accept() throws Exception { + UploadInfo info = createExpiredUploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(2L); + info.setLength(10L); + info.updateExpiration(100L); + + when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); + when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); + + assertTrue(uploadFilter.accept(Paths.get(info.getId().toString()))); + } + + @Test + public void acceptNotFound() throws Exception { + when(diskStorageService.getUploadInfo(any(UploadId.class))).thenReturn(null); + when(uploadLockingService.isLocked(any(UploadId.class))).thenReturn(false); + + assertFalse(uploadFilter.accept(Paths.get(UUID.randomUUID().toString()))); + } + + @Test + public void acceptCompletedUpload() throws Exception { + UploadInfo info = createExpiredUploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(10L); + info.setLength(10L); + info.updateExpiration(100L); + + when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); + when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); + + // Completed uploads also expire + assertTrue(uploadFilter.accept(Paths.get(info.getId().toString()))); + } + + @Test + public void acceptInProgressButNotExpired() throws Exception { + UploadInfo info = new UploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(2L); + info.setLength(10L); + info.updateExpiration(172800000L); + + when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); + when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); + + assertFalse(uploadFilter.accept(Paths.get(info.getId().toString()))); + } + + @Test + public void acceptLocked() throws Exception { + UploadInfo info = createExpiredUploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(8L); + info.setLength(10L); + info.updateExpiration(100L); + + when(diskStorageService.getUploadInfo(eq(info.getId()))).thenReturn(info); + when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(true); + + assertFalse(uploadFilter.accept(Paths.get(info.getId().toString()))); + } + + @Test + public void acceptException() throws Exception { + UploadInfo info = createExpiredUploadInfo(); + info.setId(new UploadId(UUID.randomUUID())); + info.setOffset(8L); + info.setLength(10L); + info.updateExpiration(100L); + + when(diskStorageService.getUploadInfo(eq(info.getId()))).thenThrow(new IOException()); + when(uploadLockingService.isLocked(eq(info.getId()))).thenReturn(false); + + assertFalse(uploadFilter.accept(Paths.get(info.getId().toString()))); + } + + private UploadInfo createExpiredUploadInfo() throws ParseException { + final long time = + DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2018-01-20T10:43:11").getTime(); + + return new UploadInfo() { + @Override + protected long getCurrentTime() { + return getExpirationTimestamp() == null ? time : time + 10000L; + } + }; + } +} diff --git a/src/test/java/me/desair/tus/server/upload/disk/FileBasedLockTest.java b/src/test/java/me/desair/tus/server/upload/disk/FileBasedLockTest.java index 41058da..be3a757 100644 --- a/src/test/java/me/desair/tus/server/upload/disk/FileBasedLockTest.java +++ b/src/test/java/me/desair/tus/server/upload/disk/FileBasedLockTest.java @@ -13,81 +13,82 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; - import me.desair.tus.server.exception.UploadAlreadyLockedException; import org.junit.BeforeClass; import org.junit.Test; public class FileBasedLockTest { - private static Path storagePath; + private static Path storagePath; - @BeforeClass - public static void setupDataFolder() throws IOException { - storagePath = Paths.get("target", "tus", "locks").toAbsolutePath(); - Files.createDirectories(storagePath); - } + @BeforeClass + public static void setupDataFolder() throws IOException { + storagePath = Paths.get("target", "tus", "locks").toAbsolutePath(); + Files.createDirectories(storagePath); + } - @Test - public void testLockRelease() throws UploadAlreadyLockedException, IOException { - UUID test = UUID.randomUUID(); - FileBasedLock lock = new FileBasedLock("/test/upload/" + test.toString(), storagePath.resolve(test.toString())); - lock.release(); - assertFalse(Files.exists(storagePath.resolve(test.toString()))); - } + @Test + public void testLockRelease() throws UploadAlreadyLockedException, IOException { + UUID test = UUID.randomUUID(); + FileBasedLock lock = + new FileBasedLock("/test/upload/" + test.toString(), storagePath.resolve(test.toString())); + lock.release(); + assertFalse(Files.exists(storagePath.resolve(test.toString()))); + } - @Test(expected = UploadAlreadyLockedException.class) - public void testOverlappingLock() throws Exception { - UUID test = UUID.randomUUID(); - Path path = storagePath.resolve(test.toString()); - try (FileBasedLock lock1 = new FileBasedLock("/test/upload/" + test.toString(), path)) { - FileBasedLock lock2 = new FileBasedLock("/test/upload/" + test.toString(), path); - } + @Test(expected = UploadAlreadyLockedException.class) + public void testOverlappingLock() throws Exception { + UUID test = UUID.randomUUID(); + Path path = storagePath.resolve(test.toString()); + try (FileBasedLock lock1 = new FileBasedLock("/test/upload/" + test.toString(), path)) { + FileBasedLock lock2 = new FileBasedLock("/test/upload/" + test.toString(), path); } + } - @Test(expected = UploadAlreadyLockedException.class) - public void testAlreadyLocked() throws Exception { - UUID test1 = UUID.randomUUID(); - Path path1 = storagePath.resolve(test1.toString()); - try (FileBasedLock lock1 = new FileBasedLock("/test/upload/" + test1.toString(), path1)) { - FileBasedLock lock2 = new FileBasedLock("/test/upload/" + test1.toString(), path1) { - @Override - protected FileChannel createFileChannel() throws IOException { - FileChannel channel = createFileChannelMock(); - doReturn(null).when(channel).tryLock(anyLong(), anyLong(), anyBoolean()); - return channel; - } - }; - } + @Test(expected = UploadAlreadyLockedException.class) + public void testAlreadyLocked() throws Exception { + UUID test1 = UUID.randomUUID(); + Path path1 = storagePath.resolve(test1.toString()); + try (FileBasedLock lock1 = new FileBasedLock("/test/upload/" + test1.toString(), path1)) { + FileBasedLock lock2 = + new FileBasedLock("/test/upload/" + test1.toString(), path1) { + @Override + protected FileChannel createFileChannel() throws IOException { + FileChannel channel = createFileChannelMock(); + doReturn(null).when(channel).tryLock(anyLong(), anyLong(), anyBoolean()); + return channel; + } + }; } + } - @Test - public void testLockReleaseLockRelease() throws UploadAlreadyLockedException, IOException { - UUID test = UUID.randomUUID(); - Path path = storagePath.resolve(test.toString()); - FileBasedLock lock = new FileBasedLock("/test/upload/" + test.toString(), path); - lock.release(); - assertFalse(Files.exists(path)); - lock = new FileBasedLock("/test/upload/" + test.toString(), path); - lock.release(); - assertFalse(Files.exists(path)); - } + @Test + public void testLockReleaseLockRelease() throws UploadAlreadyLockedException, IOException { + UUID test = UUID.randomUUID(); + Path path = storagePath.resolve(test.toString()); + FileBasedLock lock = new FileBasedLock("/test/upload/" + test.toString(), path); + lock.release(); + assertFalse(Files.exists(path)); + lock = new FileBasedLock("/test/upload/" + test.toString(), path); + lock.release(); + assertFalse(Files.exists(path)); + } - @Test(expected = IOException.class) - public void testLockIOException() throws UploadAlreadyLockedException, IOException { - //Create directory on place where lock file will be - UUID test = UUID.randomUUID(); - Path path = storagePath.resolve(test.toString()); - try { - Files.createDirectories(path); - } catch (IOException e) { - fail(); - } - - FileBasedLock lock = new FileBasedLock("/test/upload/" + test.toString(), path); + @Test(expected = IOException.class) + public void testLockIOException() throws UploadAlreadyLockedException, IOException { + // Create directory on place where lock file will be + UUID test = UUID.randomUUID(); + Path path = storagePath.resolve(test.toString()); + try { + Files.createDirectories(path); + } catch (IOException e) { + fail(); } - private FileChannel createFileChannelMock() throws IOException { - return spy(FileChannel.class); - } -} \ No newline at end of file + FileBasedLock lock = new FileBasedLock("/test/upload/" + test.toString(), path); + } + + private FileChannel createFileChannelMock() throws IOException { + return spy(FileChannel.class); + } +} diff --git a/src/test/java/me/desair/tus/server/util/HttpChunkedEncodingInputStreamTest.java b/src/test/java/me/desair/tus/server/util/HttpChunkedEncodingInputStreamTest.java index 764a677..e8652bb 100644 --- a/src/test/java/me/desair/tus/server/util/HttpChunkedEncodingInputStreamTest.java +++ b/src/test/java/me/desair/tus/server/util/HttpChunkedEncodingInputStreamTest.java @@ -11,258 +11,268 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; - import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; public class HttpChunkedEncodingInputStreamTest { - Map> trailerHeaders; - - @Before - public void setUp() { - trailerHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + Map> trailerHeaders; + + @Before + public void setUp() { + trailerHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + } + + @Test + public void chunkedWithoutHeaders() throws IOException { + String content = + "4\r\n" + + "Wiki\r\n" + + "5\r\n" + + "pedia\r\n" + + "D\r\n" + + " in\n" + + "\n\r" + + "chunks.\r\n" + + "0\r\n" + + "\r\n"; + + HttpChunkedEncodingInputStream inputStream = + new HttpChunkedEncodingInputStream(IOUtils.toInputStream(content, StandardCharsets.UTF_8)); + + String expectedContent = "Wikipedia in\n" + "\n\r" + "chunks."; + + StringWriter writer = new StringWriter(); + IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8); + inputStream.close(); + + assertEquals(expectedContent, writer.toString()); + } + + @Test + public void chunkedWithHeaders() throws IOException { + String content = + "8\r\n" + + "Mozilla \r\n" + + "A\r\n" + + "Developer \r\n" + + "7\r\n" + + "Network\r\n" + + "0\r\n" + + "Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n" + + "\r\n"; + + HttpChunkedEncodingInputStream inputStream = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(content, StandardCharsets.UTF_8), trailerHeaders); + + String expectedContent = "Mozilla Developer Network"; + + StringWriter writer = new StringWriter(); + IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8); + inputStream.close(); + + assertEquals(expectedContent, writer.toString()); + + assertEquals("Wed, 21 Oct 2015 07:28:00 GMT", trailerHeaders.get("expires").get(0)); + } + + @Test + public void chunkedWithFoldedHeaders() throws IOException { + String content = + "8\r\n" + + "Mozilla \r\n" + + "A\r\n" + + "Developer \r\n" + + "7\r\n" + + "Network\r\n" + + "0\r\n" + + "Expires: Wed, 21 Oct 2015\n" + + " 07:28:00 GMT\r\n" + + "Cookie: ABC\n" + + "\tDEF\r\n" + + "\r\n"; + + HttpChunkedEncodingInputStream inputStream = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(content, StandardCharsets.UTF_8), trailerHeaders); + + String expectedContent = "Mozilla Developer Network"; + + StringWriter writer = new StringWriter(); + IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8); + inputStream.close(); + + assertEquals(expectedContent, writer.toString()); + + assertEquals("Wed, 21 Oct 2015 07:28:00 GMT", trailerHeaders.get("expires").get(0)); + assertEquals("ABC DEF", trailerHeaders.get("cookie").get(0)); + } + + @Test + public void testChunkedInputStream() throws IOException { + String correctInput = + "10;key=\"value\r\n" + + "newline\"\r\n" + + "1234567890" + + "123456\r\n" + + "5\r\n" + + "12345\r\n" + + "0\r\n" + + "Footer1: abcde\r\n" + + "Footer2: fghij\r\n"; + + String correctResult = "123456789012345612345"; + + // Test for when buffer is larger than chunk size + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(correctInput, StandardCharsets.UTF_8), trailerHeaders); + StringWriter writer = new StringWriter(); + IOUtils.copy(in, writer, StandardCharsets.UTF_8); + in.close(); + + assertEquals(correctResult, writer.toString()); + + assertEquals("abcde", trailerHeaders.get("footer1").get(0)); + assertEquals("fghij", trailerHeaders.get("footer2").get(0)); + } + + @Test(expected = IOException.class) + public void testCorruptChunkedInputStream1() throws IOException { + // missing \r\n at the end of the first chunk + String corruptInput = + "10;key=\"val\\ue\"\r\n" + + "1234567890" + + "12345\r\n" + + "5\r\n" + + "12345\r\n" + + "0\r\n" + + "Footer1: abcde\r\n" + + "Footer2: fghij\r\n"; + + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(corruptInput, StandardCharsets.UTF_8), trailerHeaders); + StringWriter writer = new StringWriter(); + IOUtils.copy(in, writer, StandardCharsets.UTF_8); + } + + @Test + public void testEmptyChunkedInputStream() throws IOException { + String input = "0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + StringWriter writer = new StringWriter(); + IOUtils.copy(in, writer, StandardCharsets.UTF_8); + assertEquals(0, writer.toString().length()); + } + + @Test + public void testReadPartialByteArray() throws IOException { + String input = "A\r\n0123456789\r\n0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + + byte[] byteArray = new byte[5]; + in.read(byteArray); + in.close(); + + assertEquals("01234", new String(byteArray)); + } + + @Test + public void testReadByte() throws IOException { + String input = "4\r\n" + "0123\r\n" + "6\r\n" + "456789\r\n0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + + assertEquals('0', (char) in.read()); + assertEquals('1', (char) in.read()); + assertEquals('2', (char) in.read()); + assertEquals('3', (char) in.read()); + assertEquals('4', (char) in.read()); + assertEquals('5', (char) in.read()); + assertEquals('6', (char) in.read()); + assertEquals('7', (char) in.read()); + assertEquals('8', (char) in.read()); + assertEquals('9', (char) in.read()); + in.close(); + } + + @Test + public void testReadEof() throws IOException { + String input = "A\r\n0123456789\r\n0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + + byte[] byteArray = new byte[10]; + in.read(byteArray); + + assertEquals(-1, in.read()); + assertEquals(-1, in.read()); + } + + @Test + public void testReadEof2() throws IOException { + String input = "A\r\n0123456789\r\n0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + + byte[] byteArray = new byte[10]; + in.read(byteArray); + + assertEquals(-1, in.read(byteArray)); + assertEquals(-1, in.read(byteArray)); + } + + @Test + public void testReadClosed() throws IOException { + String input = "A\r\n0123456789\r\n0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + + in.close(); + + try { + byte[] byteArray = new byte[10]; + assertEquals(-1, in.read(byteArray)); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof IOException); } - @Test - public void chunkedWithoutHeaders() throws IOException { - String content = "4\r\n" + - "Wiki\r\n" + - "5\r\n" + - "pedia\r\n" + - "D\r\n" + - " in\n" + - "\n\r" + - "chunks.\r\n" + - "0\r\n" + - "\r\n"; - - HttpChunkedEncodingInputStream inputStream = new HttpChunkedEncodingInputStream( - IOUtils.toInputStream(content, StandardCharsets.UTF_8)); - - String expectedContent = "Wikipedia in\n" + - "\n\r" + - "chunks."; - - StringWriter writer = new StringWriter(); - IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8); - inputStream.close(); - - assertEquals(expectedContent, writer.toString()); - } - - @Test - public void chunkedWithHeaders() throws IOException { - String content = "8\r\n" + - "Mozilla \r\n" + - "A\r\n" + - "Developer \r\n" + - "7\r\n" + - "Network\r\n" + - "0\r\n" + - "Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n" + - "\r\n"; - - HttpChunkedEncodingInputStream inputStream = new HttpChunkedEncodingInputStream( - IOUtils.toInputStream(content, StandardCharsets.UTF_8), trailerHeaders); - - String expectedContent = "Mozilla Developer Network"; - - StringWriter writer = new StringWriter(); - IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8); - inputStream.close(); - - assertEquals(expectedContent, writer.toString()); - - assertEquals("Wed, 21 Oct 2015 07:28:00 GMT", trailerHeaders.get("expires").get(0)); + try { + assertEquals(-1, in.read()); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof IOException); } - @Test - public void chunkedWithFoldedHeaders() throws IOException { - String content = "8\r\n" + - "Mozilla \r\n" + - "A\r\n" + - "Developer \r\n" + - "7\r\n" + - "Network\r\n" + - "0\r\n" + - "Expires: Wed, 21 Oct 2015\n" + - " 07:28:00 GMT\r\n" + - "Cookie: ABC\n" + - "\tDEF\r\n" + - "\r\n"; - - HttpChunkedEncodingInputStream inputStream = new HttpChunkedEncodingInputStream( - IOUtils.toInputStream(content, StandardCharsets.UTF_8), trailerHeaders); - - String expectedContent = "Mozilla Developer Network"; - - StringWriter writer = new StringWriter(); - IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8); - inputStream.close(); - - assertEquals(expectedContent, writer.toString()); - - assertEquals("Wed, 21 Oct 2015 07:28:00 GMT", trailerHeaders.get("expires").get(0)); - assertEquals("ABC DEF", trailerHeaders.get("cookie").get(0)); - } - - @Test - public void testChunkedInputStream() throws IOException { - String correctInput = "10;key=\"value\r\n" + - "newline\"\r\n" + - "1234567890" + - "123456\r\n" + - "5\r\n" + - "12345\r\n" + - "0\r\n" + - "Footer1: abcde\r\n" + - "Footer2: fghij\r\n"; - - String correctResult = "123456789012345612345"; - - //Test for when buffer is larger than chunk size - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(correctInput, StandardCharsets.UTF_8), - trailerHeaders); - StringWriter writer = new StringWriter(); - IOUtils.copy(in, writer, StandardCharsets.UTF_8); - in.close(); - - assertEquals(correctResult, writer.toString()); - - assertEquals("abcde", trailerHeaders.get("footer1").get(0)); - assertEquals("fghij", trailerHeaders.get("footer2").get(0)); - } - - @Test(expected = IOException.class) - public void testCorruptChunkedInputStream1() throws IOException { - //missing \r\n at the end of the first chunk - String corruptInput = "10;key=\"val\\ue\"\r\n" + - "1234567890" + - "12345\r\n" + - "5\r\n" + - "12345\r\n" + - "0\r\n" + - "Footer1: abcde\r\n" + - "Footer2: fghij\r\n"; - - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(corruptInput, StandardCharsets.UTF_8), - trailerHeaders); - StringWriter writer = new StringWriter(); - IOUtils.copy(in, writer, StandardCharsets.UTF_8); - } - - @Test - public void testEmptyChunkedInputStream() throws IOException { - String input = "0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - StringWriter writer = new StringWriter(); - IOUtils.copy(in, writer, StandardCharsets.UTF_8); - assertEquals(0, writer.toString().length()); - } - - @Test - public void testReadPartialByteArray() throws IOException { - String input = "A\r\n0123456789\r\n0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - - byte[] byteArray = new byte[5]; - in.read(byteArray); - in.close(); - - assertEquals("01234", new String(byteArray)); - } - - @Test - public void testReadByte() throws IOException { - String input = "4\r\n" + - "0123\r\n" + - "6\r\n" + - "456789\r\n0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - - assertEquals('0', (char) in.read()); - assertEquals('1', (char) in.read()); - assertEquals('2', (char) in.read()); - assertEquals('3', (char) in.read()); - assertEquals('4', (char) in.read()); - assertEquals('5', (char) in.read()); - assertEquals('6', (char) in.read()); - assertEquals('7', (char) in.read()); - assertEquals('8', (char) in.read()); - assertEquals('9', (char) in.read()); - in.close(); - } - - @Test - public void testReadEof() throws IOException { - String input = "A\r\n0123456789\r\n0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - - byte[] byteArray = new byte[10]; - in.read(byteArray); - - assertEquals(-1, in.read()); - assertEquals(-1, in.read()); - } - - @Test - public void testReadEof2() throws IOException { - String input = "A\r\n0123456789\r\n0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - - byte[] byteArray = new byte[10]; - in.read(byteArray); - - assertEquals(-1, in.read(byteArray)); - assertEquals(-1, in.read(byteArray)); - } - - @Test - public void testReadClosed() throws IOException { - String input = "A\r\n0123456789\r\n0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - - in.close(); - - try { - byte[] byteArray = new byte[10]; - assertEquals(-1, in.read(byteArray)); - fail(); - } catch (Exception ex) { - assertTrue(ex instanceof IOException); - } - - try { - assertEquals(-1, in.read()); - fail(); - } catch (Exception ex) { - assertTrue(ex instanceof IOException); - } - - //double close has not effect - in.close(); - } - - @Test(expected = IllegalArgumentException.class) - public void testNullInputstream() throws IOException { - InputStream in = new HttpChunkedEncodingInputStream(null); - } - - @Test(expected = IOException.class) - public void testNegativeChunkSize() throws IOException { - String input = "-A\r\n0123456789\r\n0\r\n"; - InputStream in = new HttpChunkedEncodingInputStream(IOUtils.toInputStream(input, StandardCharsets.UTF_8), - trailerHeaders); - - byte[] byteArray = new byte[10]; - in.read(byteArray); - } -} \ No newline at end of file + // double close has not effect + in.close(); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullInputstream() throws IOException { + InputStream in = new HttpChunkedEncodingInputStream(null); + } + + @Test(expected = IOException.class) + public void testNegativeChunkSize() throws IOException { + String input = "-A\r\n0123456789\r\n0\r\n"; + InputStream in = + new HttpChunkedEncodingInputStream( + IOUtils.toInputStream(input, StandardCharsets.UTF_8), trailerHeaders); + + byte[] byteArray = new byte[10]; + in.read(byteArray); + } +} diff --git a/src/test/java/me/desair/tus/server/util/MapMatcher.java b/src/test/java/me/desair/tus/server/util/MapMatcher.java index bc58fb5..47dd44d 100644 --- a/src/test/java/me/desair/tus/server/util/MapMatcher.java +++ b/src/test/java/me/desair/tus/server/util/MapMatcher.java @@ -1,31 +1,26 @@ package me.desair.tus.server.util; import java.util.Map; - import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; -/** - * Custom Matcher class used in tests - */ +/** Custom Matcher class used in tests */ public class MapMatcher { - private MapMatcher() { - } - - public static Matcher> hasSize(final int size) { - return new TypeSafeMatcher>() { - @Override - public boolean matchesSafely(Map kvMap) { - return kvMap.size() == size; - } + private MapMatcher() {} - @Override - public void describeTo(Description description) { - description.appendText(" has ").appendValue(size).appendText(" key/value pairs"); - } - }; - } + public static Matcher> hasSize(final int size) { + return new TypeSafeMatcher>() { + @Override + public boolean matchesSafely(Map kvMap) { + return kvMap.size() == size; + } + @Override + public void describeTo(Description description) { + description.appendText(" has ").appendValue(size).appendText(" key/value pairs"); + } + }; + } } diff --git a/src/test/java/me/desair/tus/server/util/TusServletResponseTest.java b/src/test/java/me/desair/tus/server/util/TusServletResponseTest.java index fd7fdb8..f5a9d89 100644 --- a/src/test/java/me/desair/tus/server/util/TusServletResponseTest.java +++ b/src/test/java/me/desair/tus/server/util/TusServletResponseTest.java @@ -14,7 +14,6 @@ import java.util.Locale; import java.util.TimeZone; import java.util.stream.Collectors; - import org.apache.commons.lang3.time.FastDateFormat; import org.apache.commons.lang3.time.TimeZones; import org.junit.Before; @@ -23,96 +22,109 @@ public class TusServletResponseTest { - private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", - TimeZone.getTimeZone(TimeZones.GMT_ID), Locale.US); - - private static final DateFormat mockDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); - - private TusServletResponse tusServletResponse; - private MockHttpServletResponse servletResponse; - - @Before - public void setUp() { - servletResponse = new MockHttpServletResponse(); - tusServletResponse = new TusServletResponse(servletResponse); - } - - @Test - public void setDateHeader() throws Exception { - tusServletResponse.setDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:34:14").getTime()); - tusServletResponse.setDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:38:14").getTime()); - - assertThat(tusServletResponse.getHeader("TEST"), - is("" + DATE_FORMAT.parse("2018-01-03 22:38:14").getTime())); - List responseDateHeaders = servletResponse.getHeaders("TEST").stream().map(s -> { - try { - return "" + mockDateFormat.parse(s).getTime(); - } catch (ParseException e) { - return "" + new Date().getTime(); - } - }).collect(Collectors.toList()); - assertThat(responseDateHeaders, - contains("" + DATE_FORMAT.parse("2018-01-03 22:38:14").getTime())); - } - - @Test - public void addDateHeader() throws Exception { - tusServletResponse.addDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:34:12").getTime()); - tusServletResponse.addDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:38:14").getTime()); - - assertThat(tusServletResponse.getHeader("TEST"), - is("" + DATE_FORMAT.parse("2018-01-03 22:34:12").getTime())); - List responseDateHeaders = servletResponse.getHeaders("TEST").stream().map(s -> { - try { - return "" + mockDateFormat.parse(s).getTime(); - } catch (ParseException e) { - return "" + new Date().getTime(); - } - }).collect(Collectors.toList()); - assertThat(responseDateHeaders, containsInAnyOrder( - "" + DATE_FORMAT.parse("2018-01-03 22:34:12").getTime(), - "" + DATE_FORMAT.parse("2018-01-03 22:38:14").getTime())); - } - - @Test - public void setHeader() throws Exception { - tusServletResponse.setHeader("TEST", "foo"); - tusServletResponse.setHeader("TEST", "bar"); - - assertThat(tusServletResponse.getHeader("TEST"), is("bar")); - assertThat(servletResponse.getHeaders("TEST"), contains("bar")); - } - - @Test - public void addHeader() throws Exception { - tusServletResponse.addHeader("TEST", "foo"); - tusServletResponse.addHeader("TEST", "bar"); - - assertThat(tusServletResponse.getHeader("TEST"), is("foo")); - assertThat(servletResponse.getHeaders("TEST"), containsInAnyOrder("foo", "bar")); - } - - @Test - public void setIntHeader() throws Exception { - tusServletResponse.setIntHeader("TEST", 1); - tusServletResponse.setIntHeader("TEST", 2); - - assertThat(tusServletResponse.getHeader("TEST"), is("2")); - assertThat(servletResponse.getHeaders("TEST"), contains("2")); - } - - @Test - public void addIntHeader() throws Exception { - tusServletResponse.addIntHeader("TEST", 1); - tusServletResponse.addIntHeader("TEST", 2); - - assertThat(tusServletResponse.getHeader("TEST"), is("1")); - assertThat(servletResponse.getHeaders("TEST"), contains("1", "2")); - } - - @Test - public void getHeaderNull() throws Exception { - assertThat(tusServletResponse.getHeader("TEST"), is(nullValue())); - } - -} \ No newline at end of file + private static final FastDateFormat DATE_FORMAT = + FastDateFormat.getInstance( + "yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone(TimeZones.GMT_ID), Locale.US); + + private static final DateFormat mockDateFormat = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + + private TusServletResponse tusServletResponse; + private MockHttpServletResponse servletResponse; + + @Before + public void setUp() { + servletResponse = new MockHttpServletResponse(); + tusServletResponse = new TusServletResponse(servletResponse); + } + + @Test + public void setDateHeader() throws Exception { + tusServletResponse.setDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:34:14").getTime()); + tusServletResponse.setDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:38:14").getTime()); + + assertThat( + tusServletResponse.getHeader("TEST"), + is("" + DATE_FORMAT.parse("2018-01-03 22:38:14").getTime())); + List responseDateHeaders = + servletResponse.getHeaders("TEST").stream() + .map( + s -> { + try { + return "" + mockDateFormat.parse(s).getTime(); + } catch (ParseException e) { + return "" + new Date().getTime(); + } + }) + .collect(Collectors.toList()); + assertThat( + responseDateHeaders, contains("" + DATE_FORMAT.parse("2018-01-03 22:38:14").getTime())); + } + + @Test + public void addDateHeader() throws Exception { + tusServletResponse.addDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:34:12").getTime()); + tusServletResponse.addDateHeader("TEST", DATE_FORMAT.parse("2018-01-03 22:38:14").getTime()); + + assertThat( + tusServletResponse.getHeader("TEST"), + is("" + DATE_FORMAT.parse("2018-01-03 22:34:12").getTime())); + List responseDateHeaders = + servletResponse.getHeaders("TEST").stream() + .map( + s -> { + try { + return "" + mockDateFormat.parse(s).getTime(); + } catch (ParseException e) { + return "" + new Date().getTime(); + } + }) + .collect(Collectors.toList()); + assertThat( + responseDateHeaders, + containsInAnyOrder( + "" + DATE_FORMAT.parse("2018-01-03 22:34:12").getTime(), + "" + DATE_FORMAT.parse("2018-01-03 22:38:14").getTime())); + } + + @Test + public void setHeader() throws Exception { + tusServletResponse.setHeader("TEST", "foo"); + tusServletResponse.setHeader("TEST", "bar"); + + assertThat(tusServletResponse.getHeader("TEST"), is("bar")); + assertThat(servletResponse.getHeaders("TEST"), contains("bar")); + } + + @Test + public void addHeader() throws Exception { + tusServletResponse.addHeader("TEST", "foo"); + tusServletResponse.addHeader("TEST", "bar"); + + assertThat(tusServletResponse.getHeader("TEST"), is("foo")); + assertThat(servletResponse.getHeaders("TEST"), containsInAnyOrder("foo", "bar")); + } + + @Test + public void setIntHeader() throws Exception { + tusServletResponse.setIntHeader("TEST", 1); + tusServletResponse.setIntHeader("TEST", 2); + + assertThat(tusServletResponse.getHeader("TEST"), is("2")); + assertThat(servletResponse.getHeaders("TEST"), contains("2")); + } + + @Test + public void addIntHeader() throws Exception { + tusServletResponse.addIntHeader("TEST", 1); + tusServletResponse.addIntHeader("TEST", 2); + + assertThat(tusServletResponse.getHeader("TEST"), is("1")); + assertThat(servletResponse.getHeaders("TEST"), contains("1", "2")); + } + + @Test + public void getHeaderNull() throws Exception { + assertThat(tusServletResponse.getHeader("TEST"), is(nullValue())); + } +}