1'use strict' 2 3const BB = require('bluebird') 4 5const contentPath = require('./path') 6const figgyPudding = require('figgy-pudding') 7const fs = require('graceful-fs') 8const PassThrough = require('stream').PassThrough 9const pipe = BB.promisify(require('mississippi').pipe) 10const ssri = require('ssri') 11const Y = require('../util/y.js') 12 13const lstatAsync = BB.promisify(fs.lstat) 14const readFileAsync = BB.promisify(fs.readFile) 15 16const ReadOpts = figgyPudding({ 17 size: {} 18}) 19 20module.exports = read 21function read (cache, integrity, opts) { 22 opts = ReadOpts(opts) 23 return withContentSri(cache, integrity, (cpath, sri) => { 24 return readFileAsync(cpath, null).then(data => { 25 if (typeof opts.size === 'number' && opts.size !== data.length) { 26 throw sizeError(opts.size, data.length) 27 } else if (ssri.checkData(data, sri)) { 28 return data 29 } else { 30 throw integrityError(sri, cpath) 31 } 32 }) 33 }) 34} 35 36module.exports.sync = readSync 37function readSync (cache, integrity, opts) { 38 opts = ReadOpts(opts) 39 return withContentSriSync(cache, integrity, (cpath, sri) => { 40 const data = fs.readFileSync(cpath) 41 if (typeof opts.size === 'number' && opts.size !== data.length) { 42 throw sizeError(opts.size, data.length) 43 } else if (ssri.checkData(data, sri)) { 44 return data 45 } else { 46 throw integrityError(sri, cpath) 47 } 48 }) 49} 50 51module.exports.stream = readStream 52module.exports.readStream = readStream 53function readStream (cache, integrity, opts) { 54 opts = ReadOpts(opts) 55 const stream = new PassThrough() 56 withContentSri(cache, integrity, (cpath, sri) => { 57 return lstatAsync(cpath).then(stat => ({ cpath, sri, stat })) 58 }).then(({ cpath, sri, stat }) => { 59 return pipe( 60 fs.createReadStream(cpath), 61 ssri.integrityStream({ 62 integrity: sri, 63 size: opts.size 64 }), 65 stream 66 ) 67 }).catch(err => { 68 stream.emit('error', err) 69 }) 70 return stream 71} 72 73let copyFileAsync 74if (fs.copyFile) { 75 module.exports.copy = copy 76 module.exports.copy.sync = copySync 77 copyFileAsync = BB.promisify(fs.copyFile) 78} 79 80function copy (cache, integrity, dest, opts) { 81 opts = ReadOpts(opts) 82 return withContentSri(cache, integrity, (cpath, sri) => { 83 return copyFileAsync(cpath, dest) 84 }) 85} 86 87function copySync (cache, integrity, dest, opts) { 88 opts = ReadOpts(opts) 89 return withContentSriSync(cache, integrity, (cpath, sri) => { 90 return fs.copyFileSync(cpath, dest) 91 }) 92} 93 94module.exports.hasContent = hasContent 95function hasContent (cache, integrity) { 96 if (!integrity) { return BB.resolve(false) } 97 return withContentSri(cache, integrity, (cpath, sri) => { 98 return lstatAsync(cpath).then(stat => ({ size: stat.size, sri, stat })) 99 }).catch(err => { 100 if (err.code === 'ENOENT') { return false } 101 if (err.code === 'EPERM') { 102 if (process.platform !== 'win32') { 103 throw err 104 } else { 105 return false 106 } 107 } 108 }) 109} 110 111module.exports.hasContent.sync = hasContentSync 112function hasContentSync (cache, integrity) { 113 if (!integrity) { return false } 114 return withContentSriSync(cache, integrity, (cpath, sri) => { 115 try { 116 const stat = fs.lstatSync(cpath) 117 return { size: stat.size, sri, stat } 118 } catch (err) { 119 if (err.code === 'ENOENT') { return false } 120 if (err.code === 'EPERM') { 121 if (process.platform !== 'win32') { 122 throw err 123 } else { 124 return false 125 } 126 } 127 } 128 }) 129} 130 131function withContentSri (cache, integrity, fn) { 132 return BB.try(() => { 133 const sri = ssri.parse(integrity) 134 // If `integrity` has multiple entries, pick the first digest 135 // with available local data. 136 const algo = sri.pickAlgorithm() 137 const digests = sri[algo] 138 if (digests.length <= 1) { 139 const cpath = contentPath(cache, digests[0]) 140 return fn(cpath, digests[0]) 141 } else { 142 return BB.any(sri[sri.pickAlgorithm()].map(meta => { 143 return withContentSri(cache, meta, fn) 144 }, { concurrency: 1 })) 145 .catch(err => { 146 if ([].some.call(err, e => e.code === 'ENOENT')) { 147 throw Object.assign( 148 new Error('No matching content found for ' + sri.toString()), 149 { code: 'ENOENT' } 150 ) 151 } else { 152 throw err[0] 153 } 154 }) 155 } 156 }) 157} 158 159function withContentSriSync (cache, integrity, fn) { 160 const sri = ssri.parse(integrity) 161 // If `integrity` has multiple entries, pick the first digest 162 // with available local data. 163 const algo = sri.pickAlgorithm() 164 const digests = sri[algo] 165 if (digests.length <= 1) { 166 const cpath = contentPath(cache, digests[0]) 167 return fn(cpath, digests[0]) 168 } else { 169 let lastErr = null 170 for (const meta of sri[sri.pickAlgorithm()]) { 171 try { 172 return withContentSriSync(cache, meta, fn) 173 } catch (err) { 174 lastErr = err 175 } 176 } 177 if (lastErr) { throw lastErr } 178 } 179} 180 181function sizeError (expected, found) { 182 var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`) 183 err.expected = expected 184 err.found = found 185 err.code = 'EBADSIZE' 186 return err 187} 188 189function integrityError (sri, path) { 190 var err = new Error(Y`Integrity verification failed for ${sri} (${path})`) 191 err.code = 'EINTEGRITY' 192 err.sri = sri 193 err.path = path 194 return err 195} 196