Tencent/WeKnora · error

invalid file name: %w

Error message

invalid file name: %w

What it means

SaveBytes validates the caller-supplied fileName with secutils.SafeFileName before storing bytes under <baseDir>/<tenantID>/exports. When the name contains path separators, traversal sequences (../), illegal characters, or is empty, SafeFileName returns an error and SaveBytes wraps it as 'invalid file name'.

Source

Thrown at internal/application/service/file/local.go:224

		return "", fmt.Errorf("failed to copy file content: %w", err)
	}

	relPath, _ := filepath.Rel(s.baseDir, dstPath)
	newPath := localScheme + filepath.ToSlash(relPath)
	logger.Infof(ctx, "Copied local file %s to %s", srcPath, newPath)
	return newPath, nil
}

// SaveBytes saves bytes data to a file and returns the file path
// temp parameter is ignored for local storage (no auto-expiration support)
// fileName 仅允许安全文件名,禁止路径遍历(如 ../../)
func (s *localFileService) SaveBytes(ctx context.Context, data []byte, tenantID uint64, fileName string, temp bool) (string, error) {
	logger.Infof(ctx, "Saving bytes data: fileName=%s, size=%d, tenantID=%d, temp=%v", fileName, len(data), tenantID, temp)

	safeName, err := secutils.SafeFileName(fileName)
	if err != nil {
		logger.Errorf(ctx, "Invalid fileName for SaveBytes: %v", err)
		return "", fmt.Errorf("invalid file name: %w", err)
	}

	// Create storage directory with tenant ID
	dir := filepath.Join(s.baseDir, fmt.Sprintf("%d", tenantID), "exports")
	if err := os.MkdirAll(dir, 0o755); err != nil {
		logger.Errorf(ctx, "Failed to create directory: %v", err)
		return "", fmt.Errorf("failed to create directory: %w", err)
	}

	// Generate unique filename using timestamp
	ext := filepath.Ext(safeName)
	baseName := safeName[:len(safeName)-len(ext)]
	uniqueFileName := fmt.Sprintf("%s_%d%s", baseName, time.Now().UnixNano(), ext)
	filePath := filepath.Join(dir, uniqueFileName)

	// Write data to file
	if err := os.WriteFile(filePath, data, 0o644); err != nil {
		logger.Errorf(ctx, "Failed to write file: %v", err)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Sanitize the filename before calling SaveBytes: strip directory components (filepath.Base) and replace disallowed characters with '_' or a generated UUID.
  2. Never build storage names from raw user input — pass a server-generated name (UUID/timestamp + safe extension) and keep the original only as metadata.
  3. Inspect the wrapped error to see which rule SafeFileName enforced and adjust the input accordingly.
  4. If a valid name is being rejected, compare it against the sanitizer's allowlist in secutils.SafeFileName and normalize (NFC, strip controls) beforehand.

Example fix

// before
newPath, err := svc.SaveBytes(ctx, data, tenantID, rawUserFilename, false)
// after
safe := filepath.Base(rawUserFilename)
safe = strings.Map(func(r rune) rune {
    if r < 32 || strings.ContainsRune("/\\:*?\"<>|", r) {
        return '_'
    }
    return r
}, safe)
if safe == "" || safe == "." || safe == ".." {
    safe = fmt.Sprintf("upload-%d", time.Now().UnixNano())
}
newPath, err := svc.SaveBytes(ctx, data, tenantID, safe, false)
Defensive patterns

Strategy: validation

Validate before calling

func validFileName(name string) bool {
    if name == "" || len(name) > 255 {
        return false
    }
    if name != filepath.Base(name) || name == "." || name == ".." {
        return false
    }
    return !strings.ContainsAny(name, "/\\:*?\"<>|") && !strings.ContainsFunc(name, func(r rune) bool { return r < 32 || r == 127 })
}
// call validFileName(fileName) before SaveBytes

Type guard

func isSafeFileName(s string) bool {
    return validFileName(s) // reuses validation above
}

Try / catch

newPath, err := svc.SaveBytes(ctx, data, tenantID, fileName, temp)
if err != nil {
    if strings.HasPrefix(err.Error(), "invalid file name") {
        // fall back to a server-generated name
        fileName = fmt.Sprintf("upload-%d%s", time.Now().UnixNano(), filepath.Ext(fileName))
        newPath, err = svc.SaveBytes(ctx, data, tenantID, fileName, temp)
    }
    if err != nil {
        return fmt.Errorf("save failed: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling SaveBytes with fileName containing '/' or '\\', '..' path segments, control/illegal characters, an empty string, or a name that fails the sanitizer's allowlist (e.g. reserved characters on the target OS).

Common situations: Storing user-uploaded filenames verbatim (browser sends odd names like 'report/../x.pdf' or a filename with a full path from Windows clients); test harnesses passing empty names; i18n filenames with characters rejected by the sanitizer.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/0ca23271246cbd96. Report an issue: GitHub.