diff --git a/registry/httpStuff.go b/registry/httpStuff.go index 826ac7a..8f5d726 100644 --- a/registry/httpStuff.go +++ b/registry/httpStuff.go @@ -3436,6 +3436,7 @@ func HTTPProxy(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Methods", "GET, PATCH, POST, PUT, DELETE") + w.Header().Add("Link", fmt.Sprintf(";rel=xregistry-root", r.Host)) html := GenerateUI(info, data) w.Write(html) @@ -3456,6 +3457,30 @@ func HTTPWriteError(info *RequestInfo, errAny any) { if info.GetHeader("Content-Type") == "" { info.SetHeader("Content-Type", "application/json; charset=utf-8") } + // Add or replace Link header with xregistry-root rel + // Check if there's already a Link header with rel=xregistry-root + linkValue := fmt.Sprintf("<%s>;rel=xregistry-root", info.BaseURL) + existingLinks := info.GetHeaderValues("Link") + hasXRegistryLink := false + for i, v := range existingLinks { + // Check if this Link header has rel=xregistry-root + if strings.Contains(v, "rel=xregistry-root") || strings.Contains(v, "rel=\"xregistry-root\"") { + // Replace it with the current value + existingLinks[i] = linkValue + hasXRegistryLink = true + break + } + } + if hasXRegistryLink { + // Clear all Link headers and re-add them with the updated value + info.OriginalResponse.Header().Del("Link") + for _, v := range existingLinks { + info.AddHeader("Link", v) + } + } else { + // No existing xregistry-root link, just add it + info.AddHeader("Link", linkValue) + } for k, v := range xErr.Headers { info.AddHeader(k, v) diff --git a/registry/info.go b/registry/info.go index ee40677..a004e3e 100644 --- a/registry/info.go +++ b/registry/info.go @@ -266,6 +266,7 @@ func ParseRequest(tx *Tx, w http.ResponseWriter, r *http.Request) (*RequestInfo, w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Methods", "GET, PATCH, POST, PUT, DELETE") + w.Header().Add("Link", fmt.Sprintf("<%s>;rel=xregistry-root", info.BaseURL)) if log.GetVerbose() > 2 { defer func() { log.Printf("Info:\n%s\n", ToJSON(info)) }() diff --git a/tests/http3_test.go b/tests/http3_test.go index bea86f5..dbd8057 100644 --- a/tests/http3_test.go +++ b/tests/http3_test.go @@ -2779,6 +2779,59 @@ func TestHTTPVersWithResLevel(t *testing.T) { `) } +func TestHTTPLinkHeader(t *testing.T) { + reg := NewRegistry("TestHTTPLinkHeader") + defer PassDeleteReg(t, reg) + + // Test Link header on registry root GET + XCheckHTTP(t, reg, &HTTPTest{ + Name: "Link header on registry root", + URL: "/", + Method: "GET", + ReqHeaders: []string{}, + ReqBody: "", + + Code: 200, + ResHeaders: []string{"Link:;rel=xregistry-root"}, + ResBody: "*", + }) + + // Test Link header on error response + XCheckHTTP(t, reg, &HTTPTest{ + Name: "Link header on error", + URL: "/notfound", + Method: "GET", + ReqHeaders: []string{}, + ReqBody: "", + + Code: 404, + ResHeaders: []string{"Link:;rel=xregistry-root"}, + ResBody: "*", + }) + + // Test Link header with non-default registry (reg- prefix) + XCheckHTTP(t, reg, &HTTPTest{ + Name: "Link header with reg- prefix", + URL: "/reg-TestHTTPLinkHeader", + Method: "GET", + ReqHeaders: []string{}, + ReqBody: "", + + Code: 200, + ResHeaders: []string{"Link:;rel=xregistry-root"}, + ResBody: "*", + }) + + // Test to ensure Link header doesn't appear twice in error cases + res := XDoHTTP(t, reg, "GET", "/notfound", "") + linkHeaders := res.Header.Values("Link") + XCheck(t, len(linkHeaders) == 1, + "Link header should appear exactly once, got %d occurrences: %v", + len(linkHeaders), linkHeaders) + XEqual(t, "Link header value", + linkHeaders[0], ";rel=xregistry-root") +} + func TestHTTPIgnore(t *testing.T) { reg := NewRegistry("TestHTTPIgnore") defer PassDeleteReg(t, reg) diff --git a/tests/misc_test.go b/tests/misc_test.go index e861095..abede4a 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -149,6 +149,16 @@ func TestMiscCORS(t *testing.T) { XEqual(t, "cors header", res.Header.Get("Access-Control-Allow-Methods"), "GET, PATCH, POST, PUT, DELETE") + + linkHeader := res.Header.Get("Link") + XCheck(t, linkHeader != "", "Link header should be present for %s %s", test.method, test.url) + + expectedURL := "http://localhost:8181" + if test.url == "/reg-TestMiscCORS" { + expectedURL = "http://localhost:8181/reg-TestMiscCORS" + } + XEqual(t, "link header", + linkHeader, fmt.Sprintf("<%s>;rel=xregistry-root", expectedURL)) } } diff --git a/todo b/todo index 29bd664..be5d082 100644 --- a/todo +++ b/todo @@ -136,7 +136,6 @@ TODOs: - make model changes via API show up in modelsource - ban model changes that turn on hasDoc if Resources have RESOURCE* attribute extension names -- support Link: ;rel=xregistry-root http header - make sure ?defaultversionid only processed on write ops (bad_flag) - look for more bad_request error that we can specialize - add tests for setting modelsource with $include stuff - and upload data at the same time