2015-06-26 20:53:37 +02:00
|
|
|
var path = require('path')
|
|
|
|
var multicb = require('multicb')
|
|
|
|
var toPath = require('multiblob/util').toPath
|
|
|
|
var createHash = require('multiblob/util').createHash
|
|
|
|
var pull = require('pull-stream')
|
|
|
|
var toPull = require('stream-to-pull-stream')
|
2015-06-25 19:58:41 +02:00
|
|
|
var querystring = require('querystring')
|
2015-06-26 20:53:37 +02:00
|
|
|
var fs = require('fs')
|
2015-08-23 01:24:44 +02:00
|
|
|
var URL = require('url')
|
2015-06-25 19:58:41 +02:00
|
|
|
|
2015-08-04 02:54:30 +02:00
|
|
|
module.exports = function (sbot, config) {
|
2015-08-13 23:51:03 +02:00
|
|
|
var fallback_img_path = path.join(__dirname, '../../node_modules/ssb-patchwork-ui/img/default-prof-pic.png')
|
|
|
|
var fallback_video_path = path.join(__dirname, '../../node_modules/ssb-patchwork-ui/img/spinner.webm')
|
2015-07-04 20:51:33 +02:00
|
|
|
var nowaitOpts = { nowait: true }, id = function(){}
|
2015-06-26 20:53:37 +02:00
|
|
|
|
2015-06-25 19:58:41 +02:00
|
|
|
return {
|
|
|
|
// copy file from blobs into given dir with nice name
|
|
|
|
checkout: function (url, cb) {
|
|
|
|
var parsed = url_parse(url)
|
|
|
|
if (!parsed)
|
|
|
|
return cb({ badUrl: true })
|
|
|
|
|
|
|
|
var filename = parsed.qs.name || parsed.qs.filename || parsed.hash
|
|
|
|
|
|
|
|
// check if we have the blob, at the same time find an available filename
|
2015-06-25 20:42:10 +02:00
|
|
|
var done = multicb()
|
2015-08-04 02:54:30 +02:00
|
|
|
fs.stat(toPath(config.blobs_dir, parsed.hash), done())
|
2015-06-25 20:42:10 +02:00
|
|
|
findCheckoutDst(filename, parsed.hash, done())
|
2015-06-25 19:58:41 +02:00
|
|
|
done(function (err, res) {
|
2015-06-26 20:30:34 +02:00
|
|
|
if (err && err.code == 'ENOENT')
|
2015-06-25 19:58:41 +02:00
|
|
|
return cb({ notFound: true })
|
|
|
|
|
2015-06-25 20:42:10 +02:00
|
|
|
// do we need to copy?
|
|
|
|
var dst = res[1][1]
|
|
|
|
var nocopy = res[1][2]
|
|
|
|
if (nocopy)
|
|
|
|
return cb(null, dst)
|
|
|
|
|
2015-06-25 19:58:41 +02:00
|
|
|
// copy the file
|
2015-08-04 02:54:30 +02:00
|
|
|
var src = toPath(config.blobs_dir, parsed.hash)
|
2015-06-25 19:58:41 +02:00
|
|
|
var read = fs.createReadStream(src)
|
|
|
|
var write = fs.createWriteStream(dst)
|
|
|
|
read.on('error', done)
|
|
|
|
write.on('error', done)
|
|
|
|
write.on('close', done)
|
|
|
|
read.pipe(write)
|
|
|
|
function done (err) {
|
|
|
|
cb && cb(err, dst)
|
|
|
|
cb = null
|
|
|
|
}
|
|
|
|
})
|
2015-07-19 18:42:19 +02:00
|
|
|
},
|
|
|
|
|
2015-07-21 18:05:17 +02:00
|
|
|
server: function (opts) {
|
|
|
|
opts = opts || {}
|
|
|
|
return function (req, res) {
|
|
|
|
// local-host only
|
|
|
|
if (req.socket.remoteAddress != '127.0.0.1' &&
|
|
|
|
req.socket.remoteAddress != '::ffff:127.0.0.1' &&
|
|
|
|
req.socket.remoteAddress != '::1') {
|
|
|
|
console.log('Remote access attempted by', req.socket.remoteAddress)
|
|
|
|
res.writeHead(403)
|
|
|
|
return res.end('Remote access forbidden')
|
|
|
|
}
|
2015-07-19 18:42:19 +02:00
|
|
|
|
2015-07-21 18:05:17 +02:00
|
|
|
// restrict the CSP
|
|
|
|
res.setHeader('Content-Security-Policy',
|
|
|
|
"default-src 'self' 'unsafe-inline' 'unsafe-eval' data:; "+
|
|
|
|
"connect-src 'self'; "+
|
|
|
|
"object-src 'none'; "+
|
|
|
|
"frame-src 'none'; "+
|
2015-07-21 19:42:40 +02:00
|
|
|
"sandbox allow-scripts"
|
2015-07-21 18:05:17 +02:00
|
|
|
)
|
2015-07-19 18:42:19 +02:00
|
|
|
|
2015-08-23 01:24:44 +02:00
|
|
|
// local files
|
|
|
|
if (req.url.charAt(1) != '&' && req.url.charAt(1) != '@') {
|
|
|
|
if (!opts.serveFiles) {
|
|
|
|
res.writeHead(404)
|
|
|
|
res.end('File not found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-07-21 18:05:17 +02:00
|
|
|
return fs.createReadStream(req.url)
|
|
|
|
.on('error', function () {
|
|
|
|
res.writeHead(404)
|
|
|
|
res.end('File not found')
|
|
|
|
})
|
|
|
|
.pipe(res)
|
2015-07-19 18:42:19 +02:00
|
|
|
}
|
2015-07-21 18:05:17 +02:00
|
|
|
|
2015-08-23 01:24:44 +02:00
|
|
|
// blobs
|
|
|
|
// var parsed = url_parse(req.url)
|
|
|
|
var parsed = URL.parse(req.url, true)
|
|
|
|
if (req.url.charAt(1) == '&')
|
|
|
|
serveblob(parsed.pathname.slice(1), parsed.query.fallback, res)
|
|
|
|
else {
|
|
|
|
sbot.patchwork.getSiteLink(parsed.pathname.slice(1), function (err, link) {
|
|
|
|
if (err) {
|
|
|
|
res.writeHead(404)
|
|
|
|
res.end('File not found')
|
|
|
|
} else {
|
|
|
|
if (link.type)
|
|
|
|
res.setHeader('Content-Type', link.type)
|
|
|
|
serveblob(link.link, null, res)
|
2015-08-10 20:09:53 +02:00
|
|
|
}
|
2015-08-23 01:24:44 +02:00
|
|
|
})
|
|
|
|
}
|
2015-07-21 18:05:17 +02:00
|
|
|
}
|
2015-06-25 19:58:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-23 01:24:44 +02:00
|
|
|
function serveblob (hash, fallback, res) {
|
|
|
|
sbot.blobs.has(hash, function(err, has) {
|
|
|
|
if (!has) {
|
|
|
|
sbot.blobs.want(hash, nowaitOpts, id)
|
|
|
|
if (fallback) {
|
|
|
|
var p = (fallback == 'video') ? fallback_video_path : fallback_img_path
|
|
|
|
return fs.createReadStream(p)
|
|
|
|
.on('error', function () {
|
|
|
|
res.writeHead(404)
|
|
|
|
res.end('File not found')
|
|
|
|
})
|
|
|
|
.pipe(res)
|
|
|
|
}
|
|
|
|
res.writeHead(404)
|
|
|
|
res.end('File not found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pull(
|
|
|
|
sbot.blobs.get(hash),
|
|
|
|
toPull(res)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-06-25 19:58:41 +02:00
|
|
|
// helper to create a filename in checkout_dir that isnt already in use
|
2015-06-25 20:42:10 +02:00
|
|
|
// - cb(err, filepath, nocopy) - if nocopy==true, no need to do the copy operation
|
|
|
|
function findCheckoutDst (filename, hash, cb) {
|
2015-06-25 19:58:41 +02:00
|
|
|
var n = 1
|
|
|
|
var parsed = path.parse(filename)
|
|
|
|
next()
|
|
|
|
|
|
|
|
function gen () {
|
|
|
|
var name = parsed.name
|
|
|
|
if (n !== 1) name += ' ('+n+')'
|
|
|
|
name += parsed.ext
|
|
|
|
n++
|
2015-08-04 02:54:30 +02:00
|
|
|
return path.join(config.checkout_dir, name)
|
2015-06-25 19:58:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function next () {
|
2015-06-25 20:42:10 +02:00
|
|
|
var dst = gen()
|
|
|
|
// exists?
|
|
|
|
fs.stat(dst, function (err, stat) {
|
2015-06-25 19:58:41 +02:00
|
|
|
if (!stat)
|
2015-06-25 20:42:10 +02:00
|
|
|
return cb(null, dst, false)
|
|
|
|
|
|
|
|
// yes, check its hash
|
2015-07-10 20:57:20 +02:00
|
|
|
var hasher = createHash('sha256')
|
2015-06-25 20:42:10 +02:00
|
|
|
pull(
|
|
|
|
toPull.source(fs.createReadStream(dst)),
|
|
|
|
hasher,
|
|
|
|
pull.onEnd(function () {
|
|
|
|
// if the hash matches, we're set
|
|
|
|
if (hasher.digest == hash)
|
|
|
|
return cb(null, dst, true)
|
|
|
|
// try next
|
|
|
|
next()
|
|
|
|
})
|
|
|
|
)
|
2015-06-25 19:58:41 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2015-06-26 20:30:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// blob url parser
|
2015-08-10 20:09:53 +02:00
|
|
|
// var re = /^pwblob:&([a-z0-9\+\/=]+\.(?:sha256|blake2s))\??(.*)$/i
|
|
|
|
// var url_parse =
|
|
|
|
// module.exports.url_parse = function (str) {
|
|
|
|
// var parts = re.exec(str)
|
|
|
|
// if (parts)
|
|
|
|
// return { hash: parts[1], qs: querystring.parse(parts[2]) }
|
|
|
|
// }
|
|
|
|
var re = /^(?:http:\/\/localhost:7777)?\/&([a-z0-9\+\/=]+\.(?:sha256|blake2s))\??(.*)$/i
|
2015-06-26 20:30:34 +02:00
|
|
|
var url_parse =
|
|
|
|
module.exports.url_parse = function (str) {
|
|
|
|
var parts = re.exec(str)
|
|
|
|
if (parts)
|
2015-08-23 01:24:44 +02:00
|
|
|
return { path: parts[1], qs: querystring.parse(parts[2]) }
|
2015-06-25 19:58:41 +02:00
|
|
|
}
|