Skip to content

UFAL/Zip download missing content length header - #1028

Closed
Paurikova2 wants to merge 3 commits into
dtq-devfrom
ufal/zip-download-missing-content-length-header
Closed

UFAL/Zip download missing content length header#1028
Paurikova2 wants to merge 3 commits into
dtq-devfrom
ufal/zip-download-missing-content-length-header

Conversation

@Paurikova2

@Paurikova2 Paurikova2 commented Jul 30, 2025

Copy link
Copy Markdown
Phases MP MM MB MR JM Total
ETA 0 0 0 0 0 0
Developing 1 0 0 0 0 0
Review 0 0 0 0 0 0
Total - - - - - 0
ETA est. 0
ETA cust. - - - - - 0

Problem description

When downloading ZIP files, the missing Content-Length header prevents the browser from knowing the file size and download time.

Summary by CodeRabbit

  • Bug Fixes

    • Improved ZIP file download reliability by correctly setting response headers and content length.
  • Tests

    • Enhanced tests to validate HTTP headers and ZIP content structure for downloads, improving test accuracy.

@Paurikova2 Paurikova2 self-assigned this Jul 30, 2025
@coderabbitai

coderabbitai Bot commented Jul 30, 2025

Copy link
Copy Markdown

Walkthrough

The downloadFileZip method in the metadata bitstream controller was refactored to buffer the ZIP output in memory before sending it to the client, allowing the response headers (including Content-Length) to be set accurately. Corresponding integration tests were updated to validate response headers rather than comparing ZIP content bytes directly.

Changes

Cohort / File(s) Change Summary
Controller ZIP Response Refactor
dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java
Modified downloadFileZip to buffer ZIP output in a ByteArrayOutputStream, set response headers (including Content-Length), and then write ZIP bytes to the servlet output stream.
Test Adjustments for ZIP Download
dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java
Refactored test to verify HTTP response headers and status for ZIP downloads, removing direct byte content comparison.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~7 minutes

Possibly related issues

Poem

In a stream of bytes, a ZIP took flight,
Now buffered first, to set headers right.
Tests peek at headers, not just the file—
Ensuring downloads work with style.
A rabbit hops, with code so neat,
Content-Length set, the fix complete! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8516ed3 and a43e233.

📒 Files selected for processing (2)
  • dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java (3 hunks)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run Integration Tests
  • GitHub Check: dspace-dependencies / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Unit Tests
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ufal/zip-download-missing-content-length-header

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (1)
dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java (1)

120-121: Remove duplicate header setting.

The Content-Disposition and Content-Type headers are set twice - once before ZIP creation (lines 120-121) and again after (lines 149-151). The first setting is unnecessary and should be removed.

-        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", name));
-        response.setContentType("application/zip");
         List<Bundle> bundles = item.getBundles("ORIGINAL");

Also applies to: 149-151

🧹 Nitpick comments (1)
dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java (1)

86-101: Comprehensive header validation addresses PR requirements.

The test effectively validates:

  • Content-Length header presence and correctness
  • Content-Type header starts with "application/zip"
  • Content-Disposition header starts with "attachment"

This directly tests the PR objective of ensuring Content-Length header is included.

However, consider adding a basic validation that the response contains actual ZIP content to maintain some content integrity testing.

+        // Basic validation that response contains ZIP content
+        assertTrue("Response should contain ZIP content", responseBytes.length > 0);
+        // Verify ZIP magic number (first 4 bytes should be ZIP signature)
+        assertTrue("Response should start with ZIP signature", 
+                   responseBytes.length >= 4 && 
+                   responseBytes[0] == 0x50 && responseBytes[1] == 0x4B);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a39643 and 18ed75e.

📒 Files selected for processing (2)
  • dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java (3 hunks)
  • dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: dspace-dependencies / docker-build (linux/amd64, ubuntu-latest, true)
  • GitHub Check: Run Integration Tests
  • GitHub Check: Run Unit Tests
🔇 Additional comments (9)
dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java (5)

12-12: LGTM! New imports are appropriate.

The added imports ByteArrayOutputStream and OutputStream are necessary for the new buffering approach to set the Content-Length header correctly.

Also applies to: 15-15


153-156: Proper resource management with try-with-resources.

Good use of try-with-resources to ensure the output stream is properly closed. The explicit flush is also appropriate to ensure all data is sent to the client.


151-151: Content-Length header successfully added.

The addition of response.setContentLength(zipBytes.length) successfully addresses the PR objective by providing the missing Content-Length header that browsers need for download progress indication.


12-12: LGTM: Appropriate imports added.

The new imports ByteArrayOutputStream and OutputStream are correctly added to support the buffering approach for ZIP generation.

Also applies to: 15-15


124-125: Good approach to enable Content-Length header setting.

Using ByteArrayOutputStream to buffer the ZIP content allows determining the exact size before writing to the response, which is necessary for setting the Content-Length header.

dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java (4)

10-10: LGTM! Appropriate test imports added.

The new imports support the refactored test structure - assertNotNull and assertTrue for header validation, and MvcResult for capturing response details.

Also applies to: 30-30, 32-32


93-94: Excellent validation of Content-Length header.

The test properly validates that the Content-Length header is present and matches the actual response byte array length, which directly addresses the PR objective.


10-10: LGTM: Appropriate test imports added.

The new imports support the updated test approach using MvcResult for header validation.

Also applies to: 30-30, 32-32


81-84: Good refactor to capture response for header validation.

Using MvcResult allows access to response headers and content, which is essential for validating the Content-Length header fix.

Comment on lines +124 to +125
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipArchiveOutputStream zip = new ZipArchiveOutputStream(baos);

@coderabbitai coderabbitai Bot Jul 30, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Memory usage consideration for large ZIP files.

The change from streaming directly to response to buffering in ByteArrayOutputStream will consume memory proportional to the ZIP file size. For items with many or large bitstreams, this could lead to memory issues.

Consider implementing a size limit or streaming approach with chunked encoding as an alternative for large archives.

Add a configuration property to limit ZIP size or implement chunked transfer encoding:

+// Add size validation before creating ZIP
+long estimatedSize = bundles.stream()
+    .flatMap(b -> b.getBitstreams().stream())
+    .mapToLong(Bitstream::getSizeBytes)
+    .sum();
+
+if (estimatedSize > configurationService.getLongProperty("zip.max.size", 100 * 1024 * 1024)) {
+    throw new UnprocessableEntityException("ZIP archive too large");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipArchiveOutputStream zip = new ZipArchiveOutputStream(baos);
// Add size validation before creating ZIP
long estimatedSize = bundles.stream()
.flatMap(b -> b.getBitstreams().stream())
.mapToLong(Bitstream::getSizeBytes)
.sum();
if (estimatedSize > configurationService.getLongProperty("zip.max.size", 100 * 1024 * 1024)) {
throw new UnprocessableEntityException("ZIP archive too large");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipArchiveOutputStream zip = new ZipArchiveOutputStream(baos);
🤖 Prompt for AI Agents
In
dspace-server-webapp/src/main/java/org/dspace/app/rest/MetadataBitstreamController.java
around lines 124 to 125, the current code buffers the entire ZIP output in a
ByteArrayOutputStream before sending it, which can cause high memory usage for
large ZIP files. To fix this, refactor the code to stream the ZIP output
directly to the HTTP response output stream using chunked transfer encoding,
avoiding full buffering in memory. Additionally, add a configuration property to
set a maximum allowed ZIP size and enforce this limit during streaming to
prevent excessive memory consumption.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milanmajchrak May I be sure that this situation will never happen? Because the button is not generating when there is too much files....

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually really interesting review comment, because indeed the Download ZIP button is showed up only when the files are between allowed files size, BUT we can download all files as a single zip using the CURL command, so we should restrict it, just check the download.all.limit.max.file.size property

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Paurikova2 Paurikova2 linked an issue Jul 30, 2025 that may be closed by this pull request
@Paurikova2
Paurikova2 requested a review from Copilot July 30, 2025 12:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a missing Content-Length header issue when downloading ZIP files. The problem was that ZIP files were being streamed directly to the response without setting the Content-Length header, preventing browsers from showing download progress and file size information.

  • Refactored ZIP generation to use ByteArrayOutputStream for content length calculation
  • Added Content-Length header to ZIP download responses
  • Updated tests to validate HTTP headers and ZIP content structure

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
MetadataBitstreamController.java Modified ZIP generation to buffer content in memory and set Content-Length header
MetadataBitstreamControllerIT.java Enhanced test to verify HTTP headers and ZIP structure instead of byte comparison
Comments suppressed due to low confidence (1)

dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataBitstreamControllerIT.java:109

  • The test hardcodes the expected ZIP entry name as "Bitstream" but doesn't verify this matches the actual bitstream name from the test setup. This could lead to false positives if the ZIP generation logic changes.
            assertEquals("Bitstream", entry.getName());

@Paurikova2
Paurikova2 requested a review from milanmajchrak July 31, 2025 05:31
Comment on lines +124 to +125
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipArchiveOutputStream zip = new ZipArchiveOutputStream(baos);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually really interesting review comment, because indeed the Download ZIP button is showed up only when the files are between allowed files size, BUT we can download all files as a single zip using the CURL command, so we should restrict it, just check the download.all.limit.max.file.size property

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UFAL/ZIP Download Missing Content-Length Header

3 participants