diff --git a/internal/hubmodule/composer/hooks.go b/internal/hubmodule/composer/hooks.go index 83fdc01..331fd4c 100644 --- a/internal/hubmodule/composer/hooks.go +++ b/internal/hubmodule/composer/hooks.go @@ -45,7 +45,7 @@ func rewriteResponse( ) (int, map[string]string, []byte, error) { switch { case path == "/packages.json": - data, changed, err := rewriteComposerRootBody(body, ctx.Domain) + data, changed, err := rewriteComposerRootBody(body) if err != nil { return status, headers, body, err } @@ -104,29 +104,32 @@ func contentType(_ *hooks.RequestContext, locatorPath string) string { return "" } -func rewriteComposerRootBody(body []byte, domain string) ([]byte, bool, error) { - type root struct { - Packages map[string]string `json:"packages"` - } - var payload root - if err := json.Unmarshal(body, &payload); err != nil { +func rewriteComposerRootBody(body []byte) ([]byte, bool, error) { + // packages.json from Packagist may contain "packages" as array or object; we only care about URL-like fields. + var root map[string]any + if err := json.Unmarshal(body, &root); err != nil { return nil, false, err } - if len(payload.Packages) == 0 { - return body, false, nil - } + changed := false - for key, value := range payload.Packages { - rewritten := rewriteComposerAbsolute(domain, value) - if rewritten != value { - payload.Packages[key] = rewritten + for key, val := range root { + str, ok := val.(string) + if !ok { + continue + } + switch strings.ToLower(key) { + // case "metadata-url", "providers-url", "providers-lazy-url", "notify", "notify-batch", "search": + case "metadata-url": + str = strings.ReplaceAll(str, "https://repo.packagist.org", "") + root[key] = str changed = true } } + if !changed { return body, false, nil } - data, err := json.Marshal(payload) + data, err := json.Marshal(root) if err != nil { return nil, false, err } @@ -166,7 +169,11 @@ func rewriteComposerMetadata(body []byte, domain string) ([]byte, bool, error) { return data, true, nil } -func rewriteComposerPackagesPayload(raw json.RawMessage, domain string, packageName string) (json.RawMessage, bool, error) { +func rewriteComposerPackagesPayload( + raw json.RawMessage, + domain string, + packageName string, +) (json.RawMessage, bool, error) { var asArray []map[string]any if err := json.Unmarshal(raw, &asArray); err == nil { rewrote := rewriteComposerVersionSlice(asArray, domain, packageName) @@ -229,7 +236,7 @@ func rewriteComposerVersion(entry map[string]any, domain string, packageName str if !ok || urlValue == "" { return changed } - rewritten := rewriteComposerDistURL(domain, urlValue) + rewritten := rewriteComposerDistURL(urlValue) if rewritten == urlValue { return changed } @@ -237,46 +244,25 @@ func rewriteComposerVersion(entry map[string]any, domain string, packageName str return true } -func rewriteComposerDistURL(domain, original string) string { +func rewriteComposerDistURL(original string) string { parsed, err := url.Parse(original) if err != nil || parsed.Scheme == "" || parsed.Host == "" { return original } - prefix := "/dist/" + parsed.Scheme + "/" + parsed.Host - newURL := url.URL{ - Scheme: "https", - Host: domain, - Path: prefix + parsed.Path, - RawQuery: parsed.RawQuery, - Fragment: parsed.Fragment, - } - if raw := parsed.RawPath; raw != "" { - newURL.RawPath = prefix + raw - } - return newURL.String() -} - -func rewriteComposerAbsolute(domain, raw string) string { - if raw == "" { - return raw - } - if strings.HasPrefix(raw, "//") { - return "https://" + domain + strings.TrimPrefix(raw, "//") - } - if strings.HasPrefix(raw, "http://") || strings.HasPrefix(raw, "https://") { - parsed, err := url.Parse(raw) - if err != nil { - return raw + if isPackagistHost(parsed.Host) { + pathVal := parsed.Path + if raw := parsed.RawPath; raw != "" { + pathVal = raw } - parsed.Host = domain - parsed.Scheme = "https" - return parsed.String() + if !strings.HasPrefix(pathVal, "/") { + pathVal = "/" + pathVal + } + if parsed.RawQuery != "" { + return pathVal + "?" + parsed.RawQuery + } + return pathVal } - pathVal := raw - if !strings.HasPrefix(pathVal, "/") { - pathVal = "/" + pathVal - } - return "https://" + domain + pathVal + return original } func isComposerMetadataPath(path string) bool { @@ -331,3 +317,14 @@ func parseComposerDistURL(path string, rawQuery string) (*url.URL, bool) { } return target, true } + +func stripPackagistHost(raw string) string { + raw = strings.TrimSpace(raw) + raw = strings.ReplaceAll(raw, "https://repo.packagist.org", "") + raw = strings.ReplaceAll(raw, "http://repo.packagist.org", "") + return raw +} + +func isPackagistHost(host string) bool { + return strings.EqualFold(host, "repo.packagist.org") +} diff --git a/internal/hubmodule/composer/hooks_test.go b/internal/hubmodule/composer/hooks_test.go index fd50579..7ba2fa5 100644 --- a/internal/hubmodule/composer/hooks_test.go +++ b/internal/hubmodule/composer/hooks_test.go @@ -26,7 +26,7 @@ func TestResolveDistUpstream(t *testing.T) { func TestRewriteResponseUpdatesURLs(t *testing.T) { ctx := &hooks.RequestContext{Domain: "cache.example"} - body := []byte(`{"packages":{"a/b":{"1.0.0":{"dist":{"url":"https://pkg.example/dist.zip"}}}}}`) + body := []byte(`{"packages":{"a/b":{"1.0.0":{"dist":{"url":"https://repo.packagist.org/dist/package.zip"}}}}}`) _, headers, rewritten, err := rewriteResponse(ctx, 200, map[string]string{}, body, "/p2/a/b.json") if err != nil { t.Fatalf("rewrite failed: %v", err) @@ -37,7 +37,7 @@ func TestRewriteResponseUpdatesURLs(t *testing.T) { if headers["Content-Type"] != "application/json" { t.Fatalf("expected json content type") } - if !strings.Contains(string(rewritten), "https://cache.example/dist/https/pkg.example/dist.zip") { - t.Fatalf("expected rewritten URL, got %s", string(rewritten)) + if !strings.Contains(string(rewritten), "/dist/package.zip") { + t.Fatalf("expected stripped packagist host, got %s", string(rewritten)) } } diff --git a/internal/proxy/handler.go b/internal/proxy/handler.go index 400ca56..abf12b9 100644 --- a/internal/proxy/handler.go +++ b/internal/proxy/handler.go @@ -263,7 +263,8 @@ func applyHookRewrite(hook *hookState, resp *http.Response, path string) (*http. body, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { - return nil, err + resp.Body = io.NopCloser(bytes.NewReader(nil)) + return resp, err } headers := make(map[string]string, len(resp.Header)) for key, values := range resp.Header { @@ -273,7 +274,9 @@ func applyHookRewrite(hook *hookState, resp *http.Response, path string) (*http. } status, newHeaders, newBody, rewriteErr := hook.def.RewriteResponse(hook.ctx, resp.StatusCode, headers, body, path) if rewriteErr != nil { - return nil, rewriteErr + resp.Body = io.NopCloser(bytes.NewReader(body)) + resp.ContentLength = int64(len(body)) + return resp, rewriteErr } if newHeaders == nil { newHeaders = headers