| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 | 'use strict'const BB = require('bluebird')const contentPath = require('./path')const fixOwner = require('../util/fix-owner')const fs = require('graceful-fs')const moveFile = require('../util/move-file')const PassThrough = require('stream').PassThroughconst path = require('path')const pipe = BB.promisify(require('mississippi').pipe)const rimraf = BB.promisify(require('rimraf'))const ssri = require('ssri')const to = require('mississippi').toconst uniqueFilename = require('unique-filename')const Y = require('../util/y.js')const writeFileAsync = BB.promisify(fs.writeFile)module.exports = writefunction write (cache, data, opts) {  opts = opts || {}  if (opts.algorithms && opts.algorithms.length > 1) {    throw new Error(      Y`opts.algorithms only supports a single algorithm for now`    )  }  if (typeof opts.size === 'number' && data.length !== opts.size) {    return BB.reject(sizeError(opts.size, data.length))  }  const sri = ssri.fromData(data, {    algorithms: opts.algorithms  })  if (opts.integrity && !ssri.checkData(data, opts.integrity, opts)) {    return BB.reject(checksumError(opts.integrity, sri))  }  return BB.using(makeTmp(cache, opts), tmp => (    writeFileAsync(      tmp.target, data, { flag: 'wx' }    ).then(() => (      moveToDestination(tmp, cache, sri, opts)    ))  )).then(() => ({ integrity: sri, size: data.length }))}module.exports.stream = writeStreamfunction writeStream (cache, opts) {  opts = opts || {}  const inputStream = new PassThrough()  let inputErr = false  function errCheck () {    if (inputErr) { throw inputErr }  }  let allDone  const ret = to((c, n, cb) => {    if (!allDone) {      allDone = handleContent(inputStream, cache, opts, errCheck)    }    inputStream.write(c, n, cb)  }, cb => {    inputStream.end(() => {      if (!allDone) {        const e = new Error(Y`Cache input stream was empty`)        e.code = 'ENODATA'        return ret.emit('error', e)      }      allDone.then(res => {        res.integrity && ret.emit('integrity', res.integrity)        res.size !== null && ret.emit('size', res.size)        cb()      }, e => {        ret.emit('error', e)      })    })  })  ret.once('error', e => {    inputErr = e  })  return ret}function handleContent (inputStream, cache, opts, errCheck) {  return BB.using(makeTmp(cache, opts), tmp => {    errCheck()    return pipeToTmp(      inputStream, cache, tmp.target, opts, errCheck    ).then(res => {      return moveToDestination(        tmp, cache, res.integrity, opts, errCheck      ).then(() => res)    })  })}function pipeToTmp (inputStream, cache, tmpTarget, opts, errCheck) {  return BB.resolve().then(() => {    let integrity    let size    const hashStream = ssri.integrityStream({      integrity: opts.integrity,      algorithms: opts.algorithms,      size: opts.size    }).on('integrity', s => {      integrity = s    }).on('size', s => {      size = s    })    const outStream = fs.createWriteStream(tmpTarget, {      flags: 'wx'    })    errCheck()    return pipe(inputStream, hashStream, outStream).then(() => {      return { integrity, size }    }).catch(err => {      return rimraf(tmpTarget).then(() => { throw err })    })  })}function makeTmp (cache, opts) {  const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)  return fixOwner.mkdirfix(    cache, path.dirname(tmpTarget)  ).then(() => ({    target: tmpTarget,    moved: false  })).disposer(tmp => (!tmp.moved && rimraf(tmp.target)))}function moveToDestination (tmp, cache, sri, opts, errCheck) {  errCheck && errCheck()  const destination = contentPath(cache, sri)  const destDir = path.dirname(destination)  return fixOwner.mkdirfix(    cache, destDir  ).then(() => {    errCheck && errCheck()    return moveFile(tmp.target, destination)  }).then(() => {    errCheck && errCheck()    tmp.moved = true    return fixOwner.chownr(cache, destination)  })}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 checksumError (expected, found) {  var err = new Error(Y`Integrity check failed:  Wanted: ${expected}   Found: ${found}`)  err.code = 'EINTEGRITY'  err.expected = expected  err.found = found  return err}
 |