package upstream import ( "errors" "strings" "time" "github.com/rs/zerolog/log" "codeberg.org/codeberg/pages/server/cache" "codeberg.org/codeberg/pages/server/gitea" ) // canonicalDomainCacheTimeout specifies the timeout for the canonical domain cache. var canonicalDomainCacheTimeout = 15 * time.Minute // CheckCanonicalDomain returns the canonical domain specified in the repo (using the `.domains` file). func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, mainDomainSuffix string, canonicalDomainConfigs []string, canonicalDomainCache cache.ICache) (domain string, valid bool) { canonicalDomainCacheKey := o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch + "/(" + strings.Join(canonicalDomainConfigs, "|") + ")" var domains []string // Check if this request is cached. if cachedValue, ok := canonicalDomainCache.Get(canonicalDomainCacheKey); ok { domains = cachedValue.([]string) } else { // Create cache entry for future invocations. domains = o.canonicalDomainList(giteaClient, mainDomainSuffix, canonicalDomainConfigs) // Add result to cache. _ = canonicalDomainCache.Set(canonicalDomainCacheKey, domains, canonicalDomainCacheTimeout) } for _, domain := range domains { if domain == actualDomain { valid = true break } } // Return the first domain from the list and return if any of the domains // matched the requested domain. return domains[0], valid } // canonicalDomainList returns a list of normalized canonical domains as reported by the repository being served. func (o *Options) canonicalDomainList(giteaClient *gitea.Client, mainDomainSuffix string, canonicalDomainConfigs []string) []string { domainConfigMerge := "" for _, canonicalDomainConfig := range canonicalDomainConfigs { body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, canonicalDomainConfig) if err != nil && !errors.Is(err, gitea.ErrorNotFound) { log.Error().Err(err).Msgf("could not read %s of %s/%s", canonicalDomainConfig, o.TargetOwner, o.TargetRepo) continue } // Ensures files that don't end with a `\n` don't cause domains to be concatenated when combining files. domainConfigMerge = domainConfigMerge + "\n" + string(body) } domains := normalizeDomainEntries(domainConfigMerge) // Add [owner].[pages-domain] as valid domain. domains = append(domains, o.pageMainDomain(mainDomainSuffix)) return domains } // pageMainDomain returns the [owner].[pages-domain] domain. func (o *Options) pageMainDomain(mainDomainSuffix string) string { pageMainDomain := o.TargetOwner + mainDomainSuffix // If the target repository isn't called pages, add `/[repository]` to the // previous valid domain. if o.TargetRepo != "" && o.TargetRepo != "pages" { pageMainDomain += "/" + o.TargetRepo } return pageMainDomain } // normalizeDomainEntries returns a list of domains, ill formatted domains are skipped. // domainEntries is a new-line separated list of domains. func normalizeDomainEntries(domainEntries string) []string { domains := []string{} for _, domain := range strings.Split(domainEntries, "\n") { domain = strings.ToLower(domain) domain = strings.TrimSpace(domain) domain = strings.TrimPrefix(domain, "http://") domain = strings.TrimPrefix(domain, "https://") // Skip blank lines. // Skip commented lines. // Skip poorly formatted lines. // Skip domains without '.'. if domain == "" || strings.HasPrefix(domain, "#") || strings.ContainsAny(domain, "\t /") || !strings.ContainsRune(domain, '.') { continue } domains = append(domains, domain) } return domains }