123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- 'use strict'
-
- const BB = require('bluebird')
-
- const contentPath = require('./path')
- const figgyPudding = require('figgy-pudding')
- const fs = require('graceful-fs')
- const PassThrough = require('stream').PassThrough
- const pipe = BB.promisify(require('mississippi').pipe)
- const ssri = require('ssri')
- const Y = require('../util/y.js')
-
- const lstatAsync = BB.promisify(fs.lstat)
- const readFileAsync = BB.promisify(fs.readFile)
-
- const ReadOpts = figgyPudding({
- size: {}
- })
-
- module.exports = read
- function read (cache, integrity, opts) {
- opts = ReadOpts(opts)
- return withContentSri(cache, integrity, (cpath, sri) => {
- return readFileAsync(cpath, null).then(data => {
- if (typeof opts.size === 'number' && opts.size !== data.length) {
- throw sizeError(opts.size, data.length)
- } else if (ssri.checkData(data, sri)) {
- return data
- } else {
- throw integrityError(sri, cpath)
- }
- })
- })
- }
-
- module.exports.sync = readSync
- function readSync (cache, integrity, opts) {
- opts = ReadOpts(opts)
- return withContentSriSync(cache, integrity, (cpath, sri) => {
- const data = fs.readFileSync(cpath)
- if (typeof opts.size === 'number' && opts.size !== data.length) {
- throw sizeError(opts.size, data.length)
- } else if (ssri.checkData(data, sri)) {
- return data
- } else {
- throw integrityError(sri, cpath)
- }
- })
- }
-
- module.exports.stream = readStream
- module.exports.readStream = readStream
- function readStream (cache, integrity, opts) {
- opts = ReadOpts(opts)
- const stream = new PassThrough()
- withContentSri(cache, integrity, (cpath, sri) => {
- return lstatAsync(cpath).then(stat => ({ cpath, sri, stat }))
- }).then(({ cpath, sri, stat }) => {
- return pipe(
- fs.createReadStream(cpath),
- ssri.integrityStream({
- integrity: sri,
- size: opts.size
- }),
- stream
- )
- }).catch(err => {
- stream.emit('error', err)
- })
- return stream
- }
-
- let copyFileAsync
- if (fs.copyFile) {
- module.exports.copy = copy
- module.exports.copy.sync = copySync
- copyFileAsync = BB.promisify(fs.copyFile)
- }
-
- function copy (cache, integrity, dest, opts) {
- opts = ReadOpts(opts)
- return withContentSri(cache, integrity, (cpath, sri) => {
- return copyFileAsync(cpath, dest)
- })
- }
-
- function copySync (cache, integrity, dest, opts) {
- opts = ReadOpts(opts)
- return withContentSriSync(cache, integrity, (cpath, sri) => {
- return fs.copyFileSync(cpath, dest)
- })
- }
-
- module.exports.hasContent = hasContent
- function hasContent (cache, integrity) {
- if (!integrity) { return BB.resolve(false) }
- return withContentSri(cache, integrity, (cpath, sri) => {
- return lstatAsync(cpath).then(stat => ({ size: stat.size, sri, stat }))
- }).catch(err => {
- if (err.code === 'ENOENT') { return false }
- if (err.code === 'EPERM') {
- if (process.platform !== 'win32') {
- throw err
- } else {
- return false
- }
- }
- })
- }
-
- module.exports.hasContent.sync = hasContentSync
- function hasContentSync (cache, integrity) {
- if (!integrity) { return false }
- return withContentSriSync(cache, integrity, (cpath, sri) => {
- try {
- const stat = fs.lstatSync(cpath)
- return { size: stat.size, sri, stat }
- } catch (err) {
- if (err.code === 'ENOENT') { return false }
- if (err.code === 'EPERM') {
- if (process.platform !== 'win32') {
- throw err
- } else {
- return false
- }
- }
- }
- })
- }
-
- function withContentSri (cache, integrity, fn) {
- return BB.try(() => {
- const sri = ssri.parse(integrity)
- // If `integrity` has multiple entries, pick the first digest
- // with available local data.
- const algo = sri.pickAlgorithm()
- const digests = sri[algo]
- if (digests.length <= 1) {
- const cpath = contentPath(cache, digests[0])
- return fn(cpath, digests[0])
- } else {
- return BB.any(sri[sri.pickAlgorithm()].map(meta => {
- return withContentSri(cache, meta, fn)
- }, { concurrency: 1 }))
- .catch(err => {
- if ([].some.call(err, e => e.code === 'ENOENT')) {
- throw Object.assign(
- new Error('No matching content found for ' + sri.toString()),
- { code: 'ENOENT' }
- )
- } else {
- throw err[0]
- }
- })
- }
- })
- }
-
- function withContentSriSync (cache, integrity, fn) {
- const sri = ssri.parse(integrity)
- // If `integrity` has multiple entries, pick the first digest
- // with available local data.
- const algo = sri.pickAlgorithm()
- const digests = sri[algo]
- if (digests.length <= 1) {
- const cpath = contentPath(cache, digests[0])
- return fn(cpath, digests[0])
- } else {
- let lastErr = null
- for (const meta of sri[sri.pickAlgorithm()]) {
- try {
- return withContentSriSync(cache, meta, fn)
- } catch (err) {
- lastErr = err
- }
- }
- if (lastErr) { throw lastErr }
- }
- }
-
- function sizeError (expected, found) {
- var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
- err.expected = expected
- err.found = found
- err.code = 'EBADSIZE'
- return err
- }
-
- function integrityError (sri, path) {
- var err = new Error(Y`Integrity verification failed for ${sri} (${path})`)
- err.code = 'EINTEGRITY'
- err.sri = sri
- err.path = path
- return err
- }
|