Skip to content

fix: more aggressive file name sanitizing - #1001

Merged
Zibbp merged 2 commits into
mainfrom
more-aggressive-file-sanitizing
Dec 31, 2025
Merged

fix: more aggressive file name sanitizing#1001
Zibbp merged 2 commits into
mainfrom
more-aggressive-file-sanitizing

Conversation

@Zibbp

@Zibbp Zibbp commented Dec 31, 2025

Copy link
Copy Markdown
Owner

removes emojis and additional unsafe characters

removes emojis and additional unsafe characters
@coderabbitai

coderabbitai Bot commented Dec 31, 2025

Copy link
Copy Markdown

Walkthrough

Reimplemented SanitizeFileName with Unicode-aware logic: removes emoji/control characters, preserves only RFC3986 unreserved ASCII, converts separators/whitespace to single underscores, trims edge characters and defaults empty results to "unnamed_file". Tests moved to a table-driven format validating safety and length.

Changes

Cohort / File(s) Summary
SanitizeFileName implementation
internal/utils/utils.go
Replaced prior sanitizer with Unicode-aware implementation that drops emoji and control chars, treats non-unreserved characters as separators (collapsing to underscores), converts whitespace to underscores, trims leading/trailing dots/underscores/hyphens/spaces, ensures non-empty result (defaults to "unnamed_file"), added isUnreservedASCII and isEmojiRune, and imported unicode. Public function parameter name changed (fileNamein).
SanitizeFileName tests
internal/utils/utils_test.go
Replaced inline tests with TestSanitizeFileName_Table table-driven tests using in/want fields. Added isURLAndFSSafeUnreserved helper and runtime assertions to check non-empty output, max length 255, and that output contains only unreserved URL/file-system-safe characters.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: more aggressive file name sanitizing' directly and clearly summarizes the main change: the implementation of more aggressive sanitization in the SanitizeFileName function that removes emojis and unsafe characters.
Description check ✅ Passed The description 'removes emojis and additional unsafe characters' is directly related to the changeset, describing the core behavioral improvements made to the SanitizeFileName function.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch more-aggressive-file-sanitizing

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/utils/utils.go (2)

92-94: Redundant trim operation.

Line 94 is redundant—_ is already included in the cutset on line 93 ("._- ~"). Both lines perform the same trim for underscores.

Proposed fix
 	// trim separators/dots/spaces and collapse underscores already handled above.
 	out = strings.Trim(out, "._- ~") // also avoids trailing dot/space issues
-	out = strings.Trim(out, "_")

96-101: Consider enforcing the 255-character filename limit.

The test suite validates that output length does not exceed 255 characters, but this function doesn't enforce that constraint. A very long input with mostly ASCII unreserved characters would produce an output exceeding typical filesystem limits.

Proposed enhancement
 	// Handle empty/special names
 	if out == "" || out == "." || out == ".." {
 		out = "unnamed_file"
 	}

+	// Enforce filesystem-safe length limit
+	if len(out) > 255 {
+		out = out[:255]
+		// Trim any trailing separator chars introduced by truncation
+		out = strings.TrimRight(out, "._-~")
+	}
+
 	return out
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8683790 and 1f2257f.

📒 Files selected for processing (2)
  • internal/utils/utils.go
  • internal/utils/utils_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/utils/utils_test.go (1)
internal/utils/utils.go (1)
  • SanitizeFileName (50-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build OCI Images (ubuntu-24.04-arm, arm64, linux/arm64)
  • GitHub Check: Build OCI Images (ubuntu-24.04, amd64, linux/amd64)
  • GitHub Check: Analyze (go)
🔇 Additional comments (4)
internal/utils/utils_test.go (2)

8-46: Well-structured table-driven tests with good coverage.

The test cases cover important scenarios: whitespace handling, illegal filesystem characters, emoji removal (including ZWJ sequences), Unicode fallback to unnamed_file, and edge cases like . and ...

One minor observation: the test at line 23 is named "trailing_underscore_trim" but actually tests that spaces around file...name are trimmed while internal dots are preserved. Consider renaming to "surrounding_spaces_trim" for clarity.

Also note: the tests validate a 255-character limit (line 38-40), but the implementation in utils.go does not enforce this constraint. If a very long input is passed, the output could exceed 255 characters.


48-61: LGTM!

The helper correctly validates RFC3986 unreserved characters. Byte-by-byte iteration is appropriate here since SanitizeFileName guarantees ASCII-only output.

internal/utils/utils.go (2)

104-118: LGTM!

Correctly identifies RFC3986 unreserved characters with a clean switch implementation.


120-182: Comprehensive emoji detection.

Good coverage of emoji blocks, ZWJ sequences, modifiers, and common singletons. The comments documenting each range are helpful for maintainability.

Note that Unicode continues to add new emoji in each version, so this is inherently a best-effort approach. The current coverage should handle the vast majority of real-world cases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/utils/utils.go (2)

93-99: Consider extracting repeated logic into a helper.

The trimming and empty-name fallback logic is duplicated before and after truncation. Consider extracting this into a helper function to improve maintainability.

🔎 Proposed refactor
+// finalizeName trims separators and handles empty/reserved names
+func finalizeName(s string) string {
+	s = strings.Trim(s, "._- ~")
+	s = strings.Trim(s, "_")
+	if s == "" || s == "." || s == ".." {
+		return "unnamed_file"
+	}
+	return s
+}
+
 func SanitizeFileName(in string) string {
 	// ... existing loop logic ...
 	out := b.String()
 
-	// trim separators/dots/spaces and collapse underscores already handled above.
-	out = strings.Trim(out, "._- ~") // also avoids trailing dot/space issues
-	out = strings.Trim(out, "_")
-
-	// Handle empty/special names
-	if out == "" || out == "." || out == ".." {
-		out = "unnamed_file"
-	}
+	out = finalizeName(out)
 
 	if len(out) > 255 {
 		out = truncatePreserveExt(out, 255)
-		out = strings.Trim(out, "._- ~")
-		out = strings.Trim(out, "_")
-		if out == "" || out == "." || out == ".." {
-			out = "unnamed_file"
-		}
+		out = finalizeName(out)
 	}
 
 	return out
 }

Also applies to: 103-107


49-111: Consider handling Windows reserved filenames.

The function doesn't check for Windows reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9, etc. On Windows systems, these names (even with extensions like CON.txt) can cause issues. If cross-platform compatibility is important, consider adding a check.

🔎 Example implementation

Add a helper to check reserved names:

// isWindowsReserved checks if name is a Windows reserved filename
func isWindowsReserved(name string) bool {
	// Remove extension for check
	base := name
	if i := strings.LastIndexByte(name, '.'); i > 0 {
		base = name[:i]
	}
	base = strings.ToUpper(base)
	
	reserved := []string{
		"CON", "PRN", "AUX", "NUL",
		"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
		"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
	}
	
	for _, r := range reserved {
		if base == r {
			return true
		}
	}
	return false
}

Then in SanitizeFileName, after the final name is determined:

 	out = finalizeName(out)
+	
+	// Avoid Windows reserved names
+	if isWindowsReserved(out) {
+		out = "_" + out
+	}
 
 	return out
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f2257f and f513daa.

📒 Files selected for processing (1)
  • internal/utils/utils.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test
  • GitHub Check: Analyze (go)
  • GitHub Check: Build OCI Images (ubuntu-24.04, amd64, linux/amd64)
  • GitHub Check: Build OCI Images (ubuntu-24.04-arm, arm64, linux/arm64)
🔇 Additional comments (5)
internal/utils/utils.go (5)

56-88: Character filtering logic is sound.

The implementation correctly:

  • Removes emoji using the comprehensive isEmojiRune helper
  • Collapses consecutive separators with the lastWasUnderscore flag
  • Preserves only RFC3986 unreserved ASCII characters

The control character check (line 72) covers ASCII control characters (0x00-0x1F, 0x7F) but doesn't explicitly handle Unicode control characters (e.g., C0/C1 control blocks). Given that non-ASCII characters become separators anyway (line 84), this is acceptable.


113-140: Extension preservation works correctly for single extensions.

The implementation properly:

  • Preserves the file extension when truncating
  • Handles edge cases (extension too long, base becomes empty)
  • Falls back to "file" as a base when needed

Note that for compound extensions like ".tar.gz", only the last part (".gz") is preserved. While this might not be ideal for all use cases, it's a reasonable simplification.


142-156: RFC3986 unreserved character set correctly implemented.

The function accurately checks for RFC3986 unreserved characters: a-z, A-Z, 0-9, -, ., _, and ~.


158-220: Emoji detection is comprehensive but not exhaustive.

The implementation covers:

  • Common emoji blocks (emoticons, symbols, pictographs, transport, etc.)
  • Modifiers and combining characters (skin tones, ZWJ, variation selectors)
  • Regional indicators (flags) and tag characters
  • Specific symbols (©, ®, ™)

Note that emoji specifications evolve with new Unicode releases. While this implementation covers the most common cases, new emoji may not be detected until the ranges are updated. This is a reasonable best-effort approach.

Also note that ©, ®, and ™ are removed, which might affect legitimate use cases like "Product™" → "Product_". Ensure this aligns with your requirements.


93-93: Tilde trimming from edges is intentional behavior.

The test case "trim_dashes_dots_spaces" with input -._~abc~_.- expecting output abc confirms that tildes are intentionally removed from edges via strings.Trim(out, "._- ~"). While tilde is correctly preserved in the middle (as an RFC3986 unreserved character via isUnreservedASCII), the edge-trimming appears intentional for consistency with other separators.

Consider adding a code comment at line 93 explaining the design rationale—specifically that RFC3986 unreserved characters are preserved internally but trimmed from edges to avoid trailing special characters, even though this creates an asymmetric treatment of tilde.

@Zibbp
Zibbp merged commit ec2eee0 into main Dec 31, 2025
8 checks passed
@Zibbp
Zibbp deleted the more-aggressive-file-sanitizing branch December 31, 2025 15:49
@Zibbp Zibbp mentioned this pull request Jan 2, 2026
@FibreTTP

FibreTTP commented Jan 4, 2026

Copy link
Copy Markdown

Not sure if this is a good idea given that only English characters are now allowed. I have video titles in directory/file names for ease of organisation, and I (and probably every non-Windows, non-Traefik user) have had no issues with Japanese characters, emojis, spaces, etc. thus far.

Having this be a toggleable setting would be nice.

@Zibbp

Zibbp commented Jan 4, 2026

Copy link
Copy Markdown
Owner Author

In tests I've had issues with weird characters causing tests to fail so I still think doing this is a good idea. I do think it's fine to be more lenient about RFC3986 so I'll allow non-english characters while still removing emojis as browsers are pretty good and encoding characters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants