-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Refactor storage content-type handling of SignedURL #36804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a583f46
6e87c19
74a0020
f32f9fb
7481389
a9efd95
dda9fca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -246,16 +246,48 @@ func (a *AzureBlobStorage) Delete(path string) error { | |||||||||||||||||||||||
| return convertAzureBlobErr(err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func (a *AzureBlobStorage) getSasURL(b *blob.Client, template sas.BlobSignatureValues) (string, error) { | ||||||||||||||||||||||||
| urlParts, err := blob.ParseURL(b.URL()) | ||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||
| return "", err | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| t, err := time.Parse(blob.SnapshotTimeFormat, urlParts.Snapshot) | ||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||
| t = time.Time{} | ||||||||||||||||||||||||
|
Comment on lines
+255
to
+257
|
||||||||||||||||||||||||
| t, err := time.Parse(blob.SnapshotTimeFormat, urlParts.Snapshot) | |
| if err != nil { | |
| t = time.Time{} | |
| var t time.Time | |
| if urlParts.Snapshot == "" { | |
| t = time.Time{} | |
| } else { | |
| t, err = time.Parse(blob.SnapshotTimeFormat, urlParts.Snapshot) | |
| if err != nil { | |
| return "", err | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,23 @@ func TestAzureBlobStorageIterator(t *testing.T) { | |
| }) | ||
| } | ||
|
|
||
| func TestAzureBlobStorageURLContentTypeAndDisposition(t *testing.T) { | ||
| if os.Getenv("CI") == "" { | ||
| t.Skip("azureBlobStorage not present outside of CI") | ||
| return | ||
| } | ||
| testBlobStorageURLContentTypeAndDisposition(t, setting.AzureBlobStorageType, &setting.Storage{ | ||
| AzureBlobConfig: setting.AzureBlobStorageConfig{ | ||
| // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#ip-style-url | ||
| Endpoint: "http://devstoreaccount1.azurite.local:10000", | ||
| // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#well-known-storage-account-and-key | ||
| AccountName: "devstoreaccount1", | ||
| AccountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", | ||
|
||
| Container: "test", | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| func TestAzureBlobStoragePath(t *testing.T) { | ||
| m := &AzureBlobStorage{cfg: &setting.AzureBlobStorageConfig{BasePath: ""}} | ||
| assert.Empty(t, m.buildAzureBlobPath("/")) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,12 +4,14 @@ | |||||
| package storage | ||||||
|
|
||||||
| import ( | ||||||
| "net/http" | ||||||
| "strings" | ||||||
| "testing" | ||||||
|
|
||||||
| "code.gitea.io/gitea/modules/setting" | ||||||
|
|
||||||
| "github.com/stretchr/testify/assert" | ||||||
| "github.com/stretchr/testify/require" | ||||||
| ) | ||||||
|
|
||||||
| func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) { | ||||||
|
|
@@ -50,3 +52,54 @@ func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) { | |||||
| assert.Len(t, expected, count) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| func testSingleBlobStorageURLContentTypeAndDisposition(t *testing.T, s ObjectStorage, path, name string, expected SignedURLParam, reqParams *SignedURLParam) { | ||||||
| u, err := s.URL(path, name, http.MethodGet, reqParams) | ||||||
| require.NoError(t, err) | ||||||
| resp, err := http.Get(u.String()) | ||||||
| require.NoError(t, err) | ||||||
| defer resp.Body.Close() | ||||||
| assert.Equal(t, expected.ContentType, resp.Header.Get("Content-Type")) | ||||||
| if expected.ContentDisposition != "" { | ||||||
| assert.Equal(t, expected.ContentDisposition, resp.Header.Get("Content-Disposition")) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| func testBlobStorageURLContentTypeAndDisposition(t *testing.T, typStr Type, cfg *setting.Storage) { | ||||||
| s, err := NewStorage(typStr, cfg) | ||||||
| assert.NoError(t, err) | ||||||
|
|
||||||
| data := "Q2xTckt6Y1hDOWh0" | ||||||
|
||||||
| data := "Q2xTckt6Y1hDOWh0" | |
| data := "Q2xTckt6Y1hDOWh0" // arbitrary test content; specific value is irrelevant to this test |
Copilot
AI
Mar 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file saved is test.txt (plain text content), but the URL is requested with the name "test.pdf". The withDefaults logic uses the name parameter to detect MIME type and disposition, so the test is asserting that an object with a .pdf name gets ContentType: "application/pdf" and ContentDisposition: "inline" — but the expected value here reflects the request parameters inferred from the name, not the actual server response content-type of the stored object. If the storage backend serves the stored object's own content-type (e.g., Minio may sniff it), the assertion may fail or be misleading. Ensure the test accurately reflects what the server will actually return in the Content-Type header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not the blob storage already has some sort of domain sandbox???
If served directly from giteas domain, I would agree. Given you can not make assumptions about the blob signed url you cannot really reference anything more than ca. 5min that is part of the same domain.
e.g. this is how GitHub permits raw html artifacts to be served without sandbox