├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── automerge.yml │ └── ci.yml ├── .gitignore ├── .prettierrc ├── LICENSE.txt ├── README.md ├── jestconfig.json ├── package-lock.json ├── package.json ├── src └── index.ts ├── test ├── fixtures │ ├── dir │ │ └── .gitkeep │ ├── emoji.txt │ ├── encodings │ │ ├── big5.txt │ │ ├── big5_B.txt │ │ ├── bom_utf-16.txt │ │ ├── bom_utf-16le.txt │ │ ├── bom_utf-32.txt │ │ ├── bom_utf-32le.txt │ │ ├── bom_utf-8.txt │ │ ├── test-gb.txt │ │ ├── test-gb2.txt │ │ ├── test-kr.txt │ │ ├── test-latin.txt │ │ ├── test-shishi.txt │ │ ├── test-utf16be.txt │ │ ├── utf8cn.txt │ │ └── utf_8.txt │ ├── grep │ ├── no.lua │ ├── null_file.gif │ ├── pdf.pdf │ ├── perl_script │ ├── protobuf.proto │ ├── protobuf.proto.bin │ ├── protobuf.proto.txt │ ├── russian_file.rst │ ├── test.pdf │ └── trunks.gif └── index.test.ts ├── tsconfig.json └── tslint.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gjtorikian 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: PR auto-{approve,merge} 2 | 3 | on: 4 | pull_request_target: 5 | 6 | permissions: 7 | pull-requests: write 8 | contents: write 9 | 10 | jobs: 11 | dependabot: 12 | name: Dependabot 13 | runs-on: ubuntu-latest 14 | 15 | if: ${{ github.actor == 'dependabot[bot]' }} 16 | steps: 17 | - name: Fetch Dependabot metadata 18 | id: dependabot-metadata 19 | uses: dependabot/fetch-metadata@v2 20 | with: 21 | github-token: "${{ secrets.GITHUB_TOKEN }}" 22 | 23 | - name: Approve Dependabot PR 24 | if: ${{steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major'}} 25 | run: gh pr review --approve "$PR_URL" 26 | env: 27 | PR_URL: ${{github.event.pull_request.html_url}} 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Merge Dependabot PR 31 | run: gh pr merge --auto --squash "$PR_URL" 32 | env: 33 | PR_URL: ${{ github.event.pull_request.html_url }} 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | node-version: ['16', '18'] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Node ${{ matrix.ruby-version }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: Install dependencies 22 | run: npm install 23 | - name: Build Typescript 24 | run: npm run build 25 | - name: Run tests 26 | run: npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules 3 | npm-debug.log 4 | /lib 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all", 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Garen J. Torikian 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isBinaryFile 2 | 3 | Detects if a file is binary in Node.js using ✨promises✨. Similar to [Perl's `-B` switch](http://stackoverflow.com/questions/899206/how-does-perl-know-a-file-is-binary), in that: 4 | - it reads the first few thousand bytes of a file 5 | - checks for a `null` byte; if it's found, it's binary 6 | - flags non-ASCII characters. After a certain number of "weird" characters, the file is flagged as binary 7 | 8 | Much of the logic is pretty much ported from [ag](https://github.com/ggreer/the_silver_searcher). 9 | 10 | Note: if the file doesn't exist or is a directory, an error is thrown. 11 | 12 | ## Installation 13 | 14 | ``` 15 | npm install isbinaryfile 16 | ``` 17 | 18 | ## Usage 19 | 20 | Returns `Promise` (or just `boolean` for `*Sync`). `true` if the file is binary, `false` otherwise. 21 | 22 | ### isBinaryFile(filepath) 23 | 24 | * `filepath` - a `string` indicating the path to the file. 25 | 26 | ### isBinaryFile(bytes[, size]) 27 | 28 | * `bytes` - a `Buffer` of the file's contents. 29 | * `size` - an optional `number` indicating the file size. 30 | 31 | ### isBinaryFileSync(filepath) 32 | 33 | * `filepath` - a `string` indicating the path to the file. 34 | 35 | 36 | ### isBinaryFileSync(bytes[, size]) 37 | 38 | * `bytes` - a `Buffer` of the file's contents. 39 | * `size` - an optional `number` indicating the file size. 40 | 41 | ### Examples 42 | 43 | Here's an arbitrary usage: 44 | 45 | ```javascript 46 | const isBinaryFile = require("isbinaryfile").isBinaryFile; 47 | const fs = require("fs"); 48 | 49 | const filename = "fixtures/pdf.pdf"; 50 | const data = fs.readFileSync(filename); 51 | const stat = fs.lstatSync(filename); 52 | 53 | isBinaryFile(data, stat.size).then((result) => { 54 | if (result) { 55 | console.log("It is binary!") 56 | } 57 | else { 58 | console.log("No it is not.") 59 | } 60 | }); 61 | 62 | const isBinaryFileSync = require("isbinaryfile").isBinaryFileSync; 63 | const bytes = fs.readFileSync(filename); 64 | const size = fs.lstatSync(filename).size; 65 | console.log(isBinaryFileSync(bytes, size)); // true or false 66 | ``` 67 | 68 | ## Testing 69 | 70 | Run `npm install`, then run `npm test`. 71 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"] 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isbinaryfile", 3 | "description": "Detects if a file is binary in Node.js. Similar to Perl's -B.", 4 | "version": "5.0.4", 5 | "keywords": [ 6 | "text", 7 | "binary", 8 | "encoding", 9 | "istext", 10 | "is text", 11 | "isbinary", 12 | "is binary", 13 | "is text or binary", 14 | "is text or binary file", 15 | "isbinaryfile", 16 | "is binary file", 17 | "istextfile", 18 | "is text file" 19 | ], 20 | "devDependencies": { 21 | "@types/jest": "^23.3.14", 22 | "@types/node": "^10.17.60", 23 | "jest": "^29.7.0", 24 | "prettier": "^1.19.1", 25 | "release-it": "^17.0.4", 26 | "ts-jest": "^29.1.4", 27 | "tslint": "^5.20.1", 28 | "tslint-config-prettier": "^1.18.0", 29 | "typescript": "^4.9.4" 30 | }, 31 | "engines": { 32 | "node": ">= 18.0.0" 33 | }, 34 | "files": [ 35 | "lib/**/*" 36 | ], 37 | "license": "MIT", 38 | "main": "lib/index.js", 39 | "types": "lib/index.d.ts", 40 | "maintainers": [ 41 | { 42 | "name": "Garen J. Torikian", 43 | "email": "gjtorikian@gmail.com" 44 | } 45 | ], 46 | "funding": "https://github.com/sponsors/gjtorikian/", 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/gjtorikian/isBinaryFile" 50 | }, 51 | "scripts": { 52 | "build": "tsc", 53 | "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\" && tslint --fix -c tslint.json 'src/**/*.ts'", 54 | "lint": "tslint -p tsconfig.json", 55 | "prepare": "npm run build", 56 | "release": "release-it", 57 | "prepublishOnly": "npm test && npm run lint", 58 | "preversion": "npm run lint", 59 | "version": "npm run format && git add -A src", 60 | "postversion": "git push && git push --tags", 61 | "test": "jest --config jestconfig.json", 62 | "watch": "tsc -w" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { promisify } from 'util'; 3 | 4 | const statAsync = promisify(fs.stat); 5 | const openAsync = promisify(fs.open); 6 | const closeAsync = promisify(fs.close); 7 | 8 | const MAX_BYTES: number = 512; 9 | 10 | // A very basic non-exception raising reader. Read bytes and 11 | // at the end use hasError() to check whether this worked. 12 | class Reader { 13 | public fileBuffer: Buffer; 14 | public size: number; 15 | public offset: number; 16 | public error: boolean; 17 | 18 | constructor(fileBuffer: Buffer, size: number) { 19 | this.fileBuffer = fileBuffer; 20 | this.size = size; 21 | this.offset = 0; 22 | this.error = false; 23 | } 24 | 25 | public hasError(): boolean { 26 | return this.error; 27 | } 28 | 29 | public nextByte(): number { 30 | if (this.offset === this.size || this.hasError()) { 31 | this.error = true; 32 | return 0xff; 33 | } 34 | return this.fileBuffer[this.offset++]; 35 | } 36 | 37 | public next(len: number): number[] { 38 | const n = new Array(); 39 | for (let i = 0; i < len; i++) { 40 | n[i] = this.nextByte(); 41 | } 42 | return n; 43 | } 44 | } 45 | 46 | // Read a Google Protobuf var(iable)int from the buffer. 47 | function readProtoVarInt(reader: Reader): number { 48 | let idx = 0; 49 | let varInt = 0; 50 | 51 | while (!reader.hasError()) { 52 | const b = reader.nextByte(); 53 | varInt = varInt | ((b & 0x7f) << (7 * idx)); 54 | if ((b & 0x80) === 0) { 55 | break; 56 | } 57 | idx++; 58 | } 59 | 60 | return varInt; 61 | } 62 | 63 | // Attempt to taste a full Google Protobuf message. 64 | function readProtoMessage(reader: Reader): boolean { 65 | const varInt = readProtoVarInt(reader); 66 | const wireType = varInt & 0x7; 67 | 68 | switch (wireType) { 69 | case 0: 70 | readProtoVarInt(reader); 71 | return true; 72 | case 1: 73 | reader.next(8); 74 | return true; 75 | case 2: 76 | const len = readProtoVarInt(reader); 77 | reader.next(len); 78 | return true; 79 | case 5: 80 | reader.next(4); 81 | return true; 82 | } 83 | return false; 84 | } 85 | 86 | // Check whether this seems to be a valid protobuf file. 87 | function isBinaryProto(fileBuffer: Buffer, totalBytes: number): boolean { 88 | const reader = new Reader(fileBuffer, totalBytes); 89 | let numMessages = 0; 90 | 91 | while (true) { 92 | // Definitely not a valid protobuf 93 | if (!readProtoMessage(reader) && !reader.hasError()) { 94 | return false; 95 | } 96 | // Short read? 97 | if (reader.hasError()) { 98 | break; 99 | } 100 | numMessages++; 101 | } 102 | 103 | return numMessages > 0; 104 | } 105 | 106 | export async function isBinaryFile(file: string | Buffer, size?: number): Promise { 107 | if (isString(file)) { 108 | const stat = await statAsync(file); 109 | 110 | isStatFile(stat); 111 | 112 | const fileDescriptor = await openAsync(file, 'r'); 113 | 114 | const allocBuffer = Buffer.alloc(MAX_BYTES); 115 | 116 | // Read the file with no encoding for raw buffer access. 117 | // NB: something is severely wrong with promisify, had to construct my own Promise 118 | return new Promise((fulfill, reject) => { 119 | fs.read(fileDescriptor, allocBuffer, 0, MAX_BYTES, 0, (err, bytesRead, _) => { 120 | closeAsync(fileDescriptor); 121 | if (err) { 122 | reject(err); 123 | } else { 124 | fulfill(isBinaryCheck(allocBuffer, bytesRead)); 125 | } 126 | }); 127 | }); 128 | } else { 129 | if (size === undefined) { 130 | size = file.length; 131 | } 132 | return isBinaryCheck(file, size); 133 | } 134 | } 135 | 136 | export function isBinaryFileSync(file: string | Buffer, size?: number): boolean { 137 | if (isString(file)) { 138 | const stat = fs.statSync(file); 139 | 140 | isStatFile(stat); 141 | 142 | const fileDescriptor = fs.openSync(file, 'r'); 143 | 144 | const allocBuffer = Buffer.alloc(MAX_BYTES); 145 | 146 | const bytesRead = fs.readSync(fileDescriptor, allocBuffer, 0, MAX_BYTES, 0); 147 | fs.closeSync(fileDescriptor); 148 | 149 | return isBinaryCheck(allocBuffer, bytesRead); 150 | } else { 151 | if (size === undefined) { 152 | size = file.length; 153 | } 154 | return isBinaryCheck(file, size); 155 | } 156 | } 157 | 158 | function isBinaryCheck(fileBuffer: Buffer, bytesRead: number): boolean { 159 | // empty file. no clue what it is. 160 | if (bytesRead === 0) { 161 | return false; 162 | } 163 | 164 | let suspiciousBytes = 0; 165 | const totalBytes = Math.min(bytesRead, MAX_BYTES); 166 | 167 | // UTF-8 BOM 168 | if (bytesRead >= 3 && fileBuffer[0] === 0xef && fileBuffer[1] === 0xbb && fileBuffer[2] === 0xbf) { 169 | return false; 170 | } 171 | 172 | // UTF-32 BOM 173 | if ( 174 | bytesRead >= 4 && 175 | fileBuffer[0] === 0x00 && 176 | fileBuffer[1] === 0x00 && 177 | fileBuffer[2] === 0xfe && 178 | fileBuffer[3] === 0xff 179 | ) { 180 | return false; 181 | } 182 | 183 | // UTF-32 LE BOM 184 | if ( 185 | bytesRead >= 4 && 186 | fileBuffer[0] === 0xff && 187 | fileBuffer[1] === 0xfe && 188 | fileBuffer[2] === 0x00 && 189 | fileBuffer[3] === 0x00 190 | ) { 191 | return false; 192 | } 193 | 194 | // GB BOM 195 | if ( 196 | bytesRead >= 4 && 197 | fileBuffer[0] === 0x84 && 198 | fileBuffer[1] === 0x31 && 199 | fileBuffer[2] === 0x95 && 200 | fileBuffer[3] === 0x33 201 | ) { 202 | return false; 203 | } 204 | 205 | if (totalBytes >= 5 && fileBuffer.slice(0, 5).toString() === '%PDF-') { 206 | /* PDF. This is binary. */ 207 | return true; 208 | } 209 | 210 | // UTF-16 BE BOM 211 | if (bytesRead >= 2 && fileBuffer[0] === 0xfe && fileBuffer[1] === 0xff) { 212 | return false; 213 | } 214 | 215 | // UTF-16 LE BOM 216 | if (bytesRead >= 2 && fileBuffer[0] === 0xff && fileBuffer[1] === 0xfe) { 217 | return false; 218 | } 219 | 220 | for (let i = 0; i < totalBytes; i++) { 221 | if (fileBuffer[i] === 0) { 222 | // NULL byte--it's binary! 223 | return true; 224 | } else if ((fileBuffer[i] < 7 || fileBuffer[i] > 14) && (fileBuffer[i] < 32 || fileBuffer[i] > 127)) { 225 | // UTF-8 detection 226 | if (fileBuffer[i] >= 0xc0 && fileBuffer[i] <= 0xdf && i + 1 < totalBytes) { 227 | i++; 228 | if (fileBuffer[i] >= 0x80 && fileBuffer[i] <= 0xbf) { 229 | continue; 230 | } 231 | } else if (fileBuffer[i] >= 0xe0 && fileBuffer[i] <= 0xef && i + 2 < totalBytes) { 232 | i++; 233 | if (fileBuffer[i] >= 0x80 && fileBuffer[i] <= 0xbf && fileBuffer[i + 1] >= 0x80 && fileBuffer[i + 1] <= 0xbf) { 234 | i++; 235 | continue; 236 | } 237 | } else if (fileBuffer[i] >= 0xf0 && fileBuffer[i] <= 0xf7 && i + 3 < totalBytes) { 238 | i++; 239 | if ( 240 | fileBuffer[i] >= 0x80 && 241 | fileBuffer[i] <= 0xbf && 242 | fileBuffer[i + 1] >= 0x80 && 243 | fileBuffer[i + 1] <= 0xbf && 244 | fileBuffer[i + 2] >= 0x80 && 245 | fileBuffer[i + 2] <= 0xbf 246 | ) { 247 | i += 2; 248 | continue; 249 | } 250 | } 251 | 252 | suspiciousBytes++; 253 | // Read at least 32 fileBuffer before making a decision 254 | if (i >= 32 && (suspiciousBytes * 100) / totalBytes > 10) { 255 | return true; 256 | } 257 | } 258 | } 259 | 260 | if ((suspiciousBytes * 100) / totalBytes > 10) { 261 | return true; 262 | } 263 | 264 | if (suspiciousBytes > 1 && isBinaryProto(fileBuffer, totalBytes)) { 265 | return true; 266 | } 267 | 268 | return false; 269 | } 270 | 271 | function isString(x: any): x is string { 272 | return typeof x === 'string'; 273 | } 274 | 275 | function isStatFile(stat: fs.Stats): void { 276 | if (!stat.isFile()) { 277 | throw new Error(`Path provided was not a file!`); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /test/fixtures/dir/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/dir/.gitkeep -------------------------------------------------------------------------------- /test/fixtures/emoji.txt: -------------------------------------------------------------------------------- 1 | UTF-8 emoji 📦 2 | -------------------------------------------------------------------------------- /test/fixtures/encodings/big5.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/big5.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/big5_B.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/big5_B.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/bom_utf-16.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/bom_utf-16.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/bom_utf-16le.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/bom_utf-16le.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/bom_utf-32.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/bom_utf-32.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/bom_utf-32le.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/bom_utf-32le.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/bom_utf-8.txt: -------------------------------------------------------------------------------- 1 | UTF-8 chinese UTF8格式的中文,包含中文标点符号“”。看看能不能看清楚 2 | -------------------------------------------------------------------------------- /test/fixtures/encodings/test-gb.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/test-gb.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/test-gb2.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/test-gb2.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/test-kr.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/test-kr.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/test-latin.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/test-latin.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/test-shishi.txt: -------------------------------------------------------------------------------- 1 | ʩʦʵʫʿ,ʶʳʯʨʬʷ,ʾʷʵ,ʱʰʮʭʺʪʯʨʬ,ʼʹʯʨʬʴ,ʵʷʫ. 2 | ʫʦʧʨʩʺʪʫʿʫʬʮʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼʽʾʫʿ 3 | -------------------------------------------------------------------------------- /test/fixtures/encodings/test-utf16be.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/encodings/test-utf16be.txt -------------------------------------------------------------------------------- /test/fixtures/encodings/utf8cn.txt: -------------------------------------------------------------------------------- 1 | UTF-8 chinese UTF8格式的中文,包含中文标点符号“”。看看能不能看清楚 2 | -------------------------------------------------------------------------------- /test/fixtures/encodings/utf_8.txt: -------------------------------------------------------------------------------- 1 | 中文 2 | -------------------------------------------------------------------------------- /test/fixtures/grep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/grep -------------------------------------------------------------------------------- /test/fixtures/no.lua: -------------------------------------------------------------------------------- 1 | --- 2 | if table.get_length(baseObj.item_tbl) < 1 then return end 3 | -------------------------------------------------------------------------------- /test/fixtures/null_file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/null_file.gif -------------------------------------------------------------------------------- /test/fixtures/pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/pdf.pdf -------------------------------------------------------------------------------- /test/fixtures/perl_script: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | print "Hello World.\n"; -------------------------------------------------------------------------------- /test/fixtures/protobuf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Something { 4 | string entry = 3687091; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/protobuf.proto.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/protobuf.proto.bin -------------------------------------------------------------------------------- /test/fixtures/protobuf.proto.txt: -------------------------------------------------------------------------------- 1 | entry: "A very long string with no suspicous characters that is currently not detected as binary proto" 2 | -------------------------------------------------------------------------------- /test/fixtures/russian_file.rst: -------------------------------------------------------------------------------- 1 | Общие сведения о программном комплексе 2 | ************************************** 3 | test 4 | 5 | Назначение программного комплекса 6 | ================================= 7 | 8 | Функции программного комплекса 9 | ============================== 10 | 11 | Требования к минимальному составу аппаратных средств 12 | ==================================================== 13 | .. Сведения о средствах, обеспечивающих выполнение программы. 14 | 15 | Требования к минимальному составу программных средств 16 | ===================================================== 17 | .. Сведения о средствах, обеспечивающих выполнение программы. 18 | 19 | Требования к персоналу (системному программисту) 20 | ================================================ 21 | 22 | Структура программного комплекса 23 | ******************************** 24 | .. Сведения о структуре программы, ее составных частях, о связях между 25 | .. составными частями и о связях с другими программами. 26 | 27 | Настройка программного комплекса 28 | ******************************** 29 | 30 | Настройка на состав технических средств 31 | ======================================= 32 | .. Описание действий по настройке программного комплекса на условия конкретного применения. 33 | 34 | Настройка на состав программных средств 35 | ======================================= 36 | .. Описание действий по настройке программного комплекса на условия конкретного 37 | .. применения. 38 | 39 | Проверка программного комплекса 40 | ******************************* 41 | .. Описание способов проверки, позволяющих дать общее заключение о 42 | .. работоспособности программного комплекса (контрольные примеры, методы прогона, 43 | .. результаты). 44 | 45 | Сообщения системному программисту 46 | ********************************* 47 | .. Тексты сообщений, выдаваемых в ходе выполнения настройки, проверки 48 | .. программы, а также в ходе выполнения программы, описание их содержания и 49 | .. действий, которые необходимо предпринять по этим сообщениям. 50 | -------------------------------------------------------------------------------- /test/fixtures/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/test.pdf -------------------------------------------------------------------------------- /test/fixtures/trunks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/isBinaryFile/51b8c3a0b460e39cb6420ce8b7fe1e596887668a/test/fixtures/trunks.gif -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { isBinaryFile, isBinaryFileSync } from '../src/index'; 2 | 3 | import * as fs from 'fs'; 4 | import {promises as fsPromises} from 'fs'; 5 | import * as path from 'path'; 6 | 7 | const FIXTURE_PATH = "./test/fixtures"; 8 | 9 | describe('async', () => { 10 | it("does not require size if bytes are given", async () => { 11 | const bytes = fs.readFileSync(path.join(FIXTURE_PATH, "grep")); 12 | 13 | expect.assertions(1); 14 | 15 | const result = await isBinaryFile(bytes); 16 | 17 | expect(result).toBe(true); 18 | }); 19 | 20 | it("should return true on a binary program, accepting path", async () => { 21 | const file = path.join(FIXTURE_PATH, "grep"); 22 | 23 | expect.assertions(1); 24 | 25 | const result = await isBinaryFile(file); 26 | 27 | expect(result).toBe(true); 28 | }); 29 | 30 | it("should return true on a binary program, accepting bytes & size", async () => { 31 | const bytes = fs.readFileSync(path.join(FIXTURE_PATH, "grep")); 32 | const size = fs.lstatSync(path.join(FIXTURE_PATH, "grep")).size; 33 | 34 | expect.assertions(1); 35 | 36 | const result = await isBinaryFile(bytes, size); 37 | 38 | expect(result).toBe(true); 39 | }); 40 | 41 | it("should return false on a extensionless script, accepting path", async () => { 42 | const file = path.join(FIXTURE_PATH, "perl_script"); 43 | 44 | expect.assertions(1); 45 | 46 | const result = await isBinaryFile(file); 47 | 48 | expect(result).toBe(false); 49 | }); 50 | 51 | it("should return false on a extensionless script, accepting bytes & size", async () => { 52 | const bytes = fs.readFileSync(path.join(FIXTURE_PATH, "perl_script")); 53 | const size = fs.lstatSync(path.join(FIXTURE_PATH, "perl_script")).size; 54 | 55 | expect.assertions(1); 56 | 57 | const result = await isBinaryFile(bytes, size); 58 | 59 | expect(result).toBe(false); 60 | }); 61 | 62 | 63 | it("should return false on a russian text", async () => { 64 | const file = path.join(FIXTURE_PATH, "russian_file.rst"); 65 | 66 | expect.assertions(1); 67 | 68 | const result = await isBinaryFile(file); 69 | 70 | expect(result).toBe(false); 71 | }); 72 | 73 | it("should return false on a zero-byte image file", async () => { 74 | const file = path.join(FIXTURE_PATH, "null_file.gif"); 75 | 76 | expect.assertions(1); 77 | 78 | const result = await isBinaryFile(file); 79 | 80 | expect(result).toBe(false); 81 | }); 82 | 83 | it("should return true on a gif", async () => { 84 | const file = path.join(FIXTURE_PATH, "trunks.gif"); 85 | 86 | expect.assertions(1); 87 | 88 | const result = await isBinaryFile(file); 89 | 90 | expect(result).toBe(true); 91 | }); 92 | 93 | it("should return false on some UTF8 lua file", async () => { 94 | const file = path.join(FIXTURE_PATH, "no.lua"); 95 | 96 | expect.assertions(1); 97 | 98 | const result = await isBinaryFile(file); 99 | 100 | expect(result).toBe(false); 101 | }); 102 | 103 | it("should boom on a directory", async () => { 104 | const file = path.join(FIXTURE_PATH, "dir"); 105 | 106 | expect.assertions(1); 107 | 108 | await expect(isBinaryFile(file)).rejects.toThrow("Path provided was not a file!"); 109 | }); 110 | 111 | it("should boom on non-existent file", async () => { 112 | const file = path.join(FIXTURE_PATH, "blahblahblbahhhhhh"); 113 | 114 | expect.assertions(1); 115 | 116 | await expect(isBinaryFile(file)).rejects.toThrow("ENOENT: no such file or directory, stat 'test/fixtures/blahblahblbahhhhhh'"); 117 | }); 118 | 119 | it("should return true on a PDF", async () => { 120 | const file = path.join(FIXTURE_PATH, "pdf.pdf"); 121 | 122 | expect.assertions(1); 123 | 124 | const result = await isBinaryFile(file); 125 | 126 | expect(result).toBe(true); 127 | }); 128 | 129 | it("should return true on a tricky PDF that needs a header check", async () => { 130 | const file = path.join(FIXTURE_PATH, "test.pdf"); 131 | 132 | expect.assertions(1); 133 | 134 | const result = await isBinaryFile(file); 135 | 136 | expect(result).toBe(true); 137 | }); 138 | 139 | it("should return false on a protobuf.proto", async () => { 140 | const file = path.join(FIXTURE_PATH, "protobuf.proto"); 141 | 142 | expect.assertions(1); 143 | 144 | const result = await isBinaryFile(file); 145 | 146 | expect(result).toBe(false); 147 | }); 148 | 149 | it("should return false on a protobuf.proto.txt", async () => { 150 | const file = path.join(FIXTURE_PATH, "protobuf.proto.txt"); 151 | 152 | expect.assertions(1); 153 | 154 | const result = await isBinaryFile(file); 155 | 156 | expect(result).toBe(false); 157 | }); 158 | 159 | it("should return true on a protobuf.proto.bin", async () => { 160 | const file = path.join(FIXTURE_PATH, "protobuf.proto.bin"); 161 | 162 | expect.assertions(1); 163 | 164 | const result = await isBinaryFile(file); 165 | 166 | expect(result).toBe(true); 167 | }); 168 | 169 | }); 170 | 171 | describe('sync', () => { 172 | it("should require size if bytes are given", () => { 173 | const bytes = fs.readFileSync(path.join(FIXTURE_PATH, "grep")); 174 | 175 | const result = isBinaryFileSync(bytes); 176 | 177 | expect(result).toBe(true); 178 | }); 179 | 180 | it("should return true on a binary program, accepting path", () => { 181 | const file = path.join(FIXTURE_PATH, "grep"); 182 | 183 | const result = isBinaryFileSync(file); 184 | 185 | expect(result).toBe(true); 186 | }); 187 | 188 | it("should return true on a binary program, accepting bytes & size", () => { 189 | const bytes = fs.readFileSync(path.join(FIXTURE_PATH, "grep")); 190 | const size = fs.lstatSync(path.join(FIXTURE_PATH, "grep")).size; 191 | 192 | const result = isBinaryFileSync(bytes, size); 193 | 194 | expect(result).toBe(true); 195 | }); 196 | 197 | it("should return false on a extensionless script, accepting path", () => { 198 | const file = path.join(FIXTURE_PATH, "perl_script"); 199 | 200 | const result = isBinaryFileSync(file); 201 | 202 | expect(result).toBe(false); 203 | }); 204 | 205 | it("should return false on a extensionless script, accepting bytes & size", () => { 206 | const bytes = fs.readFileSync(path.join(FIXTURE_PATH, "perl_script")); 207 | const size = fs.lstatSync(path.join(FIXTURE_PATH, "perl_script")).size; 208 | 209 | const result = isBinaryFileSync(bytes, size); 210 | 211 | expect(result).toBe(false); 212 | }); 213 | 214 | 215 | it("should return false on a russian text", () => { 216 | const file = path.join(FIXTURE_PATH, "russian_file.rst"); 217 | 218 | const result = isBinaryFileSync(file); 219 | 220 | expect(result).toBe(false); 221 | }); 222 | 223 | it("should return false on a zero-byte image file", () => { 224 | const file = path.join(FIXTURE_PATH, "null_file.gif"); 225 | 226 | const result = isBinaryFileSync(file); 227 | 228 | expect(result).toBe(false); 229 | }); 230 | 231 | it("should return true on a gif", () => { 232 | const file = path.join(FIXTURE_PATH, "trunks.gif"); 233 | 234 | const result = isBinaryFileSync(file); 235 | 236 | expect(result).toBe(true); 237 | }); 238 | 239 | it("should return false on some UTF8 lua file", () => { 240 | const file = path.join(FIXTURE_PATH, "no.lua"); 241 | 242 | const result = isBinaryFileSync(file); 243 | 244 | expect(result).toBe(false); 245 | }); 246 | 247 | it("should boom on a directory", () => { 248 | const file = path.join(FIXTURE_PATH, "dir"); 249 | 250 | try { 251 | isBinaryFileSync(file); 252 | } catch (e: any) { 253 | 254 | expect(e.message).toBe("Path provided was not a file!") 255 | } 256 | }); 257 | 258 | it("should boom on non-existent file", () => { 259 | const file = path.join(FIXTURE_PATH, "blahblahblbahhhhhh"); 260 | 261 | try { 262 | isBinaryFileSync(file); 263 | } catch (e: any) { 264 | expect(e.message).toBe("ENOENT: no such file or directory, stat 'test/fixtures/blahblahblbahhhhhh'") 265 | } 266 | }); 267 | 268 | it("should return true on a PDF", () => { 269 | const file = path.join(FIXTURE_PATH, "pdf.pdf"); 270 | 271 | const result = isBinaryFileSync(file); 272 | 273 | expect(result).toBe(true); 274 | }); 275 | 276 | it("should return true on a tricky PDF that needs a header check", () => { 277 | const file = path.join(FIXTURE_PATH, "test.pdf"); 278 | 279 | const result = isBinaryFileSync(file); 280 | 281 | expect(result).toBe(true); 282 | }); 283 | 284 | it("should return false for non-UTF8 files", async () => { 285 | const encodingDir = path.join(FIXTURE_PATH, "encodings") 286 | const files = fs.readdirSync(encodingDir); 287 | 288 | files.forEach((file) => { 289 | if (!/big5/.test(file) && !/gb/.test(file) && !/kr/.test(file)){ 290 | expect(isBinaryFileSync(path.join(encodingDir, file))).toBe(false); 291 | } 292 | }); 293 | }); 294 | }); 295 | 296 | it("should return false on a UTF-8 file with emoji", () => { 297 | const file = path.join(FIXTURE_PATH, "emoji.txt"); 298 | const result = isBinaryFileSync(file); 299 | expect(result).toBe(false); 300 | }); 301 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "outDir": "./lib", 8 | "strict": true 9 | }, 10 | "include": ["src"], 11 | "exclude": ["node_modules", "**/__tests__/*"] 12 | } 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "rules": { 4 | "no-bitwise": false 5 | } 6 | } 7 | --------------------------------------------------------------------------------