package engine import ( "errors" "io/fs" "path" "path/filepath" "sort" "strings" ) type importSelectionOutcome uint8 const ( importSelectionNone importSelectionOutcome = iota importSelectionHasCandidates ) type selectedImportCandidate struct { path string accessPath string label string site importFieldSite } // importSelection contains lexical candidates only; admission assigns source identity later. type importSelection struct { candidates []selectedImportCandidate outcome importSelectionOutcome } func selectNamedImportCandidates(access *filesystemAccess, basePath string, spec importDeclarationSpec, policy resolvedImportPolicy) (importSelection, []Diagnostic) { normalized := make([]selectedImportCandidate, 0, len(spec.paths)) seen := make(map[string]struct{}, len(spec.paths)) for index, relative := range spec.paths { accessPath := access.join(basePath, relative) candidatePath := accessPath if access.native == nil { candidatePath = filepath.ToSlash(filepath.Clean(accessPath)) } if _, exists := seen[candidatePath]; exists { continue } seen[candidatePath] = struct{}{} var site importFieldSite if index < len(spec.selectorSites) { site = spec.selectorSites[index] } else { site = spec.fieldSites["path"] } normalized = append(normalized, selectedImportCandidate{path: candidatePath, accessPath: accessPath, label: relativeImportLabel(access, basePath, accessPath), site: site}) } sort.Slice(normalized, func(left, right int) bool { return normalized[left].path < normalized[right].path }) candidates := make([]selectedImportCandidate, 0, len(normalized)) for _, candidate := range normalized { missing, _, diagnostic := validateNamedImportTarget(access, candidate, policy.required, false) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing { continue } if excludedBasename(path.Base(candidate.path), policy.exclude) { continue } candidates = append(candidates, candidate) } selection := importSelection{candidates: candidates} if len(candidates) != 0 { selection.outcome = importSelectionHasCandidates } return selection, nil } func selectDirectoryImportCandidates(access *filesystemAccess, basePath string, spec importDeclarationSpec, policy resolvedImportPolicy, limits importLimits, accounting *importAccounting) (importSelection, []Diagnostic) { roots := make([]selectedImportCandidate, 0, len(spec.paths)) seenRoots := make(map[string]struct{}, len(spec.paths)) for index, relative := range spec.paths { accessPath := access.join(basePath, relative) candidatePath := normalizedImportCandidatePath(access, accessPath) if _, exists := seenRoots[candidatePath]; exists { continue } seenRoots[candidatePath] = struct{}{} site := spec.fieldSites["dir"] if index < len(spec.selectorSites) { site = spec.selectorSites[index] } roots = append(roots, selectedImportCandidate{path: candidatePath, accessPath: accessPath, site: site}) } sort.Slice(roots, func(left, right int) bool { return roots[left].path < roots[right].path }) if accounting == nil { accounting = &importAccounting{} } discovered := make([]selectedImportCandidate, 0) seenCandidates := make(map[string]struct{}) for _, root := range roots { missing, isDirectory, diagnostic := validateNamedImportTarget(access, root, false, true) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing { continue } if !isDirectory { return importSelection{}, []Diagnostic{*importSelectionDiagnostic(root.site, CodeImportInvalid, "directory search root is not a directory")} } rootTarget, targetErr := access.importEntryTarget(root.accessPath, nil, true) if targetErr != nil { return importSelection{}, []Diagnostic{*importDirectoryTargetDiagnostic(root.site, targetErr)} } root.accessPath = rootTarget.identity entries, missing, diagnostic := importDirectoryEntries(access, root.accessPath, root.site, limits, accounting) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing { continue } stack := []importDirectoryFrame{{directory: root, depth: 0, entries: entries, identity: rootTarget.identity}} activeDirectories := map[string]struct{}{rootTarget.identity: {}} for len(stack) != 0 { frameIndex := len(stack) - 1 frame := &stack[frameIndex] if frame.next >= len(frame.entries) { delete(activeDirectories, frame.identity) stack = stack[:frameIndex] continue } entry := frame.entries[frame.next] frame.next++ accessPath := access.join(frame.directory.accessPath, entry.Name()) lexicalPath := access.join(frame.directory.path, entry.Name()) candidate := selectedImportCandidate{ path: normalizedImportCandidatePath(access, lexicalPath), accessPath: accessPath, label: relativeImportLabel(access, basePath, lexicalPath), site: frame.directory.site, } if !policy.hidden && strings.HasPrefix(entry.Name(), ".") { continue } target, entryErr := access.importEntryTarget(candidate.accessPath, entry, policy.follow) if entryErr != nil { if errors.Is(entryErr, fs.ErrNotExist) { continue } return importSelection{}, []Diagnostic{*importDirectoryTargetDiagnostic(frame.directory.site, entryErr)} } if target.symlink && !policy.follow { continue } if target.dir { if !directoryDepthAllows(policy, frame.depth) { continue } if _, exists := activeDirectories[target.identity]; exists { return importSelection{}, []Diagnostic{*importSelectionDiagnostic(frame.directory.site, CodeImportCycle, "directory traversal cycle detected")} } childMissing, childIsDirectory, childDiagnostic := validateNamedImportTarget(access, candidate, false, true) if childDiagnostic != nil { return importSelection{}, []Diagnostic{*childDiagnostic} } if childMissing || !childIsDirectory { continue } childEntries, vanished, childDiagnostic := importDirectoryEntries(access, candidate.accessPath, candidate.site, limits, accounting) if childDiagnostic != nil { return importSelection{}, []Diagnostic{*childDiagnostic} } if !vanished { candidate.accessPath = target.identity activeDirectories[target.identity] = struct{}{} stack = append(stack, importDirectoryFrame{directory: candidate, depth: frame.depth + 1, entries: childEntries, identity: target.identity}) } continue } if target.identity != "" { candidate.accessPath = target.identity } else { canonical, canonicalErr := access.canonical(accessPath) if canonicalErr != nil { if errors.Is(canonicalErr, fs.ErrNotExist) { continue } return importSelection{}, []Diagnostic{globFilesystemDiagnostic(canonicalErr, "directory search file could not be canonicalized", frame.directory.site.location)} } candidate.accessPath = canonical } if !matchesImportFilename(entry.Name(), policy.pattern) { continue } if _, exists := seenCandidates[candidate.path]; exists { continue } seenCandidates[candidate.path] = struct{}{} discovered = append(discovered, candidate) } } sort.Slice(discovered, func(left, right int) bool { return discovered[left].path < discovered[right].path }) selected := make([]selectedImportCandidate, 0, len(discovered)) for _, candidate := range discovered { missing, isDirectory, diagnostic := validateNamedImportTarget(access, candidate, false, true) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing || isDirectory || excludedBasename(path.Base(candidate.path), policy.exclude) { continue } selected = append(selected, candidate) } if policy.required && len(selected) == 0 { return importSelection{}, []Diagnostic{*importSelectionDiagnostic(spec.fieldSites["dir"], CodeImportMissing, "required directory import selected no files")} } selection := importSelection{candidates: selected} if len(selected) != 0 { selection.outcome = importSelectionHasCandidates } return selection, nil } type importDirectoryFrame struct { directory selectedImportCandidate depth int entries []fs.DirEntry next int identity string } func importDirectoryTargetDiagnostic(site importFieldSite, err error) *Diagnostic { if isImportLinkCycle(err) { return importSelectionDiagnostic(site, CodeImportCycle, "directory traversal cycle detected") } if errors.Is(err, errImportPathOutsideRoot) { return importSelectionDiagnostic(site, CodeImportOutsideRoot, "import target resolves outside the configuration root") } return importSelectionDiagnostic(site, CodeImportInvalid, "directory entry could not be inspected") } func importDirectoryEntries(access *filesystemAccess, directory string, site importFieldSite, limits importLimits, accounting *importAccounting) ([]fs.DirEntry, bool, *Diagnostic) { target, targetErr := access.importEntryTarget(directory, nil, true) if targetErr != nil { if errors.Is(targetErr, fs.ErrNotExist) { return nil, true, nil } return nil, false, importDirectoryTargetDiagnostic(site, targetErr) } readPath := target.identity if readPath == "" { readPath = directory } entries := make([]fs.DirEntry, 0) err := visitBoundedImportDirectory(access, readPath, limits.entries, accounting, func(entry fs.DirEntry) error { entries = append(entries, entry) return nil }) if errors.Is(err, errImportDirectoryEntryLimit) { return nil, false, importSelectionDiagnostic(site, CodeImportLimitExceeded, "directory search exceeds the directory-entry visit limit") } if errors.Is(err, fs.ErrNotExist) { return nil, true, nil } if err != nil { return nil, false, importSelectionDiagnostic(site, CodeImportInvalid, "directory search root could not be read") } sort.Slice(entries, func(left, right int) bool { return entries[left].Name() < entries[right].Name() }) return entries, false, nil } func directoryDepthAllows(policy resolvedImportPolicy, currentDepth int) bool { if !policy.recursive { return false } childDepth := currentDepth + 1 return !policy.depthSet || policy.depth == 0 || childDepth < policy.depth } func normalizedImportCandidatePath(access *filesystemAccess, accessPath string) string { if access.native != nil { return accessPath } return filepath.ToSlash(filepath.Clean(accessPath)) } func relativeImportLabel(access *filesystemAccess, basePath, candidatePath string) string { if access.native != nil { if candidatePath == basePath { return "." } prefix := strings.TrimSuffix(basePath, "/") + "/" if basePath == "." { return candidatePath } if strings.HasPrefix(candidatePath, prefix) { return strings.TrimPrefix(candidatePath, prefix) } return candidatePath } label, err := filepath.Rel(basePath, candidatePath) if err == nil { return filepath.ToSlash(label) } return filepath.ToSlash(candidatePath) } func matchesImportFilename(name string, patterns []string) bool { if patterns == nil { return true } for _, pattern := range patterns { matched, err := path.Match(pattern, name) if err == nil && matched { return true } } return false } func validateNamedImportTarget(access *filesystemAccess, candidate selectedImportCandidate, required, allowDirectory bool) (bool, bool, *Diagnostic) { if !access.contains(candidate.accessPath) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportOutsideRoot, "import target is outside the configuration root") } canonical, err := access.canonical(candidate.accessPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { if required { return false, false, importSelectionDiagnostic(candidate.site, CodeImportMissing, "required import target does not exist") } return true, false, nil } if errors.Is(err, fs.ErrPermission) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target cannot be accessed") } return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target could not be canonicalized") } if !access.contains(canonical) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportOutsideRoot, "import target resolves outside the configuration root") } info, err := access.stat(canonical) if err != nil { if errors.Is(err, fs.ErrNotExist) { if required { return false, false, importSelectionDiagnostic(candidate.site, CodeImportMissing, "required import target does not exist") } return true, false, nil } if errors.Is(err, fs.ErrPermission) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target cannot be accessed") } return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target could not be inspected") } if info.IsDir() && allowDirectory { return false, true, nil } if !info.Mode().IsRegular() { return false, false, importSelectionDiagnostic(candidate.site, CodeSourceNotRegular, "source is not a regular file") } return false, false, nil } func excludedBasename(name string, patterns []string) bool { for _, pattern := range patterns { matched, err := path.Match(pattern, name) if err == nil && matched { return true } } return false } func importSelectionDiagnostic(site importFieldSite, code Code, message string) *Diagnostic { location := site.location return &Diagnostic{ Code: code, Severity: SeverityError, Path: site.path.clone(), Message: message, Primary: &location, } }