[mirror] Scalable static site server for Git forges (like GitHub Pages)
1package git_pages
2
3import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "net"
8 "net/http"
9 "strings"
10)
11
12func ServeCaddy(w http.ResponseWriter, r *http.Request) {
13 domain := r.URL.Query().Get("domain")
14 if domain == "" {
15 http.Error(w, "domain parameter required", http.StatusBadRequest)
16 return
17 }
18
19 // Save the backend some effort from queries that are essentially guaranteed to fail.
20 // While TLS certificates may be provisionsed for IP addresses under special circumstances[^1],
21 // this isn't really what git-pages is designed for, and object store accesses can cost money.
22 // [^1]: https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate
23 if ip := net.ParseIP(domain); ip != nil {
24 logc.Println(r.Context(), "caddy:", domain, 404, "(bare IP)")
25 w.WriteHeader(http.StatusNotFound)
26 return
27 }
28
29 var err error
30 domain = strings.ToLower(domain)
31
32 // Run a cheap check as to whether we might be serving the domain.
33 var found = domainCache.CheckDomain(r.Context(), domain)
34
35 if !found {
36 // Run an expensive check as to whether we are actually serving the domain.
37 found, err = backend.CheckDomain(r.Context(), domain)
38 }
39
40 if !found {
41 // If we don't serve the domain, but a fallback server does, then we should let our
42 // Caddy instance request a TLS certificate. Otherwise, we'll never have an opportunity
43 // to proxy the request further. (This functionality was originally added for Codeberg
44 // Pages v2, which would under some circumstances return certificates with subjectAltName
45 // not valid for the SNI. Go's TLS stack makes `tls.Dial` return an error for these,
46 // thankfully making it unnecessary to examine X.509 certificates manually here.)
47 found, err = tryDialWithSNI(r.Context(), domain)
48 if err != nil {
49 logc.Printf(r.Context(), "caddy err: check SNI: %s\n", err)
50 }
51 }
52
53 if found {
54 logc.Println(r.Context(), "caddy:", domain, 200)
55 w.WriteHeader(http.StatusOK)
56 } else if err == nil {
57 logc.Println(r.Context(), "caddy:", domain, 404)
58 w.WriteHeader(http.StatusNotFound)
59 } else {
60 logc.Println(r.Context(), "caddy:", domain, 500)
61 w.WriteHeader(http.StatusInternalServerError)
62 fmt.Fprintln(w, err)
63 }
64}
65
66func tryDialWithSNI(ctx context.Context, domain string) (bool, error) {
67 if config.Fallback.ProxyTo == nil {
68 return false, nil
69 }
70
71 fallbackURL := config.Fallback.ProxyTo
72 if fallbackURL.Scheme != "https" {
73 return false, nil
74 }
75
76 connectHost := fallbackURL.Host
77 if fallbackURL.Port() != "" {
78 connectHost += ":" + fallbackURL.Port()
79 } else {
80 connectHost += ":443"
81 }
82
83 logc.Printf(ctx, "caddy: check TLS %s", fallbackURL)
84 connection, err := tls.Dial("tcp", connectHost, &tls.Config{ServerName: domain})
85 if err != nil {
86 return false, err
87 }
88 connection.Close()
89 return true, nil
90}