| 162 | }) |
| 163 | |
| 164 | async function isBinaryFile(filepath: string, file: Bun.BunFile): Promise<boolean> { |
| 165 | const ext = path.extname(filepath).toLowerCase() |
| 166 | // binary check for common non-text extensions |
| 167 | switch (ext) { |
| 168 | case ".zip": |
| 169 | case ".tar": |
| 170 | case ".gz": |
| 171 | case ".exe": |
| 172 | case ".dll": |
| 173 | case ".so": |
| 174 | case ".class": |
| 175 | case ".jar": |
| 176 | case ".war": |
| 177 | case ".7z": |
| 178 | case ".doc": |
| 179 | case ".docx": |
| 180 | case ".xls": |
| 181 | case ".xlsx": |
| 182 | case ".ppt": |
| 183 | case ".pptx": |
| 184 | case ".odt": |
| 185 | case ".ods": |
| 186 | case ".odp": |
| 187 | case ".bin": |
| 188 | case ".dat": |
| 189 | case ".obj": |
| 190 | case ".o": |
| 191 | case ".a": |
| 192 | case ".lib": |
| 193 | case ".wasm": |
| 194 | case ".pyc": |
| 195 | case ".pyo": |
| 196 | return true |
| 197 | default: |
| 198 | break |
| 199 | } |
| 200 | |
| 201 | const stat = await file.stat() |
| 202 | const fileSize = stat.size |
| 203 | if (fileSize === 0) return false |
| 204 | |
| 205 | const bufferSize = Math.min(4096, fileSize) |
| 206 | const buffer = await file.arrayBuffer() |
| 207 | if (buffer.byteLength === 0) return false |
| 208 | const bytes = new Uint8Array(buffer.slice(0, bufferSize)) |
| 209 | |
| 210 | let nonPrintableCount = 0 |
| 211 | for (let i = 0; i < bytes.length; i++) { |
| 212 | if (bytes[i] === 0) return true |
| 213 | if (bytes[i] < 9 || (bytes[i] > 13 && bytes[i] < 32)) { |
| 214 | nonPrintableCount++ |
| 215 | } |
| 216 | } |
| 217 | // If >30% non-printable characters, consider it binary |
| 218 | return nonPrintableCount / bytes.length > 0.3 |
| 219 | } |