Skip to content

Conversation

@lu-yg
Copy link
Collaborator

@lu-yg lu-yg commented Sep 18, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Sets Content-Type and Content-Length headers; filenames are URL-encoded (spaces as %20) and download vs. inline behavior is unified under a single Content-Disposition.
  • Bug Fixes

    • Rejects empty or whitespace-only resource names and returns not-found when missing.
    • Detects and fails fast on missing resource data; safer base64 handling and reliable streaming with explicit flush to ensure complete responses.

@coderabbitai
Copy link

coderabbitai bot commented Sep 18, 2025

Walkthrough

The /resource/download/{name} endpoint now validates the name, fetches the Resource by name (fails if missing), selects base64Data (resourceData or thumbnailData), ensures non-empty data, decodes it to bytes, infers content type, sets unified headers (including Content-Disposition), and streams bytes with try-with-resources and an explicit flush.

Changes

Cohort / File(s) Summary of Changes
Resource download flow and headers
base/src/main/java/com/tinyengine/it/controller/ResourceController.java
- Validate name (reject null/blank)
- Retrieve resource by name and fail if not found
- Choose base64 from resourceData or thumbnailData via isResource(name)
- Check base64 non-empty, extract/clean base64 and decode to bytes
- Determine content type from base64
- URL-encode filename (spaces as %20) and compute single Content-Disposition via Utils.isDownload(name) (attachment vs inline)
- Set headers: Content-Type, Content-Length, Cache-Control, Accept-Ranges
- Write bytes using try-with-resources and explicitly flush the output stream

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant RC as ResourceController
  participant RS as ResourceStore

  C->>RC: GET /resource/download/{name}
  RC->>RC: Validate name (non-null, non-blank)
  RC->>RS: findByName(name)
  RS-->>RC: Resource or null
  alt resource missing
    RC-->>C: 404 / throw exception
  else resource found
    alt isResource(name)
      RC->>RC: base64Data = resourceData
    else
      RC->>RC: base64Data = thumbnailData
    end
    RC->>RC: Ensure base64Data non-empty, clean and decode -> imageBytes
    RC->>RC: Infer contentType, encode filename, decide disposition (inline/attachment)
    RC-->>C: 200 OK with headers (Content-Type, Content-Length, Cache-Control, Accept-Ranges, Content-Disposition)
    RC->>RC: Stream imageBytes (try-with-resources) and flush
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibbled bytes and sipped base64 tea,
Packed headers snug as tunnels can be.
Inline or attached, your file's set to go,
I decoded, flushed, and watched the bytes flow.
Hop on—your download's ready, neat and bright. 🐇📦

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix: modify resource download" correctly identifies the primary area of change (the resource download endpoint) and matches the diffs showing validation, base64 decoding, content-type determination, and Content-Disposition handling; it is concise and relevant, though somewhat generic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (5)

299-306: OpenAPI doc drift: remove unused isResource parameter or document the new rule.

The endpoint no longer accepts isResource, but it’s still listed in the OpenAPI annotations. This confuses clients.

-            @Parameter(name = "isResource", description = "isResource"),

316-318: Differentiate not-found vs bad-request.

Returning the same CM009 for both invalid input and missing resource blurs client semantics; prefer a distinct code (or 404 mapping) for not-found.


338-342: Add filename fallback and sanitize for broader browser support.

Some clients ignore filename*. Provide both filename and filename*, and sanitize to prevent header injection.

-        String contentDisposition = Utils.isDownload(name)
-                ? "attachment; filename*=UTF-8''" + encodedFileName
-                : "inline; filename*=UTF-8''" + encodedFileName;
+        String type = Utils.isDownload(name) ? "attachment" : "inline";
+        String safeBasicFileName = name
+                .replaceAll("[^\\x20-\\x7E]", "_")
+                .replaceAll("[\\\\/:*?\"<>|\\r\\n]", "_");
+        String contentDisposition = type
+                + "; filename=\"" + safeBasicFileName + "\""
+                + "; filename*=UTF-8''" + encodedFileName;
         response.setHeader("Content-Disposition", contentDisposition);

344-346: Redundant flush inside try-with-resources.

close() will flush; you can drop out.flush() to reduce noise.

-            out.flush();

334-334: Optional: add ETag/Last-Modified for better caching.

With Cache-Control: public, max-age=3600, consider strong ETag (e.g., SHA-256 of bytes) and/or Last-Modified to enable conditional requests.

I can provide a small helper to compute an ETag and return 304 on If-None-Match.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5d7307 and 3fde8ee.

📒 Files selected for processing (1)
  • base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1)
base/src/main/java/com/tinyengine/it/common/utils/ImageThumbnailGenerator.java (1)
  • ImageThumbnailGenerator (42-515)
🔇 Additional comments (2)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (2)

311-313: Good null/blank guard on name.


329-329: Filename encoding LGTM.

Using URLEncoder with space normalization is correct for filename*.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1)

325-333: Harden Base64 decode and MIME detection; use setContentLengthLong.

Uncaught Base64.Decoder.decode(...) and strict extractContentType(...) can 500 on malformed/no-prefix data; also prefer setContentLengthLong for large payloads. This was raised earlier and still applies.

Apply:

-        byte[] imageBytes = Base64.getDecoder().decode(cleanBase64);
+        byte[] imageBytes;
+        try {
+            imageBytes = Base64.getDecoder().decode(cleanBase64);
+        } catch (IllegalArgumentException e) {
+            throw new ServiceException(ExceptionEnum.CM009.getResultCode(), "资源数据格式无效");
+        }
@@
-        String detectedType = ImageThumbnailGenerator.extractContentType(base64Data);
+        String detectedType;
+        if (base64Data != null && base64Data.startsWith("data:")) {
+            detectedType = ImageThumbnailGenerator.extractContentType(base64Data);
+        } else {
+            String guessed = java.net.URLConnection.guessContentTypeFromName(name);
+            detectedType = (guessed != null) ? guessed : "application/octet-stream";
+        }
@@
-        response.setContentLength(imageBytes.length);
+        response.setContentLengthLong(imageBytes.length);
🧹 Nitpick comments (2)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (2)

309-309: Remove unnecessary throws Exception and align API doc.

The method doesn’t throw checked exceptions; drop throws Exception. Also, the @Operation lists an isResource parameter that the method does not accept—update the API doc to avoid confusion.

Apply this diff to tighten the signature:

-    public void getResource(@PathVariable String name, HttpServletResponse response) throws Exception {
+    public void getResource(@PathVariable String name, HttpServletResponse response) {

329-340: Use Spring’s ContentDisposition builder; safer encoding and better UA compatibility.

Build Content-Disposition with RFC 5987 support and auto ASCII fallback; also remove the now‑unused manual encodedFileName.

Apply:

-        String encodedFileName = URLEncoder.encode(name, StandardCharsets.UTF_8).replace("+", "%20");
@@
-        String contentDisposition = Utils.isDownload(name)
-                ? "attachment; filename*=UTF-8''" + encodedFileName
-                : "inline; filename*=UTF-8''" + encodedFileName;
-        response.setHeader("Content-Disposition", contentDisposition);
+        org.springframework.http.ContentDisposition cd = org.springframework.http.ContentDisposition
+                .builder(Utils.isDownload(name) ? "attachment" : "inline")
+                .filename(name, StandardCharsets.UTF_8)
+                .build();
+        response.setHeader(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, cd.toString());

If you prefer imports instead of FQNs, add:

import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fde8ee and f04b791.

📒 Files selected for processing (1)
  • base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (1)
base/src/main/java/com/tinyengine/it/common/utils/ImageThumbnailGenerator.java (1)
  • ImageThumbnailGenerator (42-515)
🔇 Additional comments (2)
base/src/main/java/com/tinyengine/it/controller/ResourceController.java (2)

311-323: LGTM: Solid input, existence, and empty‑data checks.

Null/blank name, missing resource, and empty data are handled upfront. Good guard rails.


315-318: Verify tenant scoping on lookup to avoid cross‑app data exposure.

Ensure resourceService.queryResourceByName(name) restricts by loginUserContext.getAppId() (or equivalent) to prevent downloading other apps’ assets.

Run:

Expected: the service/repository method either accepts appId or the underlying SQL includes WHERE app_id = :appId (or equivalent).

@lu-yg lu-yg merged commit 91aab3e into opentiny:develop Sep 18, 2025
1 check passed
lu-yg added a commit to lu-yg/tiny-engine-backend-java that referenced this pull request Oct 23, 2025
* feat: add resource API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* fix: modify source API

* feat: add scheduled tasks

* fix: modify resource download

* fix: modify resource group update

* fix: modify resource group update

* fix: modify resource group update

* fix: modify resource download

* fix: modify block group

* fix: modify AI chat

* fix: modify AI chat

* fix: modify AI chat

* fix: modify AI chat

* fix: modify AI chat

* fix: modify AI chat

* fix: modify AI chat

* fix: modify resource upload

* fix: modify resource upload

* fix: modify resource upload

* fix: modify resource upload

* fix: modify resource upload

* fix: modify resource upload

* fix: modify resource upload

* fix: modify resource download

* fix: modify resource download

* fix: modify resource download
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.

2 participants