upload-safety 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -7
- package/dist/archive-content.d.ts +15 -0
- package/dist/archive-content.d.ts.map +1 -0
- package/dist/archive-content.js +277 -0
- package/dist/archive-content.js.map +1 -0
- package/dist/asf-media.d.ts +22 -0
- package/dist/asf-media.d.ts.map +1 -0
- package/dist/asf-media.js +159 -0
- package/dist/asf-media.js.map +1 -0
- package/dist/check-upload.js +1 -1
- package/dist/check-upload.js.map +1 -1
- package/dist/heif-metadata.d.ts +8 -0
- package/dist/heif-metadata.d.ts.map +1 -1
- package/dist/heif-metadata.js +1 -1
- package/dist/heif-metadata.js.map +1 -1
- package/dist/media-formats.d.ts +51 -0
- package/dist/media-formats.d.ts.map +1 -0
- package/dist/media-formats.js +498 -0
- package/dist/media-formats.js.map +1 -0
- package/dist/photo-metadata.d.ts +4 -2
- package/dist/photo-metadata.d.ts.map +1 -1
- package/dist/photo-metadata.js +39 -2
- package/dist/photo-metadata.js.map +1 -1
- package/dist/rtf-content.d.ts +12 -0
- package/dist/rtf-content.d.ts.map +1 -0
- package/dist/rtf-content.js +175 -0
- package/dist/rtf-content.js.map +1 -0
- package/dist/upload-allowlist.d.ts +19 -8
- package/dist/upload-allowlist.d.ts.map +1 -1
- package/dist/upload-allowlist.js +136 -23
- package/dist/upload-allowlist.js.map +1 -1
- package/dist/video-formats.d.ts +33 -0
- package/dist/video-formats.d.ts.map +1 -0
- package/dist/video-formats.js +373 -0
- package/dist/video-formats.js.map +1 -0
- package/dist/zip-entries.d.ts +8 -0
- package/dist/zip-entries.d.ts.map +1 -1
- package/dist/zip-entries.js +29 -2
- package/dist/zip-entries.js.map +1 -1
- package/package.json +6 -3
- package/src/archive-content.ts +306 -0
- package/src/asf-media.ts +184 -0
- package/src/check-upload.ts +1 -1
- package/src/heif-metadata.ts +2 -2
- package/src/media-formats.ts +522 -0
- package/src/photo-metadata.ts +40 -2
- package/src/rtf-content.ts +178 -0
- package/src/upload-allowlist.ts +162 -27
- package/src/video-formats.ts +398 -0
- package/src/zip-entries.ts +33 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](src/index.ts)
|
|
9
9
|
[](https://upload-safety.simonhalimonov.com)
|
|
10
10
|
|
|
11
|
-
Server-side upload checks for Node that read the file, not just its name. An upload passes only when its extension, its declared MIME type and its bytes agree, and when it carries nothing that runs on its own: no macros, embedded objects, DDE fields, PDF scripts or CSV formulas. Photos, including iPhone HEIC, lose their GPS position and other metadata on the way through.
|
|
11
|
+
Server-side upload checks for Node that read the file, not just its name. An upload passes only when its extension, its declared MIME type and its bytes agree, and when it carries nothing that runs on its own: no macros, embedded objects, DDE fields, PDF scripts or CSV formulas. Photos and videos, including iPhone HEIC and MOV, lose their GPS position and other metadata on the way through. Riskier formats such as RTF and archives are opt-in, and each format's risk is spelled out.
|
|
12
12
|
|
|
13
13
|
For apps that take CVs, invoices, contracts and photos from strangers and hand them to colleagues who open them in Office or Acrobat.
|
|
14
14
|
|
|
@@ -36,6 +36,9 @@ if ( ! result.ok ) {
|
|
|
36
36
|
| `data.xlsx` that is a zip bomb | refused, `content-mismatch` |
|
|
37
37
|
| `holiday.jpg` with GPS coordinates | accepted, EXIF, XMP, IPTC and comments removed, orientation kept |
|
|
38
38
|
| `IMG_0042.heic` from an iPhone | accepted, EXIF and XMP overwritten in place, pixels unchanged |
|
|
39
|
+
| `clip.mov` from an iPhone with its location | accepted, location, title and other metadata boxes blanked in place |
|
|
40
|
+
| `letter.rtf` with an embedded OLE object or a DDE field | refused, `macros` (RTF is opt-in) |
|
|
41
|
+
| `files.zip` holding a `.docx` with macros or an `.exe` | refused, `macros` or `content-mismatch` (archives are opt-in) |
|
|
39
42
|
| `../../CON.pdf`, a plain PDF | accepted, stored as `_CON.pdf` |
|
|
40
43
|
|
|
41
44
|
Every check fails closed: a part it cannot read is a refusal, not a pass.
|
|
@@ -45,7 +48,7 @@ Every check fails closed: a part it cannot read is a refusal, not a pass.
|
|
|
45
48
|
- **Zero runtime dependencies.** Only `node:zlib` and `node:crypto`. Nothing to audit but this package.
|
|
46
49
|
- **Typed reasons.** Each refusal is one of seven string literals, so a `Record<UploadRejection, string>` of messages is checked by the compiler.
|
|
47
50
|
- **Fast enough to run inline.** Synchronous; 647 real documents up to 20 MB each took at most 65 ms on a laptop.
|
|
48
|
-
- **Tested against crafted files.**
|
|
51
|
+
- **Tested against crafted files.** 422 tests build malformed ZIPs, tars, compound files, PDFs, RTF and media files byte by byte, next to real Office, PDF, photo, audio and video files.
|
|
49
52
|
|
|
50
53
|
Full documentation, with a page per format: **[upload-safety.simonhalimonov.com](https://upload-safety.simonhalimonov.com)**.
|
|
51
54
|
|
|
@@ -139,9 +142,35 @@ app.post( '/upload', upload.single( 'file' ), ( req, res ) => {
|
|
|
139
142
|
| OpenDocument | `odt` `ods` `odp` | The `mimetype` entry; Basic and script modules; embedded OLE objects; event listeners; links to macros; encrypted manifests |
|
|
140
143
|
| Photos | `jpg` `jpeg` `png` `webp` | Structure; EXIF (GPS included), XMP, IPTC, comments and any other metadata segment or chunk are removed, the orientation is kept |
|
|
141
144
|
| HEIF photos | `heic` `heif` `avif` | Brands; EXIF and XMP items are overwritten in place, since dropping them would shift the offsets the file is built on. The orientation lives in the image properties and stays. Image sequences, such as animated AVIF, are refused |
|
|
145
|
+
| Other images | `gif` `bmp` `ico` | GIF: comments, XMP and other application extensions removed, anything after the end cut off. BMP and ICO: headers |
|
|
146
|
+
| Audio | `mp3` `aac` `wav` `flac` `ogg` `m4a` | Frame and chunk structure (every AAC frame); ID3, APE, RIFF INFO, Vorbis comment and picture blocks and iTunes tags removed. Ogg pages are checked by their CRC, their comments stay |
|
|
147
|
+
| Video | `mp4` `mov` `webm` `mkv` `avi` | Box, element or chunk structure; `udta`, `meta`, XMP and `uuid` boxes (iPhone location included), Matroska tags, attachments and title, AVI INFO, XMP and vendor chunks blanked in place, so no offset moves |
|
|
148
|
+
| Windows Media | `wma` `wmv` | ASF object structure and the stream type; title, author and tag objects blanked in place. Script commands, which make Windows Media Player open URLs, are refused as `macros`; DRM and signatures, which carry license URLs it opens, as `encrypted`. Opt-in |
|
|
142
149
|
| Text | `csv` `txt` | No binary data (UTF-8, UTF-16 with a byte order mark, or a Windows code page); in CSV no cell that starts like a formula (`=`, `+`, `-`, `@`) and calls a function or DDE. Numbers such as `-5` or `+49 (30) 1234` pass |
|
|
150
|
+
| Markdown | `md` | As text. Opt-in |
|
|
151
|
+
| RTF | `rtf` | Refused on any embedded or linked object (`\obj…`), attached template, data store, or DDE field, however it is spelled. Opt-in |
|
|
152
|
+
| Archives | `zip` `tar` `gz` `tgz` | Every file inside must be of a format in your list (not another archive) and pass its own checks; links, devices, unsafe paths and encrypted entries are refused. Opt-in |
|
|
153
|
+
| RAR, 7z | `rar` `7z` | Signature only. Opt-in |
|
|
143
154
|
|
|
144
|
-
ZIP and OLE reading is capped against zip bombs and malformed sector chains.
|
|
155
|
+
ZIP and OLE reading is capped against zip bombs and malformed sector chains. A PDF that asks for a password is refused. One that only restricts permissions opens without a password and passes, unless its object streams are encrypted, since then they cannot be checked.
|
|
156
|
+
|
|
157
|
+
## Choosing formats
|
|
158
|
+
|
|
159
|
+
Each format has a risk level in `FORMAT_RISK`. Only `low` formats are in the default list; the others are accepted only when you name them in `allowedExtensions`.
|
|
160
|
+
|
|
161
|
+
| Risk | Formats | Why |
|
|
162
|
+
| - | - | - |
|
|
163
|
+
| `low` | documents, photos and other images, audio, video, `csv`, `txt` | Read in full; nothing in them runs once the checks pass |
|
|
164
|
+
| `elevated` | `md`, `rtf`, `wma`, `wmv`, `zip`, `tar`, `gz`, `tgz` | Read in full, but RTF is a common malware carrier whose reader in Word is lenient, Windows Media files are opened by a player with a history of exploits through them, Markdown becomes HTML when rendered, and archives get unpacked, and their files keep their metadata |
|
|
165
|
+
| `unchecked` | `rar`, `7z` | Reading them needs a decompressor this package does not have, so anything can be inside. Serve them only as downloads, ideally after a virus scan |
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { ALLOWED_EXTENSIONS, checkUpload } from 'upload-safety';
|
|
169
|
+
|
|
170
|
+
checkUpload( file, { maxBytes, allowedExtensions: [ ...ALLOWED_EXTENSIONS, 'zip' ] } );
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Files inside an archive are checked against the same `allowedExtensions`, so a ZIP of PDFs needs `'pdf'` in the list too.
|
|
145
174
|
|
|
146
175
|
## API
|
|
147
176
|
|
|
@@ -151,19 +180,22 @@ ZIP and OLE reading is capped against zip bombs and malformed sector chains. Eve
|
|
|
151
180
|
| - | - |
|
|
152
181
|
| `checkUpload( { filename, mimeType, bytes }, options )` | Every check below; `{ ok: true, extension, filename, mimeType, bytes }` or `{ ok: false, reason }` |
|
|
153
182
|
| `validateUploadDescriptor( { filename, mimeType, size }, options )` | Size, extension and MIME checks before reading the body |
|
|
154
|
-
| `inspectFileContent( extension, bytes )` | `'valid' \| 'macros' \| 'encrypted' \| 'mismatch'` |
|
|
155
|
-
| `stripPhotoMetadata( extension, bytes )` |
|
|
183
|
+
| `inspectFileContent( extension, bytes, allowedExtensions? )` | `'valid' \| 'macros' \| 'encrypted' \| 'mismatch'`; files in an archive must be of a format in `allowedExtensions` |
|
|
184
|
+
| `stripPhotoMetadata( extension, bytes )` | Image, audio or video bytes without metadata, the input for other files, `null` for a broken media file |
|
|
185
|
+
| `FORMAT_RISK`, `ALLOWED_EXTENSIONS` | The risk of each format, and the default list (the `low` ones) |
|
|
156
186
|
| `sanitizeFilename( name )` | Drops path parts, unsafe characters and trailing dots, renames Windows device names such as `CON`, caps the length at 200 UTF-8 bytes |
|
|
157
187
|
| `getCanonicalMimeType( extension )` | The MIME type to store |
|
|
158
188
|
| `acceptAttribute( extensions? )` | Value for `<input type="file" accept>` |
|
|
159
189
|
| `isEncryptedPdf( buffer )` | PDF encryption check alone |
|
|
160
190
|
|
|
161
|
-
`options` takes `maxBytes` and
|
|
191
|
+
`options` takes `maxBytes` and `allowedExtensions`, which defaults to `ALLOWED_EXTENSIONS`. The reason `macros` covers all active content: VBA and Excel 4.0 macros, embedded OLE objects, DDE, event listeners and PDF scripts and attachments.
|
|
162
192
|
|
|
163
193
|
## Limits
|
|
164
194
|
|
|
165
195
|
These checks are not an antivirus scan. They refuse active content and anything they cannot read. They do not judge whether the data in an otherwise plain file is malicious, and they do not look inside a document embedded in another one, such as the workbook behind a Word chart. DDE is not looked for in legacy `.doc` files, nor in an `.xlsx` cell formula that names a DDE source without an external link part.
|
|
166
196
|
|
|
197
|
+
Media checks read the container, not the codec data. GPS that a camera records per frame in a metadata track (GoPro, DJI) stays, as do Ogg comments, and blanked video boxes keep the file at its size. Files inside archives are checked but not rewritten, so photos in a ZIP keep their metadata. `checkUpload` takes the whole file in memory; cap `maxBytes` for video accordingly.
|
|
198
|
+
|
|
167
199
|
The ZIP limits (`MAX_ZIP_UNCOMPRESSED_BYTES`, `MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES`) are set to 100 MB. If a virus scanner runs after these checks, keep them at or below its own limits: ClamAV, for one, reports content past `MaxScanSize` and `MaxFileSize` as clean.
|
|
168
200
|
|
|
169
201
|
## Before and after the checks
|
|
@@ -173,7 +205,7 @@ The library checks the bytes it is handed. The rest of the upload path is up to
|
|
|
173
205
|
- **Limit the request body before reading it.** `maxBytes` is checked after the whole file is in memory, so cap the body in your server or proxy first.
|
|
174
206
|
- **Serve stored files as downloads.** Send `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`, and use the canonical `result.mimeType`, not the one the client declared. Serving uploads from a separate origin keeps a file that slips through away from your cookies.
|
|
175
207
|
- **Limit image dimensions before resizing.** A small JPEG or PNG can declare a huge size. These checks do not decode images, so cap width and height in whatever makes thumbnails (sharp's `limitInputPixels`, ImageMagick's `policy.xml`).
|
|
176
|
-
- **Expect metadata in documents.** Only
|
|
208
|
+
- **Expect metadata in documents.** Only images, audio and video are stripped. Author names in Office and PDF files, and EXIF in images inside documents, are kept.
|
|
177
209
|
- **Run a virus scanner if you need one.** These checks refuse active content; they do not recognise malware in data.
|
|
178
210
|
|
|
179
211
|
## Development
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads ZIP, TAR and gzip archives as containers and hands every regular file in them to a
|
|
3
|
+
* caller-supplied check. Anything the reader cannot follow exactly (links, devices, unsafe names,
|
|
4
|
+
* extensions it does not model, oversized content) fails closed as 'mismatch'.
|
|
5
|
+
*/
|
|
6
|
+
export type ArchiveVerdict = 'valid' | 'macros' | 'encrypted' | 'mismatch';
|
|
7
|
+
/** Checks one file inside an archive by its name and bytes. */
|
|
8
|
+
export type EntryCheck = (name: string, bytes: Buffer) => ArchiveVerdict;
|
|
9
|
+
/** Checks every regular file of a ZIP archive; password-protected entries give 'encrypted'. */
|
|
10
|
+
export declare function inspectZipArchive(content: Buffer, checkEntry: EntryCheck): ArchiveVerdict;
|
|
11
|
+
/** Checks every regular file of a tar archive. */
|
|
12
|
+
export declare function inspectTarArchive(content: Buffer, checkEntry: EntryCheck): ArchiveVerdict;
|
|
13
|
+
/** Checks the contents of a gzip file: `tarOnly` for .tgz, else a .gz holding a tar or a single file named in its header. */
|
|
14
|
+
export declare function inspectGzip(content: Buffer, checkEntry: EntryCheck, tarOnly: boolean): ArchiveVerdict;
|
|
15
|
+
//# sourceMappingURL=archive-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-content.d.ts","sourceRoot":"","sources":["../src/archive-content.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AAE3E,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,CAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAM,cAAc,CAAC;AAwE3E,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAI,cAAc,CA0B3F;AA0ED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAI,cAAc,CAoE3F;AA0BD,6HAA6H;AAC7H,wBAAgB,WAAW,CAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAI,cAAc,CAoBvG"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads ZIP, TAR and gzip archives as containers and hands every regular file in them to a
|
|
3
|
+
* caller-supplied check. Anything the reader cannot follow exactly (links, devices, unsafe names,
|
|
4
|
+
* extensions it does not model, oversized content) fails closed as 'mismatch'.
|
|
5
|
+
*/
|
|
6
|
+
import { gunzipSync } from 'node:zlib';
|
|
7
|
+
import { hasEncryptedZipEntry, isSafeName, MAX_ZIP_ENTRIES, MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES, MAX_ZIP_UNCOMPRESSED_BYTES, readZipEntries, readZipEntryData } from './zip-entries.js';
|
|
8
|
+
const BLOCK_SIZE = 512;
|
|
9
|
+
const TAR_NAME_OFFSET = 0;
|
|
10
|
+
const TAR_NAME_LENGTH = 100;
|
|
11
|
+
const TAR_SIZE_OFFSET = 124;
|
|
12
|
+
const TAR_SIZE_LENGTH = 12;
|
|
13
|
+
const TAR_CHECKSUM_OFFSET = 148;
|
|
14
|
+
const TAR_CHECKSUM_LENGTH = 8;
|
|
15
|
+
const TAR_TYPE_OFFSET = 156;
|
|
16
|
+
const TAR_MAGIC_OFFSET = 257;
|
|
17
|
+
const TAR_PREFIX_OFFSET = 345;
|
|
18
|
+
const TAR_PREFIX_LENGTH = 155;
|
|
19
|
+
const TAR_MAGICS = [Buffer.from('ustar\x0000', 'latin1'), Buffer.from('ustar \0', 'latin1')];
|
|
20
|
+
const TAR_MAGIC_LENGTH = 8;
|
|
21
|
+
/** Longest pax header or GNU long name accepted; real paths are far shorter. */
|
|
22
|
+
const MAX_TAR_NAME_DATA_BYTES = 64 * 1024;
|
|
23
|
+
const OCTAL = /^[0-7]+$/;
|
|
24
|
+
const PAX_RECORD_LENGTH = /^[1-9][0-9]*$/;
|
|
25
|
+
const GZIP_MAGIC = [0x1f, 0x8b];
|
|
26
|
+
const GZIP_DEFLATE_METHOD = 8;
|
|
27
|
+
const GZIP_FIXED_HEADER_SIZE = 10;
|
|
28
|
+
const GZIP_FLAGS_OFFSET = 3;
|
|
29
|
+
const GZIP_FHCRC = 0x02;
|
|
30
|
+
const GZIP_FEXTRA = 0x04;
|
|
31
|
+
const GZIP_FNAME = 0x08;
|
|
32
|
+
const GZIP_FCOMMENT = 0x10;
|
|
33
|
+
const GZIP_RESERVED_FLAGS = 0xe0;
|
|
34
|
+
const APPLE_DOUBLE_MAGIC = Buffer.from([0x00, 0x05, 0x16, 0x07]);
|
|
35
|
+
const DS_STORE_MAGIC = Buffer.from('\0\0\0\x01Bud1', 'latin1');
|
|
36
|
+
const APPLE_DOUBLE_DIRECTORY = '__MACOSX/';
|
|
37
|
+
const APPLE_DOUBLE_PREFIX = '._';
|
|
38
|
+
const DS_STORE_NAME = '.DS_Store';
|
|
39
|
+
function baseName(name) {
|
|
40
|
+
return name.slice(name.search(/[^/\\]*$/));
|
|
41
|
+
}
|
|
42
|
+
/** Whether a file is the macOS metadata Finder adds to archives and its bytes really are that metadata. */
|
|
43
|
+
function isMacJunk(name, bytes) {
|
|
44
|
+
const base = baseName(name);
|
|
45
|
+
if (base === DS_STORE_NAME) {
|
|
46
|
+
return bytes.subarray(0, DS_STORE_MAGIC.length).equals(DS_STORE_MAGIC);
|
|
47
|
+
}
|
|
48
|
+
return name.startsWith(APPLE_DOUBLE_DIRECTORY) && base.startsWith(APPLE_DOUBLE_PREFIX) &&
|
|
49
|
+
bytes.subarray(0, APPLE_DOUBLE_MAGIC.length).equals(APPLE_DOUBLE_MAGIC);
|
|
50
|
+
}
|
|
51
|
+
/** Checks files one at a time, remembering whether any real file was seen and the first failure. */
|
|
52
|
+
class EntryRunner {
|
|
53
|
+
checkEntry;
|
|
54
|
+
files = 0;
|
|
55
|
+
constructor(checkEntry) {
|
|
56
|
+
this.checkEntry = checkEntry;
|
|
57
|
+
}
|
|
58
|
+
/** The verdict for one file, or null while the archive may still pass. */
|
|
59
|
+
run(name, bytes) {
|
|
60
|
+
if (isMacJunk(name, bytes)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
this.files++;
|
|
64
|
+
const verdict = this.checkEntry(name, bytes);
|
|
65
|
+
return verdict === 'valid' ? null : verdict;
|
|
66
|
+
}
|
|
67
|
+
/** The final verdict once every file has been run. */
|
|
68
|
+
finish() {
|
|
69
|
+
return this.files > 0 ? 'valid' : 'mismatch';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Checks every regular file of a ZIP archive; password-protected entries give 'encrypted'. */
|
|
73
|
+
export function inspectZipArchive(content, checkEntry) {
|
|
74
|
+
const entries = readZipEntries(content);
|
|
75
|
+
if (!entries) {
|
|
76
|
+
return hasEncryptedZipEntry(content) ? 'encrypted' : 'mismatch';
|
|
77
|
+
}
|
|
78
|
+
const runner = new EntryRunner(checkEntry);
|
|
79
|
+
let budget = MAX_ZIP_UNCOMPRESSED_BYTES;
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (entry.symlink) {
|
|
82
|
+
return 'mismatch';
|
|
83
|
+
}
|
|
84
|
+
if (entry.name.endsWith('/')) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const bytes = readZipEntryData(content, entry, Math.min(budget, MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES));
|
|
88
|
+
if (!bytes) {
|
|
89
|
+
return 'mismatch';
|
|
90
|
+
}
|
|
91
|
+
budget -= bytes.length;
|
|
92
|
+
const verdict = runner.run(entry.name, bytes);
|
|
93
|
+
if (verdict) {
|
|
94
|
+
return verdict;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return runner.finish();
|
|
98
|
+
}
|
|
99
|
+
function isZeroBlock(block) {
|
|
100
|
+
return block.every((byte) => byte === 0);
|
|
101
|
+
}
|
|
102
|
+
/** The text up to the first NUL. */
|
|
103
|
+
function cString(bytes) {
|
|
104
|
+
const end = bytes.indexOf(0);
|
|
105
|
+
return bytes.toString('utf8', 0, end < 0 ? bytes.length : end);
|
|
106
|
+
}
|
|
107
|
+
/** A number in a tar header field, or null unless it is plain octal (base-256 is not supported). */
|
|
108
|
+
function parseOctal(field) {
|
|
109
|
+
const text = field.toString('latin1').replace(/^ +/, '').replace(/[\0 ]+$/, '');
|
|
110
|
+
return OCTAL.test(text) ? parseInt(text, 8) : null;
|
|
111
|
+
}
|
|
112
|
+
function hasValidChecksum(header) {
|
|
113
|
+
const stored = parseOctal(header.subarray(TAR_CHECKSUM_OFFSET, TAR_CHECKSUM_OFFSET + TAR_CHECKSUM_LENGTH));
|
|
114
|
+
if (stored === null) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
let unsigned = 0;
|
|
118
|
+
let signed = 0;
|
|
119
|
+
for (let index = 0; index < BLOCK_SIZE; index++) {
|
|
120
|
+
const inChecksum = index >= TAR_CHECKSUM_OFFSET && index < TAR_CHECKSUM_OFFSET + TAR_CHECKSUM_LENGTH;
|
|
121
|
+
const byte = inChecksum ? 0x20 : header[index];
|
|
122
|
+
unsigned += byte;
|
|
123
|
+
signed += byte > 127 ? byte - 256 : byte;
|
|
124
|
+
}
|
|
125
|
+
return stored === unsigned || stored === signed;
|
|
126
|
+
}
|
|
127
|
+
function hasTarMagic(header) {
|
|
128
|
+
const magic = header.subarray(TAR_MAGIC_OFFSET, TAR_MAGIC_OFFSET + TAR_MAGIC_LENGTH);
|
|
129
|
+
return TAR_MAGICS.some((expected) => expected.equals(magic));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The `path` of a pax extended header (`<length> <key>=<value>\n` records), '' when it has none,
|
|
133
|
+
* or null when the header is malformed or sets something this reader does not model.
|
|
134
|
+
*/
|
|
135
|
+
function parsePaxPath(data) {
|
|
136
|
+
let path = '';
|
|
137
|
+
let position = 0;
|
|
138
|
+
while (position < data.length) {
|
|
139
|
+
const space = data.indexOf(0x20, position);
|
|
140
|
+
const lengthText = space < 0 ? '' : data.toString('latin1', position, space);
|
|
141
|
+
if (!PAX_RECORD_LENGTH.test(lengthText)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const end = position + Number(lengthText);
|
|
145
|
+
if (end > data.length || end <= space + 1 || data[end - 1] !== 0x0a) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const record = data.toString('utf8', space + 1, end - 1);
|
|
149
|
+
const equals = record.indexOf('=');
|
|
150
|
+
if (equals < 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const key = record.slice(0, equals);
|
|
154
|
+
// A pax size would change where the next header starts.
|
|
155
|
+
if (key === 'size') {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (key === 'path') {
|
|
159
|
+
path = record.slice(equals + 1);
|
|
160
|
+
}
|
|
161
|
+
position = end;
|
|
162
|
+
}
|
|
163
|
+
return path;
|
|
164
|
+
}
|
|
165
|
+
/** Checks every regular file of a tar archive. */
|
|
166
|
+
export function inspectTarArchive(content, checkEntry) {
|
|
167
|
+
const runner = new EntryRunner(checkEntry);
|
|
168
|
+
let position = 0;
|
|
169
|
+
let headers = 0;
|
|
170
|
+
let totalBytes = 0;
|
|
171
|
+
let nextName = null;
|
|
172
|
+
for (;;) {
|
|
173
|
+
if (position === content.length) {
|
|
174
|
+
return nextName === null ? runner.finish() : 'mismatch';
|
|
175
|
+
}
|
|
176
|
+
const header = content.subarray(position, position + BLOCK_SIZE);
|
|
177
|
+
if (header.length < BLOCK_SIZE) {
|
|
178
|
+
return 'mismatch';
|
|
179
|
+
}
|
|
180
|
+
if (isZeroBlock(header)) {
|
|
181
|
+
// Nothing but padding may follow the end marker.
|
|
182
|
+
return nextName === null && content.subarray(position).every((byte) => byte === 0) ? runner.finish() : 'mismatch';
|
|
183
|
+
}
|
|
184
|
+
if (++headers > MAX_ZIP_ENTRIES || !hasTarMagic(header) || !hasValidChecksum(header)) {
|
|
185
|
+
return 'mismatch';
|
|
186
|
+
}
|
|
187
|
+
const size = parseOctal(header.subarray(TAR_SIZE_OFFSET, TAR_SIZE_OFFSET + TAR_SIZE_LENGTH));
|
|
188
|
+
const type = String.fromCharCode(header[TAR_TYPE_OFFSET]);
|
|
189
|
+
const dataStart = position + BLOCK_SIZE;
|
|
190
|
+
const next = dataStart + Math.ceil((size ?? 0) / BLOCK_SIZE) * BLOCK_SIZE;
|
|
191
|
+
if (size === null || size > MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES || next > content.length) {
|
|
192
|
+
return 'mismatch';
|
|
193
|
+
}
|
|
194
|
+
const data = content.subarray(dataStart, dataStart + size);
|
|
195
|
+
position = next;
|
|
196
|
+
if (type === 'g') {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (type === 'x' || type === 'L') {
|
|
200
|
+
const name = type === 'x' ? parsePaxPath(data) : cString(data);
|
|
201
|
+
if (name === null || size > MAX_TAR_NAME_DATA_BYTES) {
|
|
202
|
+
return 'mismatch';
|
|
203
|
+
}
|
|
204
|
+
// An empty pax path leaves a pending long name in force.
|
|
205
|
+
nextName = name || nextName;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const prefix = cString(header.subarray(TAR_PREFIX_OFFSET, TAR_PREFIX_OFFSET + TAR_PREFIX_LENGTH));
|
|
209
|
+
const ownName = cString(header.subarray(TAR_NAME_OFFSET, TAR_NAME_OFFSET + TAR_NAME_LENGTH));
|
|
210
|
+
const name = nextName ?? (prefix ? `${prefix}/${ownName}` : ownName);
|
|
211
|
+
nextName = null;
|
|
212
|
+
const isDirectory = type === '5' || ((type === '0' || type === '\0') && name.endsWith('/') && size === 0);
|
|
213
|
+
if (!isSafeName(name)) {
|
|
214
|
+
return 'mismatch';
|
|
215
|
+
}
|
|
216
|
+
if (isDirectory) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (type !== '0' && type !== '\0') {
|
|
220
|
+
return 'mismatch';
|
|
221
|
+
}
|
|
222
|
+
totalBytes += size;
|
|
223
|
+
if (totalBytes > MAX_ZIP_UNCOMPRESSED_BYTES) {
|
|
224
|
+
return 'mismatch';
|
|
225
|
+
}
|
|
226
|
+
const verdict = runner.run(name, data);
|
|
227
|
+
if (verdict) {
|
|
228
|
+
return verdict;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** The FNAME of a gzip header, '' when it has none, or null when the header is malformed. */
|
|
233
|
+
function readGzipName(content) {
|
|
234
|
+
if (content.length < GZIP_FIXED_HEADER_SIZE || content[0] !== GZIP_MAGIC[0] || content[1] !== GZIP_MAGIC[1] ||
|
|
235
|
+
content[2] !== GZIP_DEFLATE_METHOD) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const flags = content[GZIP_FLAGS_OFFSET];
|
|
239
|
+
if (flags & GZIP_RESERVED_FLAGS) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
let position = GZIP_FIXED_HEADER_SIZE;
|
|
243
|
+
if (flags & GZIP_FEXTRA) {
|
|
244
|
+
if (position + 2 > content.length) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
position += 2 + content.readUInt16LE(position);
|
|
248
|
+
}
|
|
249
|
+
if (!(flags & GZIP_FNAME)) {
|
|
250
|
+
return '';
|
|
251
|
+
}
|
|
252
|
+
const end = content.indexOf(0, position);
|
|
253
|
+
return position <= end ? content.toString('latin1', position, end) : null;
|
|
254
|
+
}
|
|
255
|
+
/** Checks the contents of a gzip file: `tarOnly` for .tgz, else a .gz holding a tar or a single file named in its header. */
|
|
256
|
+
export function inspectGzip(content, checkEntry, tarOnly) {
|
|
257
|
+
const name = readGzipName(content);
|
|
258
|
+
if (name === null) {
|
|
259
|
+
return 'mismatch';
|
|
260
|
+
}
|
|
261
|
+
let data;
|
|
262
|
+
try {
|
|
263
|
+
data = gunzipSync(content, { maxOutputLength: MAX_ZIP_UNCOMPRESSED_BYTES });
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return 'mismatch';
|
|
267
|
+
}
|
|
268
|
+
if (data.length >= BLOCK_SIZE && hasTarMagic(data.subarray(0, BLOCK_SIZE))) {
|
|
269
|
+
return inspectTarArchive(data, checkEntry);
|
|
270
|
+
}
|
|
271
|
+
const file = baseName(name);
|
|
272
|
+
if (tarOnly || !file || !isSafeName(file)) {
|
|
273
|
+
return 'mismatch';
|
|
274
|
+
}
|
|
275
|
+
return checkEntry(file, data);
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=archive-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-content.js","sourceRoot":"","sources":["../src/archive-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks the structure of WMA and WMV files (Microsoft ASF) and removes their metadata (title,
|
|
3
|
+
* author, WM/ tags, cover pictures) without moving a byte. The metadata objects are overwritten
|
|
4
|
+
* in place with Padding Objects of the same size and a zeroed payload.
|
|
5
|
+
*
|
|
6
|
+
* Refused: a Script Command Object makes Windows Media Player open URLs, and the DRM objects
|
|
7
|
+
* carry license acquisition URLs that it opens too (Trojan.Wimad); DRM content cannot be checked
|
|
8
|
+
* either.
|
|
9
|
+
*
|
|
10
|
+
* Limitation: markers, the file creation date and the codec data are kept.
|
|
11
|
+
*/
|
|
12
|
+
export interface AsfInspection {
|
|
13
|
+
verdict: 'valid' | 'macros' | 'encrypted' | 'mismatch';
|
|
14
|
+
/** The cleaned file when the verdict is `valid` (the input itself when nothing changed), else null. */
|
|
15
|
+
bytes: Buffer | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Checks an ASF file as a WMA (`video` false) or WMV (`video` true) and blanks its metadata.
|
|
19
|
+
* Never throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function inspectAsf(content: Buffer, video: boolean): AsfInspection;
|
|
22
|
+
//# sourceMappingURL=asf-media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asf-media.d.ts","sourceRoot":"","sources":["../src/asf-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA+DH,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;IACvD,uGAAuG;IACvG,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AA8BD;;;GAGG;AACH,wBAAgB,UAAU,CAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAI,aAAa,CAwE3E"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks the structure of WMA and WMV files (Microsoft ASF) and removes their metadata (title,
|
|
3
|
+
* author, WM/ tags, cover pictures) without moving a byte. The metadata objects are overwritten
|
|
4
|
+
* in place with Padding Objects of the same size and a zeroed payload.
|
|
5
|
+
*
|
|
6
|
+
* Refused: a Script Command Object makes Windows Media Player open URLs, and the DRM objects
|
|
7
|
+
* carry license acquisition URLs that it opens too (Trojan.Wimad); DRM content cannot be checked
|
|
8
|
+
* either.
|
|
9
|
+
*
|
|
10
|
+
* Limitation: markers, the file creation date and the codec data are kept.
|
|
11
|
+
*/
|
|
12
|
+
/** Bounds far above real files that keep a crafted one cheap. */
|
|
13
|
+
const MAX_TOP_LEVEL_OBJECTS = 1000;
|
|
14
|
+
const MAX_HEADER_OBJECTS = 10_000;
|
|
15
|
+
const GUID_LENGTH = 16;
|
|
16
|
+
const OBJECT_HEADER = GUID_LENGTH + 8;
|
|
17
|
+
/** Header Object: object header, u32 number of objects, two reserved bytes. */
|
|
18
|
+
const HEADER_OBJECT_PREFIX = OBJECT_HEADER + 6;
|
|
19
|
+
/** Header Extension payload: reserved GUID, u16 reserved, u32 data size. */
|
|
20
|
+
const EXTENSION_PREFIX = GUID_LENGTH + 2 + 4;
|
|
21
|
+
/** Converts the textual GUID of the ASF specification to its on-disk byte order (first three groups little-endian). */
|
|
22
|
+
function guid(text) {
|
|
23
|
+
const hex = text.replaceAll('-', '');
|
|
24
|
+
const bytes = Buffer.from(hex, 'hex');
|
|
25
|
+
bytes.subarray(0, 4).reverse();
|
|
26
|
+
bytes.subarray(4, 6).reverse();
|
|
27
|
+
bytes.subarray(6, 8).reverse();
|
|
28
|
+
return bytes;
|
|
29
|
+
}
|
|
30
|
+
const HEADER_OBJECT = guid('75B22630-668E-11CF-A6D9-00AA0062CE6C');
|
|
31
|
+
const DATA_OBJECT = guid('75B22636-668E-11CF-A6D9-00AA0062CE6C');
|
|
32
|
+
const FILE_PROPERTIES_OBJECT = guid('8CABDCA1-A947-11CF-8EE4-00C00C205365');
|
|
33
|
+
const STREAM_PROPERTIES_OBJECT = guid('B7DC0791-A9B7-11CF-8EE6-00C00C205365');
|
|
34
|
+
const HEADER_EXTENSION_OBJECT = guid('5FBF03B5-A92E-11CF-8EE3-00C00C205365');
|
|
35
|
+
const CONTENT_DESCRIPTION_OBJECT = guid('75B22633-668E-11CF-A6D9-00AA0062CE6C');
|
|
36
|
+
const EXTENDED_CONTENT_DESCRIPTION_OBJECT = guid('D2D0A440-E307-11D2-97F0-00A0C95EA850');
|
|
37
|
+
const METADATA_OBJECT = guid('C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA');
|
|
38
|
+
const METADATA_LIBRARY_OBJECT = guid('44231C94-9498-49D1-A141-1D134E457054');
|
|
39
|
+
const PADDING_OBJECT = guid('1806D474-CADF-4509-A4BA-9AABCB96AAE8');
|
|
40
|
+
const SCRIPT_COMMAND_OBJECT = guid('1EFB1A30-0B62-11D0-A39B-00A0C90348F6');
|
|
41
|
+
const AUDIO_MEDIA = guid('F8699E40-5B4D-11CF-A8FD-00805F5C442B');
|
|
42
|
+
const VIDEO_MEDIA = guid('BC19EFC0-5B4D-11CF-A8FD-00805F5C442B');
|
|
43
|
+
const SIMPLE_INDEX_OBJECT = guid('33000890-E5B1-11CF-89F4-00A0C90349CB');
|
|
44
|
+
const INDEX_OBJECT = guid('D6E229D3-35DA-11D1-9034-00A0C90349BE');
|
|
45
|
+
const MEDIA_OBJECT_INDEX_OBJECT = guid('FEB103F8-12AD-4C64-840F-2A1D2F7AD48C');
|
|
46
|
+
const TIMECODE_INDEX_OBJECT = guid('3CB73FD0-0C4A-4803-953D-EDF7B6228F0C');
|
|
47
|
+
/** The only top-level objects after the Data Object. */
|
|
48
|
+
const INDEX_OBJECTS = [SIMPLE_INDEX_OBJECT, INDEX_OBJECT, MEDIA_OBJECT_INDEX_OBJECT, TIMECODE_INDEX_OBJECT];
|
|
49
|
+
const CONTENT_ENCRYPTION_OBJECT = guid('2211B3FB-BD23-11D2-B4B7-00A0C955FC6E');
|
|
50
|
+
const EXTENDED_CONTENT_ENCRYPTION_OBJECT = guid('298AE614-2622-4C17-B935-DAE07EE9289C');
|
|
51
|
+
const DIGITAL_SIGNATURE_OBJECT = guid('2211B3FC-BD23-11D2-B4B7-00A0C955FC6E');
|
|
52
|
+
const ADVANCED_CONTENT_ENCRYPTION_OBJECT = guid('43058533-6981-49E6-9B74-AD12CB86D58C');
|
|
53
|
+
const ENCRYPTION_OBJECTS = [
|
|
54
|
+
CONTENT_ENCRYPTION_OBJECT,
|
|
55
|
+
EXTENDED_CONTENT_ENCRYPTION_OBJECT,
|
|
56
|
+
DIGITAL_SIGNATURE_OBJECT,
|
|
57
|
+
ADVANCED_CONTENT_ENCRYPTION_OBJECT,
|
|
58
|
+
];
|
|
59
|
+
const METADATA_OBJECTS = [
|
|
60
|
+
CONTENT_DESCRIPTION_OBJECT,
|
|
61
|
+
EXTENDED_CONTENT_DESCRIPTION_OBJECT,
|
|
62
|
+
METADATA_OBJECT,
|
|
63
|
+
METADATA_LIBRARY_OBJECT,
|
|
64
|
+
];
|
|
65
|
+
const MISMATCH = { verdict: 'mismatch', bytes: null };
|
|
66
|
+
const hasGuid = (bytes, offset, expected) => bytes.compare(expected, 0, GUID_LENGTH, offset, offset + GUID_LENGTH) === 0;
|
|
67
|
+
const isOneOf = (bytes, offset, guids) => guids.some((expected) => hasGuid(bytes, offset, expected));
|
|
68
|
+
/** The objects that follow each other exactly over `[start, end)`, or null when one does not read or there are more than `max`. */
|
|
69
|
+
function readObjects(bytes, start, end, max) {
|
|
70
|
+
const objects = [];
|
|
71
|
+
for (let offset = start; offset < end;) {
|
|
72
|
+
if (objects.length === max || end - offset < OBJECT_HEADER) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const size = bytes.readBigUInt64LE(offset + GUID_LENGTH);
|
|
76
|
+
if (size < BigInt(OBJECT_HEADER) || size > BigInt(end - offset)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
objects.push({ start: offset, end: offset + Number(size) });
|
|
80
|
+
offset += Number(size);
|
|
81
|
+
}
|
|
82
|
+
return objects;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Checks an ASF file as a WMA (`video` false) or WMV (`video` true) and blanks its metadata.
|
|
86
|
+
* Never throws.
|
|
87
|
+
*/
|
|
88
|
+
export function inspectAsf(content, video) {
|
|
89
|
+
const top = readObjects(content, 0, content.length, MAX_TOP_LEVEL_OBJECTS);
|
|
90
|
+
if (!top || top.length < 2 || !hasGuid(content, top[0].start, HEADER_OBJECT)) {
|
|
91
|
+
return MISMATCH;
|
|
92
|
+
}
|
|
93
|
+
const [header, ...rest] = top;
|
|
94
|
+
if (header.end - header.start < HEADER_OBJECT_PREFIX || content.readUInt16BE(header.start + OBJECT_HEADER + 4) !== 0x0102) {
|
|
95
|
+
return MISMATCH;
|
|
96
|
+
}
|
|
97
|
+
if (!hasGuid(content, rest[0].start, DATA_OBJECT) || !rest.slice(1).every((object) => isOneOf(content, object.start, INDEX_OBJECTS))) {
|
|
98
|
+
return MISMATCH;
|
|
99
|
+
}
|
|
100
|
+
const children = readObjects(content, header.start + HEADER_OBJECT_PREFIX, header.end, MAX_HEADER_OBJECTS);
|
|
101
|
+
if (!children) {
|
|
102
|
+
return MISMATCH;
|
|
103
|
+
}
|
|
104
|
+
const budget = { left: MAX_HEADER_OBJECTS - children.length };
|
|
105
|
+
const all = [];
|
|
106
|
+
for (const child of children) {
|
|
107
|
+
all.push(child);
|
|
108
|
+
if (hasGuid(content, child.start, HEADER_EXTENSION_OBJECT)) {
|
|
109
|
+
const prefixEnd = child.start + OBJECT_HEADER + EXTENSION_PREFIX;
|
|
110
|
+
if (prefixEnd > child.end || content.readUInt32LE(prefixEnd - 4) !== child.end - prefixEnd) {
|
|
111
|
+
return MISMATCH;
|
|
112
|
+
}
|
|
113
|
+
const inner = readObjects(content, prefixEnd, child.end, Math.max(budget.left, 0));
|
|
114
|
+
if (!inner) {
|
|
115
|
+
return MISMATCH;
|
|
116
|
+
}
|
|
117
|
+
budget.left -= inner.length;
|
|
118
|
+
all.push(...inner);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let hasFileProperties = false;
|
|
122
|
+
let hasAudio = false;
|
|
123
|
+
let hasVideo = false;
|
|
124
|
+
for (const object of all) {
|
|
125
|
+
if (isOneOf(content, object.start, ENCRYPTION_OBJECTS)) {
|
|
126
|
+
return { verdict: 'encrypted', bytes: null };
|
|
127
|
+
}
|
|
128
|
+
if (hasGuid(content, object.start, SCRIPT_COMMAND_OBJECT)) {
|
|
129
|
+
return { verdict: 'macros', bytes: null };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const object of all) {
|
|
133
|
+
if (hasGuid(content, object.start, FILE_PROPERTIES_OBJECT)) {
|
|
134
|
+
hasFileProperties = true;
|
|
135
|
+
}
|
|
136
|
+
else if (hasGuid(content, object.start, STREAM_PROPERTIES_OBJECT)) {
|
|
137
|
+
if (object.end - object.start < OBJECT_HEADER + GUID_LENGTH) {
|
|
138
|
+
return MISMATCH;
|
|
139
|
+
}
|
|
140
|
+
const type = object.start + OBJECT_HEADER;
|
|
141
|
+
hasAudio ||= hasGuid(content, type, AUDIO_MEDIA);
|
|
142
|
+
hasVideo ||= hasGuid(content, type, VIDEO_MEDIA);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!hasFileProperties || !(video ? hasVideo : hasAudio && !hasVideo)) {
|
|
146
|
+
return MISMATCH;
|
|
147
|
+
}
|
|
148
|
+
const metadata = all.filter((object) => isOneOf(content, object.start, METADATA_OBJECTS));
|
|
149
|
+
if (metadata.length === 0) {
|
|
150
|
+
return { verdict: 'valid', bytes: content };
|
|
151
|
+
}
|
|
152
|
+
const cleaned = Buffer.from(content);
|
|
153
|
+
for (const object of metadata) {
|
|
154
|
+
PADDING_OBJECT.copy(cleaned, object.start);
|
|
155
|
+
cleaned.fill(0, object.start + OBJECT_HEADER, object.end);
|
|
156
|
+
}
|
|
157
|
+
return { verdict: 'valid', bytes: cleaned };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=asf-media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asf-media.js","sourceRoot":"","sources":["../src/asf-media.ts"],"names":[],"mappings":"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"}
|
package/dist/check-upload.js
CHANGED
|
@@ -7,7 +7,7 @@ export function checkUpload({ filename, mimeType, bytes }, options) {
|
|
|
7
7
|
return descriptor;
|
|
8
8
|
}
|
|
9
9
|
const { extension, sanitizedFilename } = descriptor;
|
|
10
|
-
const verdict = inspectFileContent(extension, bytes);
|
|
10
|
+
const verdict = inspectFileContent(extension, bytes, options.allowedExtensions);
|
|
11
11
|
if (verdict === 'macros' || verdict === 'encrypted') {
|
|
12
12
|
return { ok: false, reason: verdict };
|
|
13
13
|
}
|
package/dist/check-upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-upload.js","sourceRoot":"","sources":["../src/check-upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,GAIxB,MAAM,uBAAuB,CAAC;AAuB/B,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAe,EAAE,OAAsB;IAC9F,MAAM,UAAU,GAAG,wBAAwB,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,OAAO,CAAE,CAAC;IACvG,IAAK,CAAE,UAAU,CAAC,EAAE,EAAG,CAAC;QACvB,OAAO,UAAU,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,UAAU,CAAC;IACpD,MAAM,OAAO,GAAG,kBAAkB,CAAE,SAAS,EAAE,KAAK,CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"check-upload.js","sourceRoot":"","sources":["../src/check-upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,GAIxB,MAAM,uBAAuB,CAAC;AAuB/B,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAe,EAAE,OAAsB;IAC9F,MAAM,UAAU,GAAG,wBAAwB,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,OAAO,CAAE,CAAC;IACvG,IAAK,CAAE,UAAU,CAAC,EAAE,EAAG,CAAC;QACvB,OAAO,UAAU,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,UAAU,CAAC;IACpD,MAAM,OAAO,GAAG,kBAAkB,CAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,iBAAiB,CAAE,CAAC;IAClF,IAAK,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,WAAW,EAAG,CAAC;QACvD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACvC,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAE,SAAS,EAAE,KAAK,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACnF,IAAK,CAAE,MAAM,EAAG,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAClD,CAAC;IACD,OAAO;QACN,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,EAAE,oBAAoB,CAAE,SAAS,CAAE,EAAE,KAAK,EAAE,MAAM;KAC5G,CAAC;AACH,CAAC"}
|