From a9b2f0c761e03649a9c8a747ca733cdbc0353a9c Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Thu, 11 Jan 2024 18:43:12 +0300 Subject: [PATCH 1/4] Merge pull request #9256 from DSpace/backport-9248-to-dspace-7_x [Port dspace-7_x] Return headers for HEAD request --- .../app/rest/BitstreamRestController.java | 7 +++++++ .../rest/utils/HttpHeadersInitializer.java | 19 ++++++++++--------- .../app/rest/BitstreamRestControllerIT.java | 14 +++++++++++++- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 1326b5243559..95051e0eb4d1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -153,6 +153,7 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp .withBufferSize(BUFFER_SIZE) .withFileName(name) .withChecksum(bit.getChecksum()) + .withLength(bit.getSizeBytes()) .withMimetype(mimetype) .with(request) .with(response); @@ -189,6 +190,12 @@ public ResponseEntity retrieve(@PathVariable UUID uuid, HttpServletResponse resp if (s3DirectDownload) { return redirectToS3DownloadUrl(httpHeaders, name, bit.getInternalId()); } + + if (RequestMethod.HEAD.name().equals(request.getMethod())) { + log.debug("HEAD request - no response body"); + return ResponseEntity.ok().headers(httpHeaders).build(); + } + return ResponseEntity.ok().headers(httpHeaders).body(bitstreamResource); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java index a69da4c5e86b..d68c710a3c7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/HttpHeadersInitializer.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -33,7 +34,6 @@ public class HttpHeadersInitializer { protected final Logger log = LoggerFactory.getLogger(this.getClass()); - private static final String METHOD_HEAD = "HEAD"; private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; private static final String CONTENT_TYPE_MULTITYPE_WITH_BOUNDARY = "multipart/byteranges; boundary=" + MULTIPART_BOUNDARY; @@ -144,6 +144,9 @@ public HttpHeaders initialiseHeaders() throws IOException { if (checksum != null) { httpHeaders.put(ETAG, Collections.singletonList(checksum)); } + if (Objects.nonNull((Long.valueOf(this.length)))) { + httpHeaders.put(HttpHeaders.CONTENT_LENGTH, Collections.singletonList(String.valueOf(this.length))); + } httpHeaders.put(LAST_MODIFIED, Collections.singletonList(FastHttpDateFormat.formatDate(lastModified))); httpHeaders.put(EXPIRES, Collections.singletonList(FastHttpDateFormat.formatDate( System.currentTimeMillis() + DEFAULT_EXPIRE_TIME))); @@ -165,16 +168,14 @@ public HttpHeaders initialiseHeaders() throws IOException { httpHeaders.put(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, Collections.singletonList(HttpHeaders.ACCEPT_RANGES)); - httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, - disposition, - encodeText(fileName)))); - log.debug("Content-Disposition : {}", disposition); - // Content phase - if (METHOD_HEAD.equals(request.getMethod())) { - log.debug("HEAD request - skipping content"); - return null; + // distposition may be null here if contentType is null + if (!isNullOrEmpty(disposition)) { + httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT, + disposition, + encodeText(fileName)))); } + log.debug("Content-Disposition : {}", disposition); return httpHeaders; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index f7df7377ec99..615c07f16918 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -219,6 +219,18 @@ public void retrieveFullBitstream() throws Exception { } context.restoreAuthSystemState(); + //** WHEN ** + // we want to know what we are downloading before we download it + getClient().perform(head("/api/core/bitstreams/" + bitstream.getID() + "/content")) + //** THEN ** + .andExpect(status().isOk()) + + //The Content Length must match the full length + .andExpect(header().longValue("Content-Length", bitstreamContent.getBytes().length)) + .andExpect(header().string("Content-Type", "text/plain;charset=UTF-8")) + .andExpect(header().string("ETag", "\"" + bitstream.getChecksum() + "\"")) + .andExpect(content().bytes(new byte[] {})); + //** WHEN ** //We download the bitstream getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/content")) @@ -245,7 +257,7 @@ public void retrieveFullBitstream() throws Exception { .andExpect(status().isNotModified()); //The download and head request should also be logged as a statistics record - checkNumberOfStatsRecords(bitstream, 2); + checkNumberOfStatsRecords(bitstream, 3); } @Test From 978758269c6f77380cbd6d004d71b23d739aac02 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 7 May 2025 07:11:49 +0200 Subject: [PATCH 2/4] Added IT for content type (mimetype) of bitstream --- .../app/rest/BitstreamRestControllerIT.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 615c07f16918..9155d0bd2438 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -98,6 +98,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.MvcResult; /** * Integration test to test the /api/core/bitstreams/[id]/* endpoints @@ -1414,4 +1415,41 @@ public void testS3DirectDownloadRedirect() throws Exception { configurationService.setProperty("assetstore.s3.bucketName", null); } } + + @Test + public void testBitstreamContentTypeIsCorrect() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item with a bitstream + String bitstreamContent = "0123456789"; + + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + Item item = ItemBuilder.createItem(context, col1) + .withTitle("Test item") + .build(); + + bitstream = BitstreamBuilder.createBitstream(context, item, is) + .withName("testfile.txt") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + MvcResult result = getClient().perform(head("/api/core/bitstreams/" + bitstream.getID() + "/content")) + .andExpect(status().isOk()) + .andExpect(header().exists("Content-Type")) + .andExpect(header().string("Content-Type", "text/plain;charset=UTF-8")) + .andExpect(header().exists("Content-Disposition")) + .andExpect(header().exists("ETag")) + .andReturn(); + } } From faec4b6475b511f58bf3f56e9f59761d8f5a0591 Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 7 May 2025 10:56:22 +0200 Subject: [PATCH 3/4] removed duplicit integration test --- .../app/rest/BitstreamRestControllerIT.java | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index 9155d0bd2438..bdc18895a486 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -1415,41 +1415,4 @@ public void testS3DirectDownloadRedirect() throws Exception { configurationService.setProperty("assetstore.s3.bucketName", null); } } - - @Test - public void testBitstreamContentTypeIsCorrect() throws Exception { - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community and one collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - - //2. A public item with a bitstream - String bitstreamContent = "0123456789"; - - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - Item item = ItemBuilder.createItem(context, col1) - .withTitle("Test item") - .build(); - - bitstream = BitstreamBuilder.createBitstream(context, item, is) - .withName("testfile.txt") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - MvcResult result = getClient().perform(head("/api/core/bitstreams/" + bitstream.getID() + "/content")) - .andExpect(status().isOk()) - .andExpect(header().exists("Content-Type")) - .andExpect(header().string("Content-Type", "text/plain;charset=UTF-8")) - .andExpect(header().exists("Content-Disposition")) - .andExpect(header().exists("ETag")) - .andReturn(); - } } From 9eecab6c1b24f2be6640b8bac23923c884e69f9e Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 7 May 2025 11:17:34 +0200 Subject: [PATCH 4/4] removed unused import --- .../test/java/org/dspace/app/rest/BitstreamRestControllerIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java index bdc18895a486..615c07f16918 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestControllerIT.java @@ -98,7 +98,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MvcResult; /** * Integration test to test the /api/core/bitstreams/[id]/* endpoints