cacache 16.0.6 → 16.1.1
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 +13 -0
- package/lib/content/read.js +77 -95
- package/lib/content/rm.js +9 -9
- package/lib/content/write.js +57 -64
- package/lib/entry-index.js +97 -106
- package/lib/get.js +67 -94
- package/lib/put.js +20 -27
- package/lib/util/fix-owner.js +36 -39
- package/lib/util/move-file.js +35 -48
- package/lib/util/tmp.js +5 -7
- package/lib/verify.js +156 -192
- package/package.json +3 -12
- package/lib/util/disposer.js +0 -31
package/README.md
CHANGED
|
@@ -413,6 +413,19 @@ with an `EINTEGRITY` error.
|
|
|
413
413
|
|
|
414
414
|
`algorithms` has no effect if this option is present.
|
|
415
415
|
|
|
416
|
+
##### `opts.integrityEmitter`
|
|
417
|
+
|
|
418
|
+
*Streaming only* If present, uses the provided event emitter as a source of
|
|
419
|
+
truth for both integrity and size. This allows use cases where integrity is
|
|
420
|
+
already being calculated outside of cacache to reuse that data instead of
|
|
421
|
+
calculating it a second time.
|
|
422
|
+
|
|
423
|
+
The emitter must emit both the `'integrity'` and `'size'` events.
|
|
424
|
+
|
|
425
|
+
NOTE: If this option is provided, you must verify that you receive the correct
|
|
426
|
+
integrity value yourself and emit an `'error'` event if there is a mismatch.
|
|
427
|
+
[ssri Integrity Streams](https://github.com/npm/ssri#integrity-stream) do this for you when given an expected integrity.
|
|
428
|
+
|
|
416
429
|
##### `opts.algorithms`
|
|
417
430
|
|
|
418
431
|
Default: ['sha512']
|
package/lib/content/read.js
CHANGED
|
@@ -1,41 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const fs = require('fs')
|
|
3
|
+
const fs = require('@npmcli/fs')
|
|
6
4
|
const fsm = require('fs-minipass')
|
|
7
5
|
const ssri = require('ssri')
|
|
8
6
|
const contentPath = require('./path')
|
|
9
7
|
const Pipeline = require('minipass-pipeline')
|
|
10
8
|
|
|
11
|
-
const lstat = util.promisify(fs.lstat)
|
|
12
|
-
const readFile = util.promisify(fs.readFile)
|
|
13
|
-
|
|
14
9
|
module.exports = read
|
|
15
10
|
|
|
16
11
|
const MAX_SINGLE_READ_SIZE = 64 * 1024 * 1024
|
|
17
|
-
function read (cache, integrity, opts = {}) {
|
|
12
|
+
async function read (cache, integrity, opts = {}) {
|
|
18
13
|
const { size } = opts
|
|
19
|
-
|
|
14
|
+
const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => {
|
|
20
15
|
// get size
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
const stat = await fs.stat(cpath)
|
|
17
|
+
return { stat, cpath, sri }
|
|
18
|
+
})
|
|
19
|
+
if (typeof size === 'number' && stat.size !== size) {
|
|
20
|
+
throw sizeError(size, stat.size)
|
|
21
|
+
}
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
if (stat.size > MAX_SINGLE_READ_SIZE) {
|
|
24
|
+
return readPipeline(cpath, stat.size, sri, new Pipeline()).concat()
|
|
25
|
+
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
const data = await fs.readFile(cpath, { encoding: null })
|
|
28
|
+
if (!ssri.checkData(data, sri)) {
|
|
29
|
+
throw integrityError(sri, cpath)
|
|
30
|
+
}
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
})
|
|
38
|
-
})
|
|
32
|
+
return data
|
|
39
33
|
}
|
|
40
34
|
|
|
41
35
|
const readPipeline = (cpath, size, sri, stream) => {
|
|
@@ -57,7 +51,7 @@ module.exports.sync = readSync
|
|
|
57
51
|
function readSync (cache, integrity, opts = {}) {
|
|
58
52
|
const { size } = opts
|
|
59
53
|
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
|
60
|
-
const data = fs.readFileSync(cpath)
|
|
54
|
+
const data = fs.readFileSync(cpath, { encoding: null })
|
|
61
55
|
if (typeof size === 'number' && size !== data.length) {
|
|
62
56
|
throw sizeError(size, data.length)
|
|
63
57
|
}
|
|
@@ -76,30 +70,29 @@ module.exports.readStream = readStream
|
|
|
76
70
|
function readStream (cache, integrity, opts = {}) {
|
|
77
71
|
const { size } = opts
|
|
78
72
|
const stream = new Pipeline()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
// Set all this up to run on the stream and then just return the stream
|
|
74
|
+
Promise.resolve().then(async () => {
|
|
75
|
+
const { stat, cpath, sri } = await withContentSri(cache, integrity, async (cpath, sri) => {
|
|
76
|
+
// just stat to ensure it exists
|
|
77
|
+
const stat = await fs.stat(cpath)
|
|
78
|
+
return { stat, cpath, sri }
|
|
79
|
+
})
|
|
83
80
|
if (typeof size === 'number' && size !== stat.size) {
|
|
84
81
|
return stream.emit('error', sizeError(size, stat.size))
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
readPipeline(cpath, stat.size, sri, stream)
|
|
88
|
-
}
|
|
85
|
+
}).catch(err => stream.emit('error', err))
|
|
89
86
|
|
|
90
87
|
return stream
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
module.exports.copy = copy
|
|
96
|
-
module.exports.copy.sync = copySync
|
|
97
|
-
copyFile = util.promisify(fs.copyFile)
|
|
98
|
-
}
|
|
90
|
+
module.exports.copy = copy
|
|
91
|
+
module.exports.copy.sync = copySync
|
|
99
92
|
|
|
100
93
|
function copy (cache, integrity, dest) {
|
|
101
94
|
return withContentSri(cache, integrity, (cpath, sri) => {
|
|
102
|
-
return copyFile(cpath, dest)
|
|
95
|
+
return fs.copyFile(cpath, dest)
|
|
103
96
|
})
|
|
104
97
|
}
|
|
105
98
|
|
|
@@ -111,14 +104,17 @@ function copySync (cache, integrity, dest) {
|
|
|
111
104
|
|
|
112
105
|
module.exports.hasContent = hasContent
|
|
113
106
|
|
|
114
|
-
function hasContent (cache, integrity) {
|
|
107
|
+
async function hasContent (cache, integrity) {
|
|
115
108
|
if (!integrity) {
|
|
116
|
-
return
|
|
109
|
+
return false
|
|
117
110
|
}
|
|
118
111
|
|
|
119
|
-
|
|
120
|
-
return
|
|
121
|
-
|
|
112
|
+
try {
|
|
113
|
+
return await withContentSri(cache, integrity, async (cpath, sri) => {
|
|
114
|
+
const stat = await fs.stat(cpath)
|
|
115
|
+
return { size: stat.size, sri, stat }
|
|
116
|
+
})
|
|
117
|
+
} catch (err) {
|
|
122
118
|
if (err.code === 'ENOENT') {
|
|
123
119
|
return false
|
|
124
120
|
}
|
|
@@ -131,7 +127,7 @@ function hasContent (cache, integrity) {
|
|
|
131
127
|
return false
|
|
132
128
|
}
|
|
133
129
|
}
|
|
134
|
-
}
|
|
130
|
+
}
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
module.exports.hasContent.sync = hasContentSync
|
|
@@ -143,7 +139,7 @@ function hasContentSync (cache, integrity) {
|
|
|
143
139
|
|
|
144
140
|
return withContentSriSync(cache, integrity, (cpath, sri) => {
|
|
145
141
|
try {
|
|
146
|
-
const stat = fs.
|
|
142
|
+
const stat = fs.statSync(cpath)
|
|
147
143
|
return { size: stat.size, sri, stat }
|
|
148
144
|
} catch (err) {
|
|
149
145
|
if (err.code === 'ENOENT') {
|
|
@@ -162,61 +158,47 @@ function hasContentSync (cache, integrity) {
|
|
|
162
158
|
})
|
|
163
159
|
}
|
|
164
160
|
|
|
165
|
-
function withContentSri (cache, integrity, fn) {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (result) {
|
|
196
|
-
return result
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Throw the No matching content found error
|
|
200
|
-
const enoentError = results.find((r) => r.code === 'ENOENT')
|
|
201
|
-
if (enoentError) {
|
|
202
|
-
throw enoentError
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Throw generic error
|
|
206
|
-
throw results.find((r) => r instanceof Error)
|
|
207
|
-
})
|
|
161
|
+
async function withContentSri (cache, integrity, fn) {
|
|
162
|
+
const sri = ssri.parse(integrity)
|
|
163
|
+
// If `integrity` has multiple entries, pick the first digest
|
|
164
|
+
// with available local data.
|
|
165
|
+
const algo = sri.pickAlgorithm()
|
|
166
|
+
const digests = sri[algo]
|
|
167
|
+
|
|
168
|
+
if (digests.length <= 1) {
|
|
169
|
+
const cpath = contentPath(cache, digests[0])
|
|
170
|
+
return fn(cpath, digests[0])
|
|
171
|
+
} else {
|
|
172
|
+
// Can't use race here because a generic error can happen before
|
|
173
|
+
// a ENOENT error, and can happen before a valid result
|
|
174
|
+
const results = await Promise.all(digests.map(async (meta) => {
|
|
175
|
+
try {
|
|
176
|
+
return await withContentSri(cache, meta, fn)
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err.code === 'ENOENT') {
|
|
179
|
+
return Object.assign(
|
|
180
|
+
new Error('No matching content found for ' + sri.toString()),
|
|
181
|
+
{ code: 'ENOENT' }
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
return err
|
|
185
|
+
}
|
|
186
|
+
}))
|
|
187
|
+
// Return the first non error if it is found
|
|
188
|
+
const result = results.find((r) => !(r instanceof Error))
|
|
189
|
+
if (result) {
|
|
190
|
+
return result
|
|
208
191
|
}
|
|
209
|
-
}
|
|
210
192
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
.catch(reject)
|
|
216
|
-
} catch (err) {
|
|
217
|
-
reject(err)
|
|
193
|
+
// Throw the No matching content found error
|
|
194
|
+
const enoentError = results.find((r) => r.code === 'ENOENT')
|
|
195
|
+
if (enoentError) {
|
|
196
|
+
throw enoentError
|
|
218
197
|
}
|
|
219
|
-
|
|
198
|
+
|
|
199
|
+
// Throw generic error
|
|
200
|
+
throw results.find((r) => r instanceof Error)
|
|
201
|
+
}
|
|
220
202
|
}
|
|
221
203
|
|
|
222
204
|
function withContentSriSync (cache, integrity, fn) {
|
package/lib/content/rm.js
CHANGED
|
@@ -8,13 +8,13 @@ const rimraf = util.promisify(require('rimraf'))
|
|
|
8
8
|
|
|
9
9
|
module.exports = rm
|
|
10
10
|
|
|
11
|
-
function rm (cache, integrity) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
11
|
+
async function rm (cache, integrity) {
|
|
12
|
+
const content = await hasContent(cache, integrity)
|
|
13
|
+
// ~pretty~ sure we can't end up with a content lacking sri, but be safe
|
|
14
|
+
if (content && content.sri) {
|
|
15
|
+
await rimraf(contentPath(cache, content.sri))
|
|
16
|
+
return true
|
|
17
|
+
} else {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
20
|
}
|
package/lib/content/write.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const events = require('events')
|
|
3
4
|
const util = require('util')
|
|
4
5
|
|
|
5
6
|
const contentPath = require('./path')
|
|
6
7
|
const fixOwner = require('../util/fix-owner')
|
|
7
|
-
const fs = require('fs')
|
|
8
|
+
const fs = require('@npmcli/fs')
|
|
8
9
|
const moveFile = require('../util/move-file')
|
|
9
10
|
const Minipass = require('minipass')
|
|
10
11
|
const Pipeline = require('minipass-pipeline')
|
|
@@ -13,34 +14,35 @@ const path = require('path')
|
|
|
13
14
|
const rimraf = util.promisify(require('rimraf'))
|
|
14
15
|
const ssri = require('ssri')
|
|
15
16
|
const uniqueFilename = require('unique-filename')
|
|
16
|
-
const { disposer } = require('./../util/disposer')
|
|
17
17
|
const fsm = require('fs-minipass')
|
|
18
18
|
|
|
19
|
-
const writeFile = util.promisify(fs.writeFile)
|
|
20
|
-
|
|
21
19
|
module.exports = write
|
|
22
20
|
|
|
23
|
-
function write (cache, data, opts = {}) {
|
|
21
|
+
async function write (cache, data, opts = {}) {
|
|
24
22
|
const { algorithms, size, integrity } = opts
|
|
25
23
|
if (algorithms && algorithms.length > 1) {
|
|
26
24
|
throw new Error('opts.algorithms only supports a single algorithm for now')
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
if (typeof size === 'number' && data.length !== size) {
|
|
30
|
-
|
|
28
|
+
throw sizeError(size, data.length)
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
const sri = ssri.fromData(data, algorithms ? { algorithms } : {})
|
|
34
32
|
if (integrity && !ssri.checkData(data, integrity, opts)) {
|
|
35
|
-
|
|
33
|
+
throw checksumError(integrity, sri)
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
36
|
+
const tmp = await makeTmp(cache, opts)
|
|
37
|
+
try {
|
|
38
|
+
await fs.writeFile(tmp.target, data, { flag: 'wx' })
|
|
39
|
+
await moveToDestination(tmp, cache, sri, opts)
|
|
40
|
+
return { integrity: sri, size: data.length }
|
|
41
|
+
} finally {
|
|
42
|
+
if (!tmp.moved) {
|
|
43
|
+
await rimraf(tmp.target)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
module.exports.stream = writeStream
|
|
@@ -94,21 +96,39 @@ function writeStream (cache, opts = {}) {
|
|
|
94
96
|
return new CacacheWriteStream(cache, opts)
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
function handleContent (inputStream, cache, opts) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
99
|
+
async function handleContent (inputStream, cache, opts) {
|
|
100
|
+
const tmp = await makeTmp(cache, opts)
|
|
101
|
+
try {
|
|
102
|
+
const res = await pipeToTmp(inputStream, cache, tmp.target, opts)
|
|
103
|
+
await moveToDestination(
|
|
104
|
+
tmp,
|
|
105
|
+
cache,
|
|
106
|
+
res.integrity,
|
|
107
|
+
opts
|
|
108
|
+
)
|
|
109
|
+
return res
|
|
110
|
+
} finally {
|
|
111
|
+
if (!tmp.moved) {
|
|
112
|
+
await rimraf(tmp.target)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
function pipeToTmp (inputStream, cache, tmpTarget, opts) {
|
|
117
|
+
async function pipeToTmp (inputStream, cache, tmpTarget, opts) {
|
|
118
|
+
const outStream = new fsm.WriteStream(tmpTarget, {
|
|
119
|
+
flags: 'wx',
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (opts.integrityEmitter) {
|
|
123
|
+
// we need to create these all simultaneously since they can fire in any order
|
|
124
|
+
const [integrity, size] = await Promise.all([
|
|
125
|
+
events.once(opts.integrityEmitter, 'integrity').then(res => res[0]),
|
|
126
|
+
events.once(opts.integrityEmitter, 'size').then(res => res[0]),
|
|
127
|
+
new Pipeline(inputStream, outStream).promise(),
|
|
128
|
+
])
|
|
129
|
+
return { integrity, size }
|
|
130
|
+
}
|
|
131
|
+
|
|
112
132
|
let integrity
|
|
113
133
|
let size
|
|
114
134
|
const hashStream = ssri.integrityStream({
|
|
@@ -123,55 +143,28 @@ function pipeToTmp (inputStream, cache, tmpTarget, opts) {
|
|
|
123
143
|
size = s
|
|
124
144
|
})
|
|
125
145
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// NB: this can throw if the hashStream has a problem with
|
|
131
|
-
// it, and the data is fully written. but pipeToTmp is only
|
|
132
|
-
// called in promisory contexts where that is handled.
|
|
133
|
-
const pipeline = new Pipeline(
|
|
134
|
-
inputStream,
|
|
135
|
-
hashStream,
|
|
136
|
-
outStream
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
return pipeline.promise()
|
|
140
|
-
.then(() => ({ integrity, size }))
|
|
141
|
-
.catch(er => rimraf(tmpTarget).then(() => {
|
|
142
|
-
throw er
|
|
143
|
-
}))
|
|
146
|
+
const pipeline = new Pipeline(inputStream, hashStream, outStream)
|
|
147
|
+
await pipeline.promise()
|
|
148
|
+
return { integrity, size }
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
function makeTmp (cache, opts) {
|
|
151
|
+
async function makeTmp (cache, opts) {
|
|
147
152
|
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
|
|
148
|
-
|
|
153
|
+
await fixOwner.mkdirfix(cache, path.dirname(tmpTarget))
|
|
154
|
+
return {
|
|
149
155
|
target: tmpTarget,
|
|
150
156
|
moved: false,
|
|
151
|
-
}))
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function makeTmpDisposer (tmp) {
|
|
155
|
-
if (tmp.moved) {
|
|
156
|
-
return Promise.resolve()
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
return rimraf(tmp.target)
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
function moveToDestination (tmp, cache, sri, opts) {
|
|
160
|
+
async function moveToDestination (tmp, cache, sri, opts) {
|
|
163
161
|
const destination = contentPath(cache, sri)
|
|
164
162
|
const destDir = path.dirname(destination)
|
|
165
163
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
})
|
|
171
|
-
.then(() => {
|
|
172
|
-
tmp.moved = true
|
|
173
|
-
return fixOwner.chownr(cache, destination)
|
|
174
|
-
})
|
|
164
|
+
await fixOwner.mkdirfix(cache, destDir)
|
|
165
|
+
await moveFile(tmp.target, destination)
|
|
166
|
+
tmp.moved = true
|
|
167
|
+
await fixOwner.chownr(cache, destination)
|
|
175
168
|
}
|
|
176
169
|
|
|
177
170
|
function sizeError (expected, found) {
|