mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 03:24:05 +00:00
* chore(ci): update go to 1.25, add permissions and envs * fix(ci): update pr-lint.yml * chore: update go, fix linting * fix: tests and linting * fix(lint): lint fixes, renovate should now pass * fix: updates, security upgrades * chore: workflow updates, lint * fix: more lint, checkmake, and other fixes * fix: more lint, convert scripts to POSIX compliant * fix: simplify codeql workflow * tests: increase test coverage, fix found issues * fix(lint): editorconfig checking, add to linters * fix(lint): shellcheck, add to linters * fix(lint): apply cr comment suggestions * fix(ci): remove step-security/harden-runner * fix(lint): remove duplication, apply cr fixes * fix(ci): tests in CI/CD pipeline * chore(lint): deduplication of strings * fix(lint): apply cr comment suggestions * fix(ci): actionlint * fix(lint): apply cr comment suggestions * chore: lint, add deps management
206 lines
6.1 KiB
Go
206 lines
6.1 KiB
Go
package fileproc
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// MaxRegistryEntries is the maximum number of entries allowed in registry config slices/maps.
|
|
MaxRegistryEntries = 1000
|
|
// MaxExtensionLength is the maximum length for a single extension string.
|
|
MaxExtensionLength = 100
|
|
)
|
|
|
|
// RegistryConfig holds configuration for file type registry.
|
|
// All paths must be relative without path traversal (no ".." or leading "/").
|
|
// Extensions in CustomLanguages keys must start with "." or be alphanumeric with underscore/hyphen.
|
|
type RegistryConfig struct {
|
|
// CustomImages: file extensions to treat as images (e.g., ".svg", ".webp").
|
|
// Must be relative paths without ".." or leading separators.
|
|
CustomImages []string
|
|
|
|
// CustomBinary: file extensions to treat as binary (e.g., ".bin", ".dat").
|
|
// Must be relative paths without ".." or leading separators.
|
|
CustomBinary []string
|
|
|
|
// CustomLanguages: maps file extensions to language names (e.g., {".tsx": "TypeScript"}).
|
|
// Keys must start with "." or be alphanumeric with underscore/hyphen.
|
|
CustomLanguages map[string]string
|
|
|
|
// DisabledImages: image extensions to disable from default registry.
|
|
DisabledImages []string
|
|
|
|
// DisabledBinary: binary extensions to disable from default registry.
|
|
DisabledBinary []string
|
|
|
|
// DisabledLanguages: language extensions to disable from default registry.
|
|
DisabledLanguages []string
|
|
}
|
|
|
|
// Validate checks the RegistryConfig for invalid entries and enforces limits.
|
|
func (c *RegistryConfig) Validate() error {
|
|
// Validate CustomImages
|
|
if err := validateExtensionSlice(c.CustomImages, "CustomImages"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate CustomBinary
|
|
if err := validateExtensionSlice(c.CustomBinary, "CustomBinary"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate CustomLanguages
|
|
if len(c.CustomLanguages) > MaxRegistryEntries {
|
|
return fmt.Errorf(
|
|
"CustomLanguages exceeds maximum entries (%d > %d)",
|
|
len(c.CustomLanguages),
|
|
MaxRegistryEntries,
|
|
)
|
|
}
|
|
for ext, lang := range c.CustomLanguages {
|
|
if err := validateExtension(ext, "CustomLanguages key"); err != nil {
|
|
return err
|
|
}
|
|
if len(lang) > MaxExtensionLength {
|
|
return fmt.Errorf(
|
|
"CustomLanguages value %q exceeds maximum length (%d > %d)",
|
|
lang,
|
|
len(lang),
|
|
MaxExtensionLength,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Validate Disabled slices
|
|
if err := validateExtensionSlice(c.DisabledImages, "DisabledImages"); err != nil {
|
|
return err
|
|
}
|
|
if err := validateExtensionSlice(c.DisabledBinary, "DisabledBinary"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return validateExtensionSlice(c.DisabledLanguages, "DisabledLanguages")
|
|
}
|
|
|
|
// validateExtensionSlice validates a slice of extensions for path safety and limits.
|
|
func validateExtensionSlice(slice []string, fieldName string) error {
|
|
if len(slice) > MaxRegistryEntries {
|
|
return fmt.Errorf("%s exceeds maximum entries (%d > %d)", fieldName, len(slice), MaxRegistryEntries)
|
|
}
|
|
for _, ext := range slice {
|
|
if err := validateExtension(ext, fieldName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateExtension validates a single extension for path safety.
|
|
//
|
|
//revive:disable-next-line:cyclomatic
|
|
func validateExtension(ext, context string) error {
|
|
// Reject empty strings
|
|
if ext == "" {
|
|
return fmt.Errorf("%s entry cannot be empty", context)
|
|
}
|
|
|
|
if len(ext) > MaxExtensionLength {
|
|
return fmt.Errorf(
|
|
"%s entry %q exceeds maximum length (%d > %d)",
|
|
context, ext, len(ext), MaxExtensionLength,
|
|
)
|
|
}
|
|
|
|
// Reject absolute paths
|
|
if filepath.IsAbs(ext) {
|
|
return fmt.Errorf("%s entry %q is an absolute path (not allowed)", context, ext)
|
|
}
|
|
|
|
// Reject path traversal
|
|
if strings.Contains(ext, "..") {
|
|
return fmt.Errorf("%s entry %q contains path traversal (not allowed)", context, ext)
|
|
}
|
|
|
|
// For extensions, verify they start with "." or are alphanumeric
|
|
if strings.HasPrefix(ext, ".") {
|
|
// Reject extensions containing path separators
|
|
if strings.ContainsRune(ext, filepath.Separator) || strings.ContainsRune(ext, '/') ||
|
|
strings.ContainsRune(ext, '\\') {
|
|
return fmt.Errorf("%s entry %q contains path separators (not allowed)", context, ext)
|
|
}
|
|
// Valid extension format
|
|
return nil
|
|
}
|
|
|
|
// Check if purely alphanumeric (for bare names)
|
|
for _, r := range ext {
|
|
isValid := (r >= 'a' && r <= 'z') ||
|
|
(r >= 'A' && r <= 'Z') ||
|
|
(r >= '0' && r <= '9') ||
|
|
r == '_' || r == '-'
|
|
if !isValid {
|
|
return fmt.Errorf(
|
|
"%s entry %q contains invalid characters (must start with '.' or be alphanumeric/_/-)",
|
|
context,
|
|
ext,
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ApplyCustomExtensions applies custom extensions from configuration.
|
|
func (r *FileTypeRegistry) ApplyCustomExtensions(
|
|
customImages, customBinary []string,
|
|
customLanguages map[string]string,
|
|
) {
|
|
// Add custom image extensions
|
|
r.addExtensions(customImages, r.AddImageExtension)
|
|
|
|
// Add custom binary extensions
|
|
r.addExtensions(customBinary, r.AddBinaryExtension)
|
|
|
|
// Add custom language mappings
|
|
for ext, lang := range customLanguages {
|
|
if ext != "" && lang != "" {
|
|
r.AddLanguageMapping(strings.ToLower(ext), lang)
|
|
}
|
|
}
|
|
}
|
|
|
|
// addExtensions is a helper to add multiple extensions.
|
|
func (r *FileTypeRegistry) addExtensions(extensions []string, adder func(string)) {
|
|
for _, ext := range extensions {
|
|
if ext != "" {
|
|
adder(strings.ToLower(ext))
|
|
}
|
|
}
|
|
}
|
|
|
|
// ConfigureFromSettings applies configuration settings to the registry.
|
|
// This function is called from main.go after config is loaded to avoid circular imports.
|
|
// It validates the configuration before applying it.
|
|
func ConfigureFromSettings(config RegistryConfig) error {
|
|
// Validate configuration first
|
|
if err := config.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
registry := GetDefaultRegistry()
|
|
|
|
// Only apply custom extensions if they are non-empty (len() for nil slices/maps is zero)
|
|
if len(config.CustomImages) > 0 || len(config.CustomBinary) > 0 || len(config.CustomLanguages) > 0 {
|
|
registry.ApplyCustomExtensions(config.CustomImages, config.CustomBinary, config.CustomLanguages)
|
|
}
|
|
|
|
// Only disable extensions if they are non-empty
|
|
if len(config.DisabledImages) > 0 || len(config.DisabledBinary) > 0 || len(config.DisabledLanguages) > 0 {
|
|
registry.DisableExtensions(config.DisabledImages, config.DisabledBinary, config.DisabledLanguages)
|
|
}
|
|
|
|
return nil
|
|
}
|