package configkit import ( "os" "path/filepath" "strings" ) func validateFilesystemContext(source Source, context *FilesystemContext, main parsedSource) *Diagnostic { rootPath, _, _, diagnostic := importFilesystem(source, context, main) if diagnostic != nil { return diagnostic } root, err := os.OpenRoot(rootPath) if err != nil { value := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{}) value.Primary = nil return &value } if err := root.Close(); err != nil { value := importDiagnostic(CodeImportRead, "configuration root could not be closed", nil) return &value } return nil } func filesystemOptionDiagnostics(source Source, context *FilesystemContext) []Diagnostic { switch source.kind { case sourceNone: return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "NoSource cannot use filesystem context")} case sourceFile: if context.BaseDir != "" { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory")} } case sourceBytes, sourceReader: if context.BaseDir == "" || context.Root == "" { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "byte and reader filesystem context requires base and root directories")} } if !filepath.IsAbs(context.BaseDir) || !filepath.IsAbs(context.Root) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "byte and reader filesystem base and root directories must be absolute")} } } return nil } func importFilesystem(source Source, context *FilesystemContext, main parsedSource) (rootPath, basePath, mainPath string, diagnostic *Diagnostic) { if main.source == nil || main.document == nil { value := importDiagnostic(CodeImportInvalid, "imports require a selected configuration source", nil) return "", "", "", &value } if source.kind == sourceFile { if context != nil && context.BaseDir != "" { value := optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory") return "", "", "", &value } canonical, err := canonicalExistingPath(source.path) if err != nil { value := importDiagnostic(CodeImportInvalid, "selected source path could not be canonicalized", nil) return "", "", "", &value } mainPath = canonical basePath = filepath.Dir(canonical) rootPath = basePath if context != nil && context.Root != "" { rootPath, err = canonicalExistingPath(context.Root) if err != nil { value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil) return "", "", "", &value } } } else { if context == nil || context.BaseDir == "" || context.Root == "" { value := optionDiagnostic(CodeOptionsInvalid, "byte and reader imports require absolute base and root directories") return "", "", "", &value } if !filepath.IsAbs(context.BaseDir) || !filepath.IsAbs(context.Root) { value := optionDiagnostic(CodeOptionsInvalid, "byte and reader import base and root directories must be absolute") return "", "", "", &value } var err error basePath, err = canonicalExistingPath(context.BaseDir) if err != nil { value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil) return "", "", "", &value } rootPath, err = canonicalExistingPath(context.Root) if err != nil { value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil) return "", "", "", &value } } if !containedPath(rootPath, basePath) || (mainPath != "" && !containedPath(rootPath, mainPath)) { value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil) return "", "", "", &value } for _, candidate := range []string{rootPath, basePath} { info, err := os.Stat(candidate) if err != nil || !info.IsDir() { value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil) return "", "", "", &value } } return rootPath, basePath, mainPath, nil } func canonicalExistingPath(name string) (string, error) { absolute, err := filepath.Abs(name) if err != nil { return "", err } return filepath.EvalSymlinks(filepath.Clean(absolute)) } func containedPath(root, target string) bool { relative, err := filepath.Rel(root, target) if err != nil || filepath.IsAbs(relative) { return false } return relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) }