| 1 | package cmdutils |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "unicode" |
| 6 | ) |
| 7 | |
| 8 | const maxRunes = 128 |
| 9 | |
| 10 | // CleanAndTrim sanitizes untrusted strings from remote peers to prevent display issues |
| 11 | // across web UIs, terminals, and logs. It replaces control characters, format characters, |
| 12 | // and surrogates with U+FFFD (�), then enforces a maximum length of 128 runes. |
| 13 | // |
| 14 | // This follows the libp2p identify specification and RFC 9839 guidance: |
| 15 | // replacing problematic code points is preferred over deletion as deletion |
| 16 | // is a known security risk. |
| 17 | func CleanAndTrim(str string) string { |
| 18 | // Build sanitized result |
| 19 | var result []rune |
| 20 | for _, r := range str { |
| 21 | // Replace control characters (Cc) with U+FFFD - prevents terminal escapes, CR, LF, etc. |
| 22 | if unicode.Is(unicode.Cc, r) { |
| 23 | result = append(result, '\uFFFD') |
| 24 | continue |
| 25 | } |
| 26 | // Replace format characters (Cf) with U+FFFD - prevents RTL/LTR overrides, zero-width chars |
| 27 | if unicode.Is(unicode.Cf, r) { |
| 28 | result = append(result, '\uFFFD') |
| 29 | continue |
| 30 | } |
| 31 | // Replace surrogate characters (Cs) with U+FFFD - invalid in UTF-8 |
| 32 | if unicode.Is(unicode.Cs, r) { |
| 33 | result = append(result, '\uFFFD') |
| 34 | continue |
| 35 | } |
| 36 | // Private use characters (Co) are preserved per spec |
| 37 | result = append(result, r) |
| 38 | } |
| 39 | |
| 40 | // Convert to string and trim whitespace |
| 41 | sanitized := strings.TrimSpace(string(result)) |
| 42 | |
| 43 | // Enforce maximum length (128 runes, not bytes) |
| 44 | runes := []rune(sanitized) |
| 45 | if len(runes) > maxRunes { |
| 46 | return string(runes[:maxRunes]) |
| 47 | } |
| 48 | |
| 49 | return sanitized |
| 50 | } |