Complete bounded glob imports
This commit is contained in:
110
imports.go
110
imports.go
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -19,6 +18,7 @@ import (
|
||||
const (
|
||||
maxImportDepth = 64
|
||||
maxImportSources = 1024
|
||||
maxGlobEntries = 1_000_000
|
||||
)
|
||||
|
||||
type importLimits struct {
|
||||
@@ -26,12 +26,13 @@ type importLimits struct {
|
||||
sources int
|
||||
bytes int64
|
||||
nodes int
|
||||
entries int
|
||||
}
|
||||
|
||||
func defaultImportLimits() importLimits {
|
||||
return importLimits{
|
||||
depth: maxImportDepth, sources: maxImportSources,
|
||||
bytes: maxTotalBytes, nodes: maxDocumentNodes,
|
||||
bytes: maxTotalBytes, nodes: maxDocumentNodes, entries: maxGlobEntries,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,13 +54,14 @@ type importTraversal struct {
|
||||
limits importLimits
|
||||
rule *VersionRule
|
||||
|
||||
documents []*document.Node
|
||||
sources []SourceInfo
|
||||
active map[string]int
|
||||
visited map[string]importRoute
|
||||
stack []importRoute
|
||||
usedBytes int64
|
||||
usedNodes int
|
||||
documents []*document.Node
|
||||
sources []SourceInfo
|
||||
active map[string]int
|
||||
visited map[string]importRoute
|
||||
stack []importRoute
|
||||
usedBytes int64
|
||||
usedNodes int
|
||||
usedEntries int
|
||||
}
|
||||
|
||||
func loadImportedDocuments(source Source, context *FilesystemContext, main parsedSource, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
|
||||
@@ -69,7 +71,9 @@ func loadImportedDocuments(source Source, context *FilesystemContext, main parse
|
||||
}
|
||||
root, err := os.OpenRoot(rootPath)
|
||||
if err != nil {
|
||||
return nil, sourceInfos(main.source), []Diagnostic{importDiagnostic(CodeImportInvalid, "configuration root could not be opened", nil)}
|
||||
diagnostic := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{})
|
||||
diagnostic.Primary = nil
|
||||
return nil, sourceInfos(main.source), []Diagnostic{diagnostic}
|
||||
}
|
||||
|
||||
mainInfo := cloneSourceInfo(*main.source)
|
||||
@@ -107,12 +111,49 @@ func loadImportedDocuments(source Source, context *FilesystemContext, main parse
|
||||
return nil, cloneSourceInfos(state.sources), diagnostics
|
||||
}
|
||||
if closeErr != nil {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{importDiagnostic(CodeImportInvalid, "configuration root could not be closed", nil)}
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed", nil)}
|
||||
}
|
||||
composed, diagnostics := composeDocuments(state.documents...)
|
||||
return composed, cloneSourceInfos(state.sources), diagnostics
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -209,8 +250,26 @@ func (s *importTraversal) checkInitialLimits() *Diagnostic {
|
||||
func (s *importTraversal) walkDeclarations(basePath string, declarations []importDeclaration, depth int) []Diagnostic {
|
||||
for _, declaration := range declarations {
|
||||
if declaration.glob != "" {
|
||||
location := declaration.location
|
||||
return []Diagnostic{importDiagnostic(CodeImportUnsupported, "glob imports are not available in this release boundary", &location)}
|
||||
matches, diagnostics := s.expandImportGlob(basePath, declaration)
|
||||
if len(diagnostics) != 0 {
|
||||
return diagnostics
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
if declaration.optional {
|
||||
continue
|
||||
}
|
||||
location := declaration.location
|
||||
return []Diagnostic{importDiagnostic(CodeImportMissing, "required import glob matched no files", &location)}
|
||||
}
|
||||
for _, match := range matches {
|
||||
matched := declaration
|
||||
matched.path = match
|
||||
matched.glob = ""
|
||||
if diagnostics := s.visitPath(basePath, matched, depth+1); len(diagnostics) != 0 {
|
||||
return diagnostics
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if diagnostics := s.visitPath(basePath, declaration, depth+1); len(diagnostics) != 0 {
|
||||
return diagnostics
|
||||
@@ -518,8 +577,29 @@ func validImportPath(value string) bool {
|
||||
if value == "" || strings.TrimSpace(value) != value || path.IsAbs(value) || strings.HasPrefix(value, "//") {
|
||||
return false
|
||||
}
|
||||
parsed, err := url.Parse(value)
|
||||
return err == nil && parsed.Scheme == ""
|
||||
return !hasImportScheme(value)
|
||||
}
|
||||
|
||||
func hasImportScheme(value string) bool {
|
||||
colon := strings.IndexByte(value, ':')
|
||||
slash := strings.IndexByte(value, '/')
|
||||
if colon <= 0 || (slash >= 0 && slash < colon) {
|
||||
return false
|
||||
}
|
||||
for index := 0; index < colon; index++ {
|
||||
character := value[index]
|
||||
if index == 0 {
|
||||
if (character < 'a' || character > 'z') && (character < 'A' || character > 'Z') {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (character < 'a' || character > 'z') && (character < 'A' || character > 'Z') &&
|
||||
(character < '0' || character > '9') && character != '+' && character != '-' && character != '.' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func sourceInfos(source *SourceInfo) []SourceInfo {
|
||||
|
||||
Reference in New Issue
Block a user