Add /_meta endpoint for metadata

Also, add integrity values to metadata.
This commit is contained in:
MICHAEL JACKSON 2017-08-16 15:57:31 -07:00
parent bc609ca825
commit 666d8afc95
6 changed files with 64 additions and 29 deletions

View File

@ -32,6 +32,7 @@
"react-router-dom": "^4.0.0",
"redis": "^2.7.1",
"semver": "^5.3.0",
"sri-toolbox": "^0.2.0",
"tar-fs": "^1.15.2",
"throng": "^4.0.0",
"validate-npm-package-name": "^3.0.0"

View File

@ -9,6 +9,7 @@ const { fetchStats } = require('./cloudflare')
const parsePackageURL = require('./middleware/parsePackageURL')
const fetchFile = require('./middleware/fetchFile')
const serveFile = require('./middleware/serveFile')
const serveMetadata = require('./middleware/serveMetadata')
morgan.token('fwd', function (req) {
return req.get('x-forwarded-for').replace(/\s/g, '')
@ -66,9 +67,8 @@ function createServer() {
maxAge: '365d'
}))
app.use(parsePackageURL)
app.use(fetchFile)
app.use(serveFile)
app.use('/_meta', parsePackageURL, fetchFile, serveMetadata)
app.use('/', parsePackageURL, fetchFile, serveFile)
const server = http.createServer(app)

View File

@ -1,10 +1,11 @@
const fs = require('fs')
const path = require('path')
const SRIToolbox = require('sri-toolbox')
const { getContentType, getStats, getFileType } = require('./FileUtils')
function getEntries(dir, file, maximumDepth) {
return new Promise((resolve, reject) => {
fs.readdir(path.join(dir, file), (error, files) => {
return new Promise(function (resolve, reject) {
fs.readdir(path.join(dir, file), function (error, files) {
if (error) {
reject(error)
} else {
@ -15,7 +16,7 @@ function getEntries(dir, file, maximumDepth) {
})
).then(function (statsArray) {
return Promise.all(statsArray.map(function (stats, index) {
return getMetadata(dir, path.join(file, files[index]), stats, maximumDepth - 1)
return getMetadataRecursive(dir, path.join(file, files[index]), stats, maximumDepth - 1)
}))
})
)
@ -28,7 +29,19 @@ function formatTime(time) {
return new Date(time).toISOString()
}
function getMetadata(dir, file, stats, maximumDepth) {
function getIntegrity(file) {
return new Promise(function (resolve, reject) {
fs.readFile(file, function (error, data) {
if (error) {
reject(error)
} else {
resolve(SRIToolbox.generate({ algorithms: [ 'sha384' ] }, data))
}
})
})
}
function getMetadataRecursive(dir, file, stats, maximumDepth) {
const metadata = {
lastModified: formatTime(stats.mtime),
contentType: getContentType(file),
@ -37,6 +50,13 @@ function getMetadata(dir, file, stats, maximumDepth) {
type: getFileType(stats)
}
if (stats.isFile()) {
return getIntegrity(path.join(dir, file)).then(function (integrity) {
metadata.integrity = integrity
return metadata
})
}
if (!stats.isDirectory() || maximumDepth === 0)
return Promise.resolve(metadata)
@ -46,12 +66,12 @@ function getMetadata(dir, file, stats, maximumDepth) {
})
}
function generateMetadata(baseDir, path, stats, maximumDepth, callback) {
return getMetadata(baseDir, path, stats, maximumDepth).then(function (metadata) {
function getMetadata(baseDir, path, stats, maximumDepth, callback) {
getMetadataRecursive(baseDir, path, stats, maximumDepth).then(function (metadata) {
callback(null, metadata)
}, callback)
}
module.exports = {
get: generateMetadata
get: getMetadata
}

View File

@ -1,7 +1,7 @@
const fs = require('fs')
const path = require('path')
const qs = require('querystring')
const etag = require('etag')
const Metadata = require('./MetadataUtils')
const { generateDirectoryIndexHTML } = require('./IndexUtils')
const { getContentType } = require('./FileUtils')
@ -10,11 +10,6 @@ const { getContentType } = require('./FileUtils')
*/
const AutoIndex = !process.env.DISABLE_INDEX
/**
* Maximum recursion depth for ?meta listings.
*/
const MaximumDepth = 128
function sendFile(res, file, stats) {
let contentType = getContentType(file)
@ -41,20 +36,12 @@ function sendFile(res, file, stats) {
* Send the file, JSON metadata, or HTML directory listing.
*/
function serveFile(req, res, next) {
// TODO: remove support for "json" query param
if (req.query.meta != null || req.query.json != null) {
Metadata.get(req.packageDir, req.file, req.stats, MaximumDepth, function (error, metadata) {
if (error) {
console.error(error)
res.status(500).type('text').send(`Cannot generate JSON metadata for ${req.packageSpec}${req.filename}`)
} else {
// Cache metadata for 1 year.
res.set({
'Cache-Control': 'public, max-age=31536000',
'Cache-Tag': 'meta'
}).send(metadata)
}
})
// Preserve support for ?meta and ?json for backwards compat.
delete req.query.meta
delete req.query.json
const search = qs.stringify(req.query)
res.status(301).redirect(`/_meta${req.pathname}${search}`)
} else if (req.stats.isFile()) {
// Cache files for 1 year.
res.set({

View File

@ -0,0 +1,23 @@
const Metadata = require('./MetadataUtils')
/**
* Maximum recursion depth for ?meta listings.
*/
const MaximumDepth = 128
function serveMetadata(req, res) {
Metadata.get(req.packageDir, req.file, req.stats, MaximumDepth, function (error, metadata) {
if (error) {
console.error(error)
res.status(500).type('text').send(`Cannot generate metadata for ${req.packageSpec}${req.filename}`)
} else {
// Cache metadata for 1 year.
res.set({
'Cache-Control': 'public, max-age=31536000',
'Cache-Tag': 'meta'
}).send(metadata)
}
})
}
module.exports = serveMetadata

View File

@ -4929,6 +4929,10 @@ sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
sri-toolbox@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/sri-toolbox/-/sri-toolbox-0.2.0.tgz#a7fea5c3fde55e675cf1c8c06f3ebb5c2935835e"
sshpk@^1.7.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c"