Skip to content

Commit

Permalink
demonstrate fetch tar extract errors with chunked encoding.
Browse files Browse the repository at this point in the history
Related to npm/cli#3884, npm/make-fetch-happen#63

npm v7+ throws integrity and zlib corrupted errors when installing from
a registry that uses chunked encoding.

This package reproduces that error using make-fetch-happen and tar. The
error only occurs when fetch is passed integrity and the server uses
chunked encoding. Only the `extract with integirty` test fails.
  • Loading branch information
Caleb ツ Everett committed Nov 30, 2021
0 parents commit ed48050
Show file tree
Hide file tree
Showing 5 changed files with 8,943 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.nyc_output/
/node_modules/
96 changes: 96 additions & 0 deletions error.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import crypto from 'crypto'
import fetch from 'make-fetch-happen'
import tar from 'tar'
import fs from 'fs/promises'
import http from 'http'
import t from 'tap'

const lodash = await fs.readFile('lodash-4.17.19.tgz')
const integrity = crypto
.createHash('sha512')
.update(lodash)
.digest('base64')

// different sizes will reproduce the issue, but large sizes will not.
const size = 0x4000

const server = http.createServer(async (req, res) => {
const headers = {}
if (req.url === '/content-length') headers['content-length'] = lodash.length
res.writeHead(200, headers)
for (let i = 0; i < lodash.length; i += size) {
res.write(lodash.slice(i, i+size));
}
res.end()
})

server.listen(4000, 'localhost')
await new Promise((resolve, reject) => {
server.once('listening', resolve)
server.once('error', reject)
})
console.log(server.address())
t.teardown(() => server.close())

await t.test('res.body data events updating hash', async t => {
const res = await fetch('http://localhost:4000', { integrity })
const hash = crypto.createHash('sha512')
await new Promise((resolve,reject) => {
res.body.on('end', resolve)
res.body.on('error', reject)
res.body.on('data', data => hash.update(data))
})

t.equal(hash.digest('base64'), integrity, 'yields the right digest')
})

await t.test('res.body pipe to createHash', async t => {
const res = await fetch('http://localhost:4000', { integrity })
const hash = crypto.createHash('sha512')
await new Promise((resolve,reject) => {
// XXX Hash doesn't emit end?
// hash.on('end', resolve)
res.body.on('end', resolve)
hash.on('error', reject)
res.body.on('error', reject)
res.body.pipe(hash)
})

t.equal(hash.digest('base64'), integrity, 'yields the right digest')
})

await t.test('extract with integrity', async t => {
const dest = t.testdir()
const res = await fetch('http://localhost:4000', { integrity })

await extract(res.body, dest)
t.pass('succeeds')
})

await t.test('extract with integrity and content-length', async t => {
const dest = t.testdir()
const res = await fetch('http://localhost:4000/content-length', { integrity })

await extract(res.body, dest)
t.pass('succeeds')
})

await t.test('extract without integrity', async t => {
const dest = t.testdir()
const res = await fetch('http://localhost:4000')

await extract(res.body, dest)
t.pass('succeeds')
})

async function extract (stream, dest) {
// copied from pacote
// https://github.com/npm/pacote/blob/bd67be1ea53ab02c2be781a3fc2283eb9fcba3c8/lib/fetcher.js#L399-L419
const extractor = tar.x({ cwd: dest })
return new Promise((resolve, reject) => {
extractor.on('end', resolve)
extractor.on('error', reject)
stream.on('error', reject)
stream.pipe(extractor)
})
}
Binary file added lodash-4.17.19.tgz
Binary file not shown.
Loading

0 comments on commit ed48050

Please sign in to comment.