socle

serve: answer the go toolchain's ?go-get=1 vanity-import probe

commit 52092f45a78764cd6f370ec6fbd857657321133c · parent 701ac20d8a · browse this tree
Ximianth, 2026-10-27 17:08:02 UTC

The probe outranks all other dispatch under a repo: go queries the
full package path (not just the module root), and the home's
trailing-slash redirect would drop the query string, so the check
sits right after repo matching.

The page carries go-import (prefix, vcs git, clone URL -- fetch of
which socle already serves) and go-source (pkg.go.dev deep links
into the tree pages; no #L{line} fragment since file views have no
line anchors).  Meta tag shapes per https://go.dev/ref/mod#vcs-find.

Gated on the configured baseurl, so socle never claims an import
prefix for a domain nobody chose.
diff --git a/serve.go b/serve.go
index 23b1226..5a23982 100644
--- a/serve.go
+++ b/serve.go
@@ -95,6 +95,16 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		http.NotFound(w, r)
 		return
 	}
+	// The go toolchain's vanity-import probe outranks all other dispatch under a repo:
+	// go queries the full package path (so this must answer on subpaths, not just the home),
+	// and it must run before the trailing-slash redirect below, which would drop the query.
+	// Gated on a configured baseurl: the import prefix is a public claim that ends up in
+	// module caches and trackers, so socle only makes it on purpose -- without a baseurl
+	// the probe is inert and the URL means whatever it meant before.
+	if snap.baseURL != "" && r.URL.Query().Get("go-get") == "1" {
+		serveGoGetMeta(w, rp, snap.baseURL)
+		return
+	}
 	if rest == "" {
 		// Canonicalize the repo home to a trailing slash, so relative URLs in the
 		// rendered README resolve under the repo's own prefix.
@@ -125,6 +135,25 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+// serveGoGetMeta answers the vanity-import probe: a minimal page whose go-import meta tag
+// maps the import prefix onto this repo's clone URL, plus a go-source tag teaching
+// pkg.go.dev to deep-link into socle's own tree pages.
+// (The go-source file template carries no #L{line} fragment: file views have no line
+// anchors, and an anchor that lands nowhere would be a small lie.)
+// Written with Fprintf, no templating or escaping: both inputs are config-validated into
+// HTML-inert charsets (baseURL to scheme://host, repo names to segments of [A-Za-z0-9._-]).
+func serveGoGetMeta(w http.ResponseWriter, rp *repo, baseURL string) {
+	prefix := strings.TrimPrefix(strings.TrimPrefix(baseURL, "https://"), "http://") + "/" + rp.name
+	repoURL := baseURL + "/" + rp.name
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	fmt.Fprintf(w, `<!DOCTYPE html>
+<html><head>
+<meta name="go-import" content="%s git %s">
+<meta name="go-source" content="%s %s/ %s/tree/HEAD{/dir} %s/tree/HEAD{/dir}/{file}">
+</head><body><a href="/%s/">%s</a></body></html>
+`, prefix, repoURL, prefix, repoURL, repoURL, repoURL, rp.name, prefix)
+}
+
 // httpError maps a plumbing-layer error onto an HTTP response:
 // errInvalid is the client's fault (400), errNotFound speaks for itself (404),
 // and anything else is internal (500) -- logged in full, reported generically.
diff --git a/serve_test.go b/serve_test.go
index 70aef6e..ea7c364 100644
--- a/serve_test.go
+++ b/serve_test.go
@@ -272,3 +272,50 @@ func TestCommitPageShapes(t *testing.T) {
 		t.Errorf("merge commit shows %d parent links, want 2", got)
 	}
 }
+
+// TestGoGetMeta covers the vanity-import answers: with a configured baseurl, any GET
+// under a repo carrying ?go-get=1 gets the meta-tag page (exact content asserted,
+// since the go toolchain parses it); without one, the probe is inert -- fail closed,
+// so socle never claims an import path for a domain nobody chose.
+func TestGoGetMeta(t *testing.T) {
+	t.Parallel()
+	wantImport := `<meta name="go-import" content="code.example/fixturama git https://code.example/fixturama">`
+	wantSource := `<meta name="go-source" content="code.example/fixturama https://code.example/fixturama/ https://code.example/fixturama/tree/HEAD{/dir} https://code.example/fixturama/tree/HEAD{/dir}/{file}">`
+
+	s := newServer(map[string]*repo{"fixturama": fixtureRepo(t)}, "https://code.example", display.NewTheme("test"))
+	for _, target := range []string{
+		"/fixturama?go-get=1",           // the slashless home: no redirect detour (it would drop the query)
+		"/fixturama/?go-get=1",          // the home itself
+		"/fixturama/sub/pkg?go-get=1",   // go queries the full package path, not just the module root
+		"/fixturama/tree/main?go-get=1", // even paths that mean something else to socle answer the probe
+	} {
+		rec := do(s, target, nil, nil)
+		if rec.Code != 200 {
+			t.Fatalf("GET %s: status %d, want 200", target, rec.Code)
+		}
+		body := rec.Body.String()
+		if !strings.Contains(body, wantImport) {
+			t.Errorf("GET %s: body lacks the go-import tag\nwant: %s\ngot body:\n%s", target, wantImport, body)
+		}
+		if !strings.Contains(body, wantSource) {
+			t.Errorf("GET %s: body lacks the go-source tag\nwant: %s", target, wantSource)
+		}
+	}
+	// The configured address also becomes the displayed clone URL.
+	if body := do(s, "/fixturama/", nil, nil).Body.String(); !strings.Contains(body, "git clone https://code.example/fixturama") {
+		t.Errorf("home page clone line does not use the configured baseurl")
+	}
+
+	// Without a baseurl: the probe must change nothing.
+	bare := fixtureServer(t)
+	if rec := do(bare, "/fixturama?go-get=1", nil, nil); rec.Code != 301 {
+		t.Errorf("no baseurl: slashless home with go-get=1: status %d, want the usual 301", rec.Code)
+	}
+	rec := do(bare, "/fixturama/?go-get=1", nil, nil)
+	if rec.Code != 200 {
+		t.Fatalf("no baseurl: home with go-get=1: status %d, want 200", rec.Code)
+	}
+	if body := rec.Body.String(); strings.Contains(body, "go-import") {
+		t.Errorf("no baseurl: home page must not carry a go-import tag")
+	}
+}