From e918cf9bd5c504cfb72d7cf065fcbc102dea2877 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 16 Mar 2026 09:16:30 -0700 Subject: [PATCH] http: match Go bool parsing for dl --- seaweed-volume/src/server/handlers.rs | 11 +++++- .../volume_server/http/headers_static_test.go | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/seaweed-volume/src/server/handlers.rs b/seaweed-volume/src/server/handlers.rs index 8ee0b5e3e..0ebb2409b 100644 --- a/seaweed-volume/src/server/handlers.rs +++ b/seaweed-volume/src/server/handlers.rs @@ -1024,8 +1024,7 @@ async fn get_or_head_handler_inner( // Only set if not already set by response-content-disposition query param if !response_headers.contains_key(header::CONTENT_DISPOSITION) && !filename.is_empty() { let disposition_type = if let Some(ref dl_val) = query.dl { - // Parse dl as bool: "true", "1" -> attachment; anything else -> inline - if dl_val == "true" || dl_val == "1" { + if parse_go_bool(dl_val).unwrap_or(false) { "attachment" } else { "inline" @@ -1315,6 +1314,14 @@ fn extract_filename_from_path(path: &str) -> String { } } +fn parse_go_bool(value: &str) -> Option { + match value { + "1" | "t" | "T" | "TRUE" | "True" | "true" => Some(true), + "0" | "f" | "F" | "FALSE" | "False" | "false" => Some(false), + _ => None, + } +} + // ============================================================================ // Image processing helpers // ============================================================================ diff --git a/test/volume_server/http/headers_static_test.go b/test/volume_server/http/headers_static_test.go index 36bbade8b..82ac8adc6 100644 --- a/test/volume_server/http/headers_static_test.go +++ b/test/volume_server/http/headers_static_test.go @@ -59,6 +59,44 @@ func TestReadPassthroughHeadersAndDownloadDisposition(t *testing.T) { } } +func TestDownloadDispositionUsesGoBoolParsing(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + clusterHarness := framework.StartVolumeCluster(t, matrix.P1()) + conn, grpcClient := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress()) + defer conn.Close() + + const volumeID = uint32(97) + framework.AllocateVolume(t, grpcClient, volumeID, "") + + client := framework.NewHTTPClient() + fullFileID := framework.NewFileID(volumeID, 661123, 0x55667789) + uploadResp := framework.UploadBytes(t, client, clusterHarness.VolumeAdminURL(), fullFileID, []byte("dl-bool-parse-content")) + _ = framework.ReadAllAndClose(t, uploadResp) + if uploadResp.StatusCode != http.StatusCreated { + t.Fatalf("upload expected 201, got %d", uploadResp.StatusCode) + } + + parts := strings.SplitN(fullFileID, ",", 2) + if len(parts) != 2 { + t.Fatalf("unexpected file id format: %q", fullFileID) + } + fidOnly := parts[1] + + url := fmt.Sprintf("%s/%d/%s/%s?dl=t", clusterHarness.VolumeAdminURL(), volumeID, fidOnly, "report.txt") + resp := framework.DoRequest(t, client, mustNewRequest(t, http.MethodGet, url)) + _ = framework.ReadAllAndClose(t, resp) + if resp.StatusCode != http.StatusOK { + t.Fatalf("download read expected 200, got %d", resp.StatusCode) + } + contentDisposition := resp.Header.Get("Content-Disposition") + if !strings.Contains(contentDisposition, "attachment") || !strings.Contains(contentDisposition, "report.txt") { + t.Fatalf("download disposition with dl=t mismatch: %q", contentDisposition) + } +} + func TestStaticAssetEndpoints(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode")