mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-02-24 19:53:27 +00:00
chore: modernize workflows, security scanning, and linting configuration (#50)
* build: update Go 1.25, CI workflows, and build tooling - Upgrade to Go 1.25 - Add benchmark targets to Makefile - Implement parallel gosec execution - Lock tool versions for reproducibility - Add shellcheck directives to scripts - Update CI workflows with improved caching * refactor: migrate from golangci-lint to revive - Replace golangci-lint with revive for linting - Configure comprehensive revive rules - Fix all EditorConfig violations - Add yamllint and yamlfmt support - Remove deprecated .golangci.yml * refactor: rename utils to shared and deduplicate code - Rename utils package to shared - Add shared constants package - Deduplicate constants across packages - Address CodeRabbit review feedback * fix: resolve SonarQube issues and add safety guards - Fix all 73 SonarQube OPEN issues - Add nil guards for resourceMonitor, backpressure, metricsCollector - Implement io.Closer for headerFileReader - Propagate errors from processing helpers - Add metrics and templates packages - Improve error handling across codebase * test: improve test infrastructure and coverage - Add benchmarks for cli, fileproc, metrics - Improve test coverage for cli, fileproc, config - Refactor tests with helper functions - Add shared test constants - Fix test function naming conventions - Reduce cognitive complexity in benchmark tests * docs: update documentation and configuration examples - Update CLAUDE.md with current project state - Refresh README with new features - Add usage and configuration examples - Add SonarQube project configuration - Consolidate config.example.yaml * fix: resolve shellcheck warnings in scripts - Use ./*.go instead of *.go to prevent dash-prefixed filenames from being interpreted as options (SC2035) - Remove unreachable return statement after exit (SC2317) - Remove obsolete gibidiutils/ directory reference * chore(deps): upgrade go dependencies * chore(lint): megalinter fixes * fix: improve test coverage and fix file descriptor leaks - Add defer r.Close() to fix pipe file descriptor leaks in benchmark tests - Refactor TestProcessorConfigureFileTypes with helper functions and assertions - Refactor TestProcessorLogFinalStats with output capture and keyword verification - Use shared constants instead of literal strings (TestFilePNG, FormatMarkdown, etc.) - Reduce cognitive complexity by extracting helper functions * fix: align test comments with function names Remove underscores from test comments to match actual function names: - benchmark/benchmark_test.go (2 fixes) - fileproc/filetypes_config_test.go (4 fixes) - fileproc/filetypes_registry_test.go (6 fixes) - fileproc/processor_test.go (6 fixes) - fileproc/resource_monitor_types_test.go (4 fixes) - fileproc/writer_test.go (3 fixes) * fix: various test improvements and bug fixes - Remove duplicate maxCacheSize check in filetypes_registry_test.go - Shorten long comment in processor_test.go to stay under 120 chars - Remove flaky time.Sleep in collector_test.go, use >= 0 assertion - Close pipe reader in benchmark_test.go to fix file descriptor leak - Use ContinueOnError in flags_test.go to match ResetFlags behavior - Add nil check for p.ui in processor_workers.go before UpdateProgress - Fix resource_monitor_validation_test.go by setting hardMemoryLimitBytes directly * chore(yaml): add missing document start markers Add --- document start to YAML files to satisfy yamllint: - .github/workflows/codeql.yml - .github/workflows/build-test-publish.yml - .github/workflows/security.yml - .github/actions/setup/action.yml * fix: guard nil resourceMonitor and fix test deadlock - Guard resourceMonitor before CreateFileProcessingContext call - Add ui.UpdateProgress on emergency stop and path error returns - Fix potential deadlock in TestProcessFile using wg.Go with defer close
This commit is contained in:
@@ -1,156 +1,7 @@
|
||||
// Package fileproc handles file processing, collection, and output formatting.
|
||||
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
|
||||
}
|
||||
import "strings"
|
||||
|
||||
// ApplyCustomExtensions applies custom extensions from configuration.
|
||||
func (r *FileTypeRegistry) ApplyCustomExtensions(
|
||||
@@ -182,24 +33,12 @@ func (r *FileTypeRegistry) addExtensions(extensions []string, adder func(string)
|
||||
|
||||
// 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
|
||||
func ConfigureFromSettings(
|
||||
customImages, customBinary []string,
|
||||
customLanguages map[string]string,
|
||||
disabledImages, disabledBinary, disabledLanguages []string,
|
||||
) {
|
||||
registry := DefaultRegistry()
|
||||
registry.ApplyCustomExtensions(customImages, customBinary, customLanguages)
|
||||
registry.DisableExtensions(disabledImages, disabledBinary, disabledLanguages)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user