diff --git a/docs/releases.md b/docs/releases.md index 2b97796d..8eef6daf 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1968,6 +1968,7 @@ since I do have to reset emails on a regular basis. * Generate access tokens, IDs, and magic-link tokens with a cryptographically secure RNG (`crypto/rand`) instead of a clock-seeded PRNG * `X-Email: yes` (also `true`/`1`) now sends to your primary verified email regardless of the `smtp-sender-verify` setting (previously it was rejected unless verification was enabled); it requires being logged in with a verified address * Grant users full access to their own sync topic (`st_...`) so cross-device subscription sync works under `auth-default-access: deny-all` ([#733](https://github.com/binwiederhier/ntfy/issues/733), [#1795](https://github.com/binwiederhier/ntfy/pull/1795), thanks to [@lmorchard](https://github.com/lmorchard) for the contribution) +* Support HTTP (non-TLS) S3-compatible endpoints by preserving the endpoint scheme, e.g. for a local MinIO instance ([#1794](https://github.com/binwiederhier/ntfy/pull/1794), [#1734](https://github.com/binwiederhier/ntfy/issues/1734), thanks to [@sskender](https://github.com/sskender) for the contribution) ### ntfy Android v1.25.x (UNRELEASED) diff --git a/s3/client_test.go b/s3/client_test.go index de754cf7..b8a3f097 100644 --- a/s3/client_test.go +++ b/s3/client_test.go @@ -52,6 +52,27 @@ func TestParseURL_EndpointHTTP(t *testing.T) { require.True(t, cfg.PathStyle) } +func TestParseURL_EndpointNoScheme(t *testing.T) { + // A bare host:port endpoint (no scheme) must default to https for backward compatibility. + // Without this, url.Parse treats the host as the scheme ("localhost:9000" -> scheme "localhost"). + cfg, err := ParseURL("s3://AKID:SECRET@my-bucket?region=us-east-1&endpoint=localhost:9000") + require.Nil(t, err) + require.Equal(t, "https", cfg.Scheme) + require.Equal(t, "localhost:9000", cfg.Endpoint) + require.True(t, cfg.PathStyle) + require.Equal(t, "https://localhost:9000/my-bucket", cfg.BucketURL()) +} + +func TestParseURL_EndpointNoSchemeHostname(t *testing.T) { + // A dotted hostname with a port and no scheme must also default to https + // ("minio.example.com:9000" must not become scheme "minio.example.com"). + cfg, err := ParseURL("s3://AKID:SECRET@my-bucket?region=us-east-1&endpoint=minio.example.com:9000") + require.Nil(t, err) + require.Equal(t, "https", cfg.Scheme) + require.Equal(t, "minio.example.com:9000", cfg.Endpoint) + require.Equal(t, "https://minio.example.com:9000/my-bucket", cfg.BucketURL()) +} + func TestParseURL_EndpointTrailingSlash(t *testing.T) { cfg, err := ParseURL("s3://AKID:SECRET@my-bucket?region=us-east-1&endpoint=https://s3.example.com/") require.Nil(t, err) diff --git a/s3/util.go b/s3/util.go index 25dbe09f..b759fd19 100644 --- a/s3/util.go +++ b/s3/util.go @@ -74,12 +74,13 @@ func ParseURL(s3URL string) (*Config, error) { var endpoint string var pathStyle bool if endpointParam != "" { - // Custom endpoint: strip scheme prefix to extract host[:port] - uep, err := url.Parse(endpointParam) - if err != nil { - return nil, fmt.Errorf("s3: invalid endpoint URL: %w", err) + // Custom endpoint: derive the scheme from the prefix and strip it to extract host[:port]. + // Default to https for backward compatibility, including bare "host:port" endpoints (no + // scheme) -- url.Parse would otherwise misread the host before the port colon as the scheme. + scheme = "https" + if strings.HasPrefix(endpointParam, "http://") { + scheme = "http" } - scheme = uep.Scheme ep := strings.TrimRight(endpointParam, "/") ep = strings.TrimPrefix(ep, "https://") ep = strings.TrimPrefix(ep, "http://")