Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 4 additions & 70 deletions internal/processor/strategy/after-first.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,20 @@
package strategy

import (
"bufio"
"fmt"
"os"
)

// AfterFirstAppearStrategy finds the first appearance of markers
type AfterFirstAppearStrategy struct{}

func (s *AfterFirstAppearStrategy) FindInitSectionPosition(filePath string, markers []string) (int64, int64, error) {
file, err := os.Open(filePath)
scanner, err := NewFileScanner(filePath)
if err != nil {
return 0, 0, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
lineNum := int64(0)

// Sliding window for multiline marker detection
window := make([]string, 0, len(markers)+10)

for scanner.Scan() {
line := scanner.Text()
window = append(window, line)

// Keep window size reasonable
maxWindowSize := len(markers) + 10
if len(window) > maxWindowSize {
window = window[1:] // Remove oldest line
}

// Try to find start marker pattern in current window
if matchPos := findStartMarkerInWindow(window, markers, lineNum-int64(len(window))+1); matchPos != nil {
return matchPos.begin, matchPos.end, nil
}

lineNum++
}

return 0, 0, fmt.Errorf("start marker not found: %v", markers)
return scanner.FindFirstMarkerFromStart(markers)
}

func (s *AfterFirstAppearStrategy) FindPrintSectionPosition(filePath string, markers []string, searchFromLine int64) (int64, int64, error) {
file, err := os.Open(filePath)
scanner, err := NewFileScanner(filePath)
if err != nil {
return 0, 0, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
lineNum := int64(0)

// Skip to the search start position
for lineNum <= searchFromLine && scanner.Scan() {
lineNum++
}

// Sliding window for multiline marker detection
window := make([]string, 0, len(markers)+10)

for scanner.Scan() {
line := scanner.Text()
window = append(window, line)

// Keep window size reasonable
maxWindowSize := len(markers) + 10
if len(window) > maxWindowSize {
window = window[1:] // Remove oldest line
}

// Calculate the correct window start line position for this iteration
currentWindowStart := lineNum - int64(len(window)) + 1

// Try to find marker pattern in current window
if matchPos := findStartMarkerInWindow(window, markers, currentWindowStart); matchPos != nil {
return matchPos.begin, matchPos.end, nil
}

lineNum++
}

return 0, 0, fmt.Errorf("end marker not found after line %d: %v", searchFromLine, markers)
return scanner.FindFirstMarkerFromLine(markers, searchFromLine+1)
}
74 changes: 4 additions & 70 deletions internal/processor/strategy/before-first.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,20 @@
package strategy

import (
"bufio"
"fmt"
"os"
)

// BeforeCommandStrategy finds markers that appear before specific commands
type BeforeCommandStrategy struct{}

func (s *BeforeCommandStrategy) FindInitSectionPosition(filePath string, markers []string) (int64, int64, error) {
file, err := os.Open(filePath)
scanner, err := NewFileScanner(filePath)
if err != nil {
return 0, 0, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
lineNum := int64(0)

// Sliding window for multiline marker detection
window := make([]string, 0, len(markers)+10)

for scanner.Scan() {
line := scanner.Text()
window = append(window, line)

// Keep window size reasonable
maxWindowSize := len(markers) + 10
if len(window) > maxWindowSize {
window = window[1:] // Remove oldest line
}

// Try to find start marker pattern in current window
if matchPos := findStartMarkerInWindow(window, markers, lineNum-int64(len(window))+1); matchPos != nil {
return matchPos.begin, matchPos.end, nil
}

lineNum++
}

return 0, 0, fmt.Errorf("start marker not found before commands: %v", markers)
return scanner.FindFirstMarkerFromStart(markers)
}

func (s *BeforeCommandStrategy) FindPrintSectionPosition(filePath string, markers []string, searchFromLine int64) (int64, int64, error) {
file, err := os.Open(filePath)
scanner, err := NewFileScanner(filePath)
if err != nil {
return 0, 0, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
lineNum := int64(0)

// Skip to the search start position
for lineNum <= searchFromLine && scanner.Scan() {
lineNum++
}

// Sliding window for multiline marker detection
window := make([]string, 0, len(markers)+10)

for scanner.Scan() {
line := scanner.Text()
window = append(window, line)

// Keep window size reasonable
maxWindowSize := len(markers) + 10
if len(window) > maxWindowSize {
window = window[1:] // Remove oldest line
}

// Calculate the correct window start line position for this iteration
currentWindowStart := lineNum - int64(len(window)) + 1

// Try to find marker pattern in current window
if matchPos := findStartMarkerInWindow(window, markers, currentWindowStart); matchPos != nil {
return matchPos.begin, matchPos.end, nil
}

lineNum++
}

return 0, 0, fmt.Errorf("end marker not found before commands after line %d: %v", searchFromLine, markers)
return scanner.FindFirstMarkerFromLine(markers, searchFromLine+1)
}
122 changes: 121 additions & 1 deletion internal/processor/strategy/common.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,132 @@
package strategy

import "strings"
import (
"bufio"
"fmt"
"os"
"strings"
)

type startMarkerMatch struct {
begin int64
end int64
}

// FileScanner provides common file scanning functionality for strategies
type FileScanner struct {
filePath string
lines []string
}

// NewFileScanner creates a new file scanner
func NewFileScanner(filePath string) (*FileScanner, error) {
lines, err := readAllLines(filePath)
if err != nil {
return nil, err
}
return &FileScanner{
filePath: filePath,
lines: lines,
}, nil
}

// FindFirstMarkerFromStart finds the first occurrence of markers from the beginning
func (fs *FileScanner) FindFirstMarkerFromStart(markers []string) (int64, int64, error) {
return fs.findMarkerWithSlidingWindow(markers, 0, len(fs.lines)-1, true)
}

// FindFirstMarkerFromLine finds the first occurrence of markers starting from a specific line
func (fs *FileScanner) FindFirstMarkerFromLine(markers []string, startLine int64) (int64, int64, error) {
if startLine < 0 || int(startLine) >= len(fs.lines) {
return 0, 0, fmt.Errorf("start line %d out of bounds", startLine)
}
return fs.findMarkerWithSlidingWindow(markers, int(startLine), len(fs.lines)-1, true)
}

// FindLastMarkerFromStart finds the last occurrence of markers from the beginning
func (fs *FileScanner) FindLastMarkerFromStart(markers []string) (int64, int64, error) {
return fs.findMarkerWithSlidingWindow(markers, 0, len(fs.lines)-1, false)
}

// FindLastMarkerFromLine finds the last occurrence of markers starting from a specific line
func (fs *FileScanner) FindLastMarkerFromLine(markers []string, startLine int64) (int64, int64, error) {
if startLine < 0 || int(startLine) >= len(fs.lines) {
return 0, 0, fmt.Errorf("start line %d out of bounds", startLine)
}
return fs.findMarkerWithSlidingWindow(markers, int(startLine), len(fs.lines)-1, false)
}

// findMarkerWithSlidingWindow implements the sliding window search algorithm
func (fs *FileScanner) findMarkerWithSlidingWindow(markers []string, startIdx, endIdx int, returnFirst bool) (int64, int64, error) {
if len(markers) == 0 {
return 0, 0, fmt.Errorf("no markers provided")
}

var lastFoundBegin, lastFoundEnd int64 = -1, -1

if len(markers) == 1 {
// Single line marker - simple search
for i := startIdx; i <= endIdx; i++ {
if strings.Contains(strings.TrimSpace(fs.lines[i]), strings.TrimSpace(markers[0])) {
if returnFirst {
return int64(i), int64(i), nil
}
lastFoundBegin = int64(i)
lastFoundEnd = int64(i)
}
}
} else {
// Multiline marker - sliding window approach
window := make([]string, 0, len(markers)+10)

for i := startIdx; i <= endIdx; i++ {
line := fs.lines[i]
window = append(window, line)

// Keep window size reasonable
maxWindowSize := len(markers) + 10
if len(window) > maxWindowSize {
window = window[1:] // Remove oldest line
}

// Calculate the correct window start line position
currentWindowStart := int64(i) - int64(len(window)) + 1

// Try to find marker pattern in current window
if matchPos := findStartMarkerInWindow(window, markers, currentWindowStart); matchPos != nil {
if returnFirst {
return matchPos.begin, matchPos.end, nil
}
lastFoundBegin = matchPos.begin
lastFoundEnd = matchPos.end
}
}
}

if lastFoundBegin == -1 {
return 0, 0, fmt.Errorf("marker not found: %v", markers)
}

return lastFoundBegin, lastFoundEnd, nil
}

// readAllLines reads all lines from a file into memory
func readAllLines(filePath string) ([]string, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()

var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}

return lines, scanner.Err()
}

// findStartMarkerInWindow searches for start marker pattern in the sliding window
func findStartMarkerInWindow(window []string, markers []string, windowStartLine int64) *startMarkerMatch {
if len(markers) == 1 {
Expand Down
45 changes: 45 additions & 0 deletions internal/webserver/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
ErrorTypeFileIO ErrorType = "file_io"
ErrorTypeUpload ErrorType = "upload"
ErrorTypeInternal ErrorType = "internal"
ErrorTypeSecurity ErrorType = "security"
)

// ErrorResponse represents a structured error response
Expand Down Expand Up @@ -50,6 +51,50 @@ func CategorizeErrorWithLang(err error, lang string) ErrorResponse {
errMsg := err.Error()
errMsgLower := strings.ToLower(errMsg)

// Security errors - handle these first as they're high priority
if strings.Contains(errMsgLower, "csrf") || strings.Contains(errMsgLower, "token") {
return ErrorResponse{
Type: ErrorTypeSecurity,
Code: "csrf_token_invalid",
Title: GetTranslation(lang, "error_security_title"),
Description: GetTranslation(lang, "error_security_description"),
Details: errMsg,
Suggestions: []string{
GetTranslation(lang, "error_security_suggestion_refresh"),
GetTranslation(lang, "error_security_suggestion_cookies"),
},
}
}

if strings.Contains(errMsgLower, "file validation") || strings.Contains(errMsgLower, "invalid file") {
return ErrorResponse{
Type: ErrorTypeSecurity,
Code: "file_validation_failed",
Title: GetTranslation(lang, "error_file_validation_title"),
Description: GetTranslation(lang, "error_file_validation_description"),
Details: errMsg,
Suggestions: []string{
GetTranslation(lang, "error_file_validation_suggestion_type"),
GetTranslation(lang, "error_file_validation_suggestion_size"),
GetTranslation(lang, "error_file_validation_suggestion_content"),
},
}
}

if strings.Contains(errMsgLower, "path traversal") || strings.Contains(errMsgLower, "dangerous") {
return ErrorResponse{
Type: ErrorTypeSecurity,
Code: "security_violation",
Title: GetTranslation(lang, "error_security_title"),
Description: GetTranslation(lang, "error_security_description"),
Details: errMsg,
Suggestions: []string{
GetTranslation(lang, "error_security_suggestion_filename"),
GetTranslation(lang, "error_security_suggestion_safe"),
},
}
}

// Template-related errors
if strings.Contains(errMsgLower, "template") || strings.Contains(errMsgLower, "parse") {
if strings.Contains(errMsgLower, "custom template") {
Expand Down
Loading