package engine import ( "bytes" "errors" "io/fs" "os" "path/filepath" "strconv" "strings" yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml" ) func activeOverlay(name string) bool { return name != "" && name != "default" } func validOverlayName(name string) bool { if !activeOverlay(name) { return true } for _, character := range name { if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_' || character == '-' { continue } return false } return true } func loadSelectedOverlayDocument(source Source, context *FilesystemContext, main parsedSource, base *composedDocument, sources []SourceInfo, name string, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) { rootPath, _, _, diagnostic := importFilesystem(source, context, main) if diagnostic != nil { return nil, cloneSourceInfos(sources), []Diagnostic{*diagnostic} } overlayPath := filepath.Join(rootPath, "overlays", name+".yml") canonical, err := canonicalExistingPath(overlayPath) if err != nil { code := CodeOverlayInvalid message := "selected overlay could not be resolved" if errors.Is(err, fs.ErrNotExist) { code, message = CodeOverlayMissing, "selected overlay does not exist" } else if errors.Is(err, fs.ErrPermission) { code, message = CodeImportPermission, "selected overlay cannot be accessed" } return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(code, message, nil)} } if !containedPath(rootPath, canonical) { return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay resolves outside the configuration root", nil)} } for _, existing := range sources { if existing.Path != "" && existing.Path == canonical { return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayRepeated, "selected overlay is already present in the base source graph", nil)} } } if len(sources)+1 > limits.sources { return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the source-count limit", nil)} } root, err := os.OpenRoot(rootPath) if err != nil { value := globFilesystemDiagnostic(err, "configuration root could not be opened for the selected overlay", Location{}) value.Primary = nil return nil, cloneSourceInfos(sources), []Diagnostic{value} } rootClosed := false defer func() { if !rootClosed { _ = root.Close() } }() relative, err := filepath.Rel(rootPath, canonical) if err != nil || filepath.IsAbs(relative) || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) { return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay could not be addressed inside the configuration root", nil)} } sourceID := "source-" + strconv.Itoa(len(sources)+1) overlayLocation := &Location{SourceID: sourceID} overlaySource := SourceInfo{ID: sourceID, Label: filepath.ToSlash(filepath.Join("overlays", name+".yml")), Path: canonical, Layer: LayerOverlay} sources = append(cloneSourceInfos(sources), overlaySource) data, diagnostic := readOverlayFile(root, relative, sourceID, limits.bytes-base.inputBytes) if diagnostic != nil { return nil, sources, []Diagnostic{*diagnostic} } overlayRoot, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{Depth: maxDocumentDepth, Nodes: maxDocumentNodes}) if len(issues) != 0 { return nil, sources, parserDiagnostics(sourceID, issues) } nodeCount := countInputNodes(overlayRoot) if base.inputNodes+nodeCount > limits.nodes { return nil, sources, []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the aggregate node limit", overlayLocation)} } if diagnostics := overlayDirectiveDiagnostics(overlayRoot, Path{}); len(diagnostics) != 0 { return nil, sources, diagnostics } if versionNode := fragmentVersionClaim(overlayRoot, rule); versionNode != nil { location := publicLocation(versionNode.Location) return nil, sources, []Diagnostic{{ Code: CodeOverlayVersion, Severity: SeverityError, Path: rule.Path.clone(), Message: "overlays cannot contribute to or replace the schema-version path", Primary: &location, }} } closeErr := root.Close() rootClosed = true if closeErr != nil { return nil, sources, []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed after reading the selected overlay", nil)} } overlayd, diagnostics := overlayDocuments(base, overlayRoot) if overlayd != nil { overlayd.inputBytes = base.inputBytes + int64(len(data)) overlayd.inputNodes = base.inputNodes + nodeCount } return overlayd, sources, diagnostics } func readOverlayFile(root *os.Root, relative, sourceID string, remaining int64) ([]byte, *Diagnostic) { limit := min(int64(maxSourceBytes), remaining) if limit < 0 { limit = 0 } data, failure := readRegularFile(relative, root.Stat, root.Open, limit) if failure != nil { value := sourceReadDiagnostic(failure, sourceID, CodeOverlayLimitExceeded, "selected overlay exceeds a source or aggregate byte limit") return nil, &value } return data, nil }