Skip to content

Commit 12c8c8f

Browse files
babyhueyowlstronaut
authored andcommitted
fix: fall back to git clone when tarball response is not a valid archive
When a hosted git provider (e.g. GitLab) returns an HTML page with HTTP 200 for a private repo's archive endpoint instead of a proper 401/403, the tarball extraction fails with TAR_BAD_ARCHIVE. Previously, the fallback to git clone only triggered on HTTP errors. This widens the catch to include any TAR_* error code, allowing pacote to recover by cloning the repo directly. Fixes #476 Ref: npm/cli#3229
1 parent 61f065a commit 12c8c8f

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

lib/git.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,11 @@ class GitFetcher extends Fetcher {
254254
resolved: this.resolved,
255255
integrity: null, // it'll always be different, if we have one
256256
}).extract(tmp).then(() => handler(`${tmp}${this.spec.gitSubdir || ''}`), er => {
257-
// fall back to ssh download if tarball fails
258-
if (typeof er.statusCode === 'number' && er.statusCode >= 400) {
257+
// fall back to clone if the tarball download fails due to an
258+
// HTTP error or if the response is not a valid tarball (e.g.
259+
// a hosted provider returning an HTML sign-in page with 200)
260+
if ((typeof er.statusCode === 'number' && er.statusCode >= 400) ||
261+
/^TAR_/.test(er.code)) {
259262
return this.#clone(handler, false)
260263
} else {
261264
throw er

test/git.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -649,13 +649,30 @@ t.test('fetch a private repo where the tgz is a 404 and http error has minified
649649
})
650650

651651
t.test('fetch a private repo where the tgz is not a tarball', { skip: isWindows && 'posix only' },
652-
t => {
652+
() => {
653653
const gf = new GitFetcher(`localhost:repo/x#${REPO_HEAD}`, opts)
654654
gf.spec.hosted.tarball = () => `${hostedUrl}/not-tar.tgz`
655-
// should NOT retry, because the error was not an HTTP fetch error
656-
return t.rejects(gf.extract(me + '/bad-tgz'), {
657-
code: 'TAR_BAD_ARCHIVE',
655+
// should fall back to clone, since the tarball content is not valid.
656+
// this can happen when a hosted git provider returns an HTML page
657+
// (e.g. a sign-in page) with HTTP 200 for private repo archives.
658+
return gf.extract(me + '/bad-tgz')
659+
})
660+
661+
t.test('non-retriable tarball error is thrown, not retried', { skip: isWindows && 'posix only' },
662+
async t => {
663+
const gf = new GitFetcher(`localhost:repo/x#${REPO_HEAD}`, opts)
664+
gf.spec.hosted.tarball = () => `${hostedUrl}/not-tar.tgz`
665+
// override the hosted tarball to simulate a non-recoverable error
666+
// that is neither an HTTP error nor a TAR error
667+
const orig = gf.spec.hosted.tarball
668+
gf.spec.hosted.tarball = () => orig()
669+
const _extract = RemoteFetcher.prototype.extract
670+
t.teardown(() => {
671+
RemoteFetcher.prototype.extract = _extract
658672
})
673+
RemoteFetcher.prototype.extract = () =>
674+
Promise.reject(Object.assign(new Error('bad'), { code: 'EINTEGRITY' }))
675+
await t.rejects(gf.extract(me + '/bad-tgz-other'), { code: 'EINTEGRITY' })
659676
})
660677

661678
t.test('resolved is a git+ssh url for hosted repos that support it',

0 commit comments

Comments
 (0)