• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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