[mirror] Scalable static site server for Git forges (like GitHub Pages)
1package git_pages
2
3import (
4 "fmt"
5 "slices"
6 "strings"
7
8 "github.com/valyala/fasttemplate"
9)
10
11type WildcardPattern struct {
12 Domain []string
13 CloneURL *fasttemplate.Template
14 IndexRepos []*fasttemplate.Template
15 IndexBranch string
16 Authorization bool
17}
18
19func (pattern *WildcardPattern) GetHost() string {
20 parts := []string{"*"}
21 parts = append(parts, pattern.Domain...)
22 return strings.Join(parts, ".")
23}
24
25// Returns `subdomain, found` where if `found == true`, `subdomain` contains the part of `host`
26// corresponding to the * in the domain pattern.
27func (pattern *WildcardPattern) Matches(host string) (string, bool) {
28 hostParts := strings.Split(host, ".")
29 hostLen := len(hostParts)
30 patternLen := len(pattern.Domain)
31
32 // host must have at least one more part than the pattern domain
33 if hostLen <= patternLen {
34 return "", false
35 }
36
37 // break the host parts into <subdomain parts> and <domain parts>
38 mid := hostLen - patternLen
39 prefix := hostParts[:mid]
40 suffix := hostParts[mid:]
41
42 // check if the suffix matches the domain
43 if !slices.Equal(suffix, pattern.Domain) {
44 return "", false
45 }
46
47 // return all the subdomain parts
48 subdomain := strings.Join(prefix, ".")
49 return subdomain, true
50}
51
52func (pattern *WildcardPattern) ApplyTemplate(userName string, projectName string) ([]string, string) {
53 var repoURLs []string
54 var branch string
55 repoURLTemplate := pattern.CloneURL
56 if projectName == ".index" {
57 for _, indexRepoTemplate := range pattern.IndexRepos {
58 indexRepo := indexRepoTemplate.ExecuteString(map[string]any{"user": userName})
59 repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
60 "user": userName,
61 "project": indexRepo,
62 }))
63 }
64 branch = pattern.IndexBranch
65 } else {
66 repoURLs = append(repoURLs, repoURLTemplate.ExecuteString(map[string]any{
67 "user": userName,
68 "project": projectName,
69 }))
70 branch = "pages"
71 }
72 return repoURLs, branch
73}
74
75func TranslateWildcards(configs []WildcardConfig) ([]*WildcardPattern, error) {
76 var wildcardPatterns []*WildcardPattern
77 for _, config := range configs {
78 cloneURLTemplate, err := fasttemplate.NewTemplate(config.CloneURL, "<", ">")
79 if err != nil {
80 return nil, fmt.Errorf("wildcard pattern: clone URL: %w", err)
81 }
82
83 var indexRepoTemplates []*fasttemplate.Template
84 var indexRepoBranch string = config.IndexRepoBranch
85 for _, indexRepo := range config.IndexRepos {
86 indexRepoTemplate, err := fasttemplate.NewTemplate(indexRepo, "<", ">")
87 if err != nil {
88 return nil, fmt.Errorf("wildcard pattern: index repo: %w", err)
89 }
90 indexRepoTemplates = append(indexRepoTemplates, indexRepoTemplate)
91 }
92
93 authorization := false
94 if config.Authorization != "" {
95 if slices.Contains([]string{"gogs", "gitea", "forgejo"}, config.Authorization) {
96 // Currently these are the only supported forges, and the authorization mechanism
97 // is the same for all of them.
98 authorization = true
99 } else {
100 return nil, fmt.Errorf(
101 "wildcard pattern: unknown authorization mechanism: %s",
102 config.Authorization,
103 )
104 }
105 }
106
107 wildcardPatterns = append(wildcardPatterns, &WildcardPattern{
108 Domain: strings.Split(config.Domain, "."),
109 CloneURL: cloneURLTemplate,
110 IndexRepos: indexRepoTemplates,
111 IndexBranch: indexRepoBranch,
112 Authorization: authorization,
113 })
114 }
115 return wildcardPatterns, nil
116}