diff --git a/package.json b/package.json index cb4d2c313c..d77b32db1e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "atob": "2.0.3", "aws-sdk": "2.40.0", "btoa": "1.1.2", + "content-disposition": "0.5.2", "convict": "3.0.0", "cookies": "0.7.0", "core-js": "2.4.1", diff --git a/server/src/pages/shot/model.js b/server/src/pages/shot/model.js index c29702583a..03147873f4 100644 --- a/server/src/pages/shot/model.js +++ b/server/src/pages/shot/model.js @@ -1,9 +1,15 @@ +const { createDownloadUrl } = require("../../proxy-url"); const { getGitRevision } = require("../../linker"); const MobileDetect = require('mobile-detect'); exports.createModel = function(req) { let buildTime = require("../../build-time").string; let isMobile = !!(new MobileDetect(req.headers['user-agent'])).mobile(); + let downloadUrl = null; + let clip = req.shot.getClip(req.shot.clipNames()[0]); + if (clip) { + downloadUrl = createDownloadUrl(clip.image.url, req.shot.filename); + } let serverPayload = { title: req.shot.title, staticLink: req.staticLink, @@ -28,6 +34,7 @@ exports.createModel = function(req) { cspNonce: req.cspNonce, hashAnalytics: true, userAgent: req.headers['user-agent'], + downloadUrl, isMobile }; let clientPayload = { @@ -54,6 +61,7 @@ exports.createModel = function(req) { defaultExpiration: req.config.defaultExpiration * 1000, hashAnalytics: true, userAgent: req.headers['user-agent'], + downloadUrl, isMobile }; if (serverPayload.expireTime !== null && Date.now() > serverPayload.expireTime) { diff --git a/server/src/pages/shot/view.js b/server/src/pages/shot/view.js index 3d50dfbc93..09e8c46ad0 100644 --- a/server/src/pages/shot/view.js +++ b/server/src/pages/shot/view.js @@ -301,7 +301,6 @@ class Body extends React.Component { let clip = this.props.shot.getClip(clipId); clipUrl = clip.image.url; } - let clipFilename = this.props.shot.filename; let renderGetFirefox = this.props.userAgent && (this.props.userAgent + "").search(/firefox\/\d+/i) === -1; let renderExtensionNotification = !(this.props.isExtInstalled || renderGetFirefox); @@ -327,8 +326,8 @@ class Body extends React.Component {
{ trashOrFlagButton } - + diff --git a/server/src/proxy-url.js b/server/src/proxy-url.js index 5fbedc2394..f5f0f8d894 100644 --- a/server/src/proxy-url.js +++ b/server/src/proxy-url.js @@ -10,3 +10,8 @@ exports.createProxyUrl = function(req, url, hash) { } return proxy; }; + +exports.createDownloadUrl = function(url, filename) { + const sig = dbschema.getKeygrip().sign(new Buffer(filename, 'utf8')); + return `${url}?download=${encodeURIComponent(filename)}&sig=${encodeURIComponent(sig)}`; +}; diff --git a/server/src/server.js b/server/src/server.js index c66df2dcda..3dc8b0fcac 100644 --- a/server/src/server.js +++ b/server/src/server.js @@ -59,6 +59,7 @@ const { const dbschema = require("./dbschema"); const express = require("express"); const bodyParser = require('body-parser'); +const contentDisposition = require("content-disposition"); const csrf = require("csurf"); const morgan = require("morgan"); const linker = require("./linker"); @@ -771,6 +772,8 @@ app.post("/api/set-expiration", csrfProtection, function(req, res) { app.get("/images/:imageid", function(req, res) { let embedded = req.query.embedded; + let download = req.query.download; + let sig = req.query.sig; Shot.getRawBytesForClip( req.params.imageid ).then((obj) => { @@ -805,6 +808,11 @@ app.get("/images/:imageid", function(req, res) { }).send(); } res.header("Content-Type", "image/png"); + if (download) { + if (dbschema.getKeygrip().verify(new Buffer(download, 'utf8'), sig)) { + res.header("Content-Disposition", contentDisposition(download)); + } + } res.status(200); res.send(obj.data); }