|
1 | 1 | const { StreamingUSTarParser, USTarParser, str, assertUsTar, roundNextSector, isTarSector } = require("./index.js"); |
2 | 2 | const { join } = require("path"); |
| 3 | +const { createHash } = require("crypto"); |
3 | 4 | const { assert } = require("chai"); |
4 | 5 | const { readFileSync, writeFileSync, existsSync } = require("fs"); |
5 | 6 |
|
@@ -69,6 +70,83 @@ describe('fixtures', () => { |
69 | 70 | t("random.tar"); |
70 | 71 | }); |
71 | 72 |
|
| 73 | +describe('preserves last byte of file content', () => { |
| 74 | + // Build a minimal USTAR archive containing a single file. Field offsets and |
| 75 | + // the space-then-sum checksum are per the USTAR spec (https://wiki.osdev.org/USTAR). |
| 76 | + function buildUstarArchive(name, content) { |
| 77 | + const HEADER_SIZE = 512; |
| 78 | + const header = Buffer.alloc(HEADER_SIZE); |
| 79 | + Buffer.from(name).copy(header, 0); |
| 80 | + Buffer.from("0000644\0").copy(header, 100); |
| 81 | + Buffer.from("0000000\0").copy(header, 108); |
| 82 | + Buffer.from("0000000\0").copy(header, 116); |
| 83 | + Buffer.from(content.length.toString(8).padStart(11, "0") + "\0").copy(header, 124); |
| 84 | + Buffer.from("00000000000\0").copy(header, 136); |
| 85 | + header[156] = 0x30; |
| 86 | + Buffer.from("ustar\0").copy(header, 257); |
| 87 | + Buffer.from("00").copy(header, 263); |
| 88 | + |
| 89 | + header.fill(0x20, 148, 148 + 8); |
| 90 | + let sum = 0; |
| 91 | + for (let i = 0; i < HEADER_SIZE; i++) sum += header[i]; |
| 92 | + Buffer.from(sum.toString(8).padStart(6, "0") + "\0 ").copy(header, 148); |
| 93 | + |
| 94 | + const padded = Buffer.alloc(Math.ceil(content.length / HEADER_SIZE) * HEADER_SIZE); |
| 95 | + Buffer.from(content).copy(padded, 0); |
| 96 | + return new Uint8Array(Buffer.concat([header, padded, Buffer.alloc(HEADER_SIZE * 2)])); |
| 97 | + } |
| 98 | + |
| 99 | + async function extract(tar) { |
| 100 | + const parser = new USTarParser(); |
| 101 | + let received = null; |
| 102 | + parser.on("file", (name, content) => { received = { name, content }; }); |
| 103 | + await parser.parse(tar); |
| 104 | + return received; |
| 105 | + } |
| 106 | + |
| 107 | + it("keeps the last byte when it is a printable non-newline character", async () => { |
| 108 | + const content = Buffer.from("hello, world}"); |
| 109 | + const got = await extract(buildUstarArchive("file.txt", content)); |
| 110 | + |
| 111 | + assert.equal(got.content.length, content.length); |
| 112 | + assert.deepEqual([...got.content], [...content]); |
| 113 | + }); |
| 114 | + |
| 115 | + it("keeps the last byte when it is 0x00", async () => { |
| 116 | + const content = Buffer.from([0x01, 0x02, 0x00]); |
| 117 | + const got = await extract(buildUstarArchive("bin", content)); |
| 118 | + |
| 119 | + assert.equal(got.content.length, content.length); |
| 120 | + assert.deepEqual([...got.content], [...content]); |
| 121 | + }); |
| 122 | + |
| 123 | + it("keeps the last byte when it is 0x0a", async () => { |
| 124 | + const content = Buffer.from("line one\nline two\n"); |
| 125 | + const got = await extract(buildUstarArchive("multi.txt", content)); |
| 126 | + |
| 127 | + assert.equal(got.content.length, content.length); |
| 128 | + assert.deepEqual([...got.content], [...content]); |
| 129 | + }); |
| 130 | + |
| 131 | + // Regression for https://github.com/cdnjs/cdnjs/issues/14305 — real |
| 132 | + // fa-regular-400.woff2 extracted from @fortawesome/fontawesome-free@7.3.0. |
| 133 | + it("extracts a real font-awesome .woff2 byte-exact against upstream", async () => { |
| 134 | + const UPSTREAM_SIZE = 19480; |
| 135 | + const UPSTREAM_SHA256 = "f4f5cc8867d30647f0ad918c01599fa8aa9657c39eb55f0fc610120986544385"; |
| 136 | + |
| 137 | + const tar = new Uint8Array(readFileSync(join("fixtures", "fa-regular-400.tar"))); |
| 138 | + const got = await extract(tar); |
| 139 | + |
| 140 | + assert.equal(got.name, "package/webfonts/fa-regular-400.woff2"); |
| 141 | + assert.equal(got.content.length, UPSTREAM_SIZE); |
| 142 | + assert.equal( |
| 143 | + createHash("sha256").update(Buffer.from(got.content)).digest("hex"), |
| 144 | + UPSTREAM_SHA256 |
| 145 | + ); |
| 146 | + assert.deepEqual([...got.content.slice(0, 4)], [0x77, 0x4f, 0x46, 0x32]); |
| 147 | + }); |
| 148 | +}); |
| 149 | + |
72 | 150 | describe('streaming', () => { |
73 | 151 | it("should dump in streaming - files between chunks", async () => { |
74 | 152 | const tar = new Uint8Array(readFileSync("./fixtures/hi-sven-1.0.0.tar", null)); |
|
0 commit comments