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) {
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 isPackagistHost(parsed.Host) {
pathVal := parsed.Path
if raw := parsed.RawPath; raw != "" {
newURL.RawPath = prefix + raw
pathVal = 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
parsed.Scheme = "https"
return parsed.String()
}
pathVal := raw
if !strings.HasPrefix(pathVal, "/") {
pathVal = "/" + pathVal
}
return "https://" + domain + pathVal
if parsed.RawQuery != "" {
return pathVal + "?" + parsed.RawQuery
}
return 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")
}

View File

@@ -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))
}
}

View File

@@ -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