This commit is contained in:
2025-11-17 16:07:15 +08:00
parent 1ddda89499
commit 8e853a996f
3 changed files with 56 additions and 56 deletions

View File

@@ -45,7 +45,7 @@ func rewriteResponse(
) (int, map[string]string, []byte, error) { ) (int, map[string]string, []byte, error) {
switch { switch {
case path == "/packages.json": case path == "/packages.json":
data, changed, err := rewriteComposerRootBody(body, ctx.Domain) data, changed, err := rewriteComposerRootBody(body)
if err != nil { if err != nil {
return status, headers, body, err return status, headers, body, err
} }
@@ -104,29 +104,32 @@ func contentType(_ *hooks.RequestContext, locatorPath string) string {
return "" return ""
} }
func rewriteComposerRootBody(body []byte, domain string) ([]byte, bool, error) { func rewriteComposerRootBody(body []byte) ([]byte, bool, error) {
type root struct { // packages.json from Packagist may contain "packages" as array or object; we only care about URL-like fields.
Packages map[string]string `json:"packages"` var root map[string]any
} if err := json.Unmarshal(body, &root); err != nil {
var payload root
if err := json.Unmarshal(body, &payload); err != nil {
return nil, false, err return nil, false, err
} }
if len(payload.Packages) == 0 {
return body, false, nil
}
changed := false changed := false
for key, value := range payload.Packages { for key, val := range root {
rewritten := rewriteComposerAbsolute(domain, value) str, ok := val.(string)
if rewritten != value { if !ok {
payload.Packages[key] = rewritten 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 changed = true
} }
} }
if !changed { if !changed {
return body, false, nil return body, false, nil
} }
data, err := json.Marshal(payload) data, err := json.Marshal(root)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@@ -166,7 +169,11 @@ func rewriteComposerMetadata(body []byte, domain string) ([]byte, bool, error) {
return data, true, nil 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 var asArray []map[string]any
if err := json.Unmarshal(raw, &asArray); err == nil { if err := json.Unmarshal(raw, &asArray); err == nil {
rewrote := rewriteComposerVersionSlice(asArray, domain, packageName) rewrote := rewriteComposerVersionSlice(asArray, domain, packageName)
@@ -229,7 +236,7 @@ func rewriteComposerVersion(entry map[string]any, domain string, packageName str
if !ok || urlValue == "" { if !ok || urlValue == "" {
return changed return changed
} }
rewritten := rewriteComposerDistURL(domain, urlValue) rewritten := rewriteComposerDistURL(urlValue)
if rewritten == urlValue { if rewritten == urlValue {
return changed return changed
} }
@@ -237,46 +244,25 @@ func rewriteComposerVersion(entry map[string]any, domain string, packageName str
return true return true
} }
func rewriteComposerDistURL(domain, original string) string { func rewriteComposerDistURL(original string) string {
parsed, err := url.Parse(original) parsed, err := url.Parse(original)
if err != nil || parsed.Scheme == "" || parsed.Host == "" { if err != nil || parsed.Scheme == "" || parsed.Host == "" {
return original return original
} }
prefix := "/dist/" + parsed.Scheme + "/" + parsed.Host if isPackagistHost(parsed.Host) {
newURL := url.URL{ pathVal := parsed.Path
Scheme: "https", if raw := parsed.RawPath; raw != "" {
Host: domain, pathVal = raw
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
} }
parsed.Host = domain if !strings.HasPrefix(pathVal, "/") {
parsed.Scheme = "https" pathVal = "/" + pathVal
return parsed.String() }
if parsed.RawQuery != "" {
return pathVal + "?" + parsed.RawQuery
}
return pathVal
} }
pathVal := raw return original
if !strings.HasPrefix(pathVal, "/") {
pathVal = "/" + pathVal
}
return "https://" + domain + pathVal
} }
func isComposerMetadataPath(path string) bool { func isComposerMetadataPath(path string) bool {
@@ -331,3 +317,14 @@ func parseComposerDistURL(path string, rawQuery string) (*url.URL, bool) {
} }
return target, true 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")
}

View File

@@ -26,7 +26,7 @@ func TestResolveDistUpstream(t *testing.T) {
func TestRewriteResponseUpdatesURLs(t *testing.T) { func TestRewriteResponseUpdatesURLs(t *testing.T) {
ctx := &hooks.RequestContext{Domain: "cache.example"} 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") _, headers, rewritten, err := rewriteResponse(ctx, 200, map[string]string{}, body, "/p2/a/b.json")
if err != nil { if err != nil {
t.Fatalf("rewrite failed: %v", err) t.Fatalf("rewrite failed: %v", err)
@@ -37,7 +37,7 @@ func TestRewriteResponseUpdatesURLs(t *testing.T) {
if headers["Content-Type"] != "application/json" { if headers["Content-Type"] != "application/json" {
t.Fatalf("expected json content type") t.Fatalf("expected json content type")
} }
if !strings.Contains(string(rewritten), "https://cache.example/dist/https/pkg.example/dist.zip") { if !strings.Contains(string(rewritten), "/dist/package.zip") {
t.Fatalf("expected rewritten URL, got %s", string(rewritten)) t.Fatalf("expected stripped packagist host, got %s", string(rewritten))
} }
} }

View File

@@ -263,7 +263,8 @@ func applyHookRewrite(hook *hookState, resp *http.Response, path string) (*http.
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
if err != nil { if err != nil {
return nil, err resp.Body = io.NopCloser(bytes.NewReader(nil))
return resp, err
} }
headers := make(map[string]string, len(resp.Header)) headers := make(map[string]string, len(resp.Header))
for key, values := range 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) status, newHeaders, newBody, rewriteErr := hook.def.RewriteResponse(hook.ctx, resp.StatusCode, headers, body, path)
if rewriteErr != nil { if rewriteErr != nil {
return nil, rewriteErr resp.Body = io.NopCloser(bytes.NewReader(body))
resp.ContentLength = int64(len(body))
return resp, rewriteErr
} }
if newHeaders == nil { if newHeaders == nil {
newHeaders = headers newHeaders = headers