| 1 | package sftpfs |
| 2 | |
| 3 | import ( |
| 4 | "slices" |
| 5 | "unicode/utf8" |
| 6 | ) |
| 7 | |
| 8 | // Kind classifies file content for preview purposes. |
| 9 | type Kind int |
| 10 | |
| 11 | const ( |
| 12 | // KindText is UTF-8 (or ASCII) text safe to render and edit. |
| 13 | KindText Kind = iota |
| 14 | // KindBinary contains NUL bytes or invalid UTF-8; not editable as text. |
| 15 | KindBinary |
| 16 | ) |
| 17 | |
| 18 | const ( |
| 19 | // DefaultReadCap bounds a text preview to keep memory and transfer sane. |
| 20 | DefaultReadCap = 4 << 20 // 4 MiB |
| 21 | // sniffLen is how many leading bytes DetectKind inspects. |
| 22 | sniffLen = 8 << 10 // 8 KiB |
| 23 | ) |
| 24 | |
| 25 | // DetectKind classifies a leading sample of file content. A NUL byte marks |
| 26 | // binary immediately; otherwise the sample must be valid UTF-8 (allowing a |
| 27 | // trailing rune truncated by the sample boundary). |
| 28 | func DetectKind(sample []byte) Kind { |
| 29 | if len(sample) > sniffLen { |
| 30 | sample = sample[:sniffLen] |
| 31 | } |
| 32 | if slices.Contains(sample, 0) { |
| 33 | return KindBinary |
| 34 | } |
| 35 | if utf8.Valid(sample) { |
| 36 | return KindText |
| 37 | } |
| 38 | // The sample may have split a multi-byte rune at the tail; retry without |
| 39 | // the trailing partial rune before declaring binary. |
| 40 | for i := 0; i < utf8.UTFMax-1 && i < len(sample); i++ { |
| 41 | if utf8.Valid(sample[:len(sample)-1-i]) { |
| 42 | return KindText |
| 43 | } |
| 44 | } |
| 45 | return KindBinary |
| 46 | } |
| 47 |