diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 531c382d9a..313577054a 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -100,13 +100,43 @@ module.exports = React.createClass({ var contentUrl = this._getContentUrl(); + var fileName = content.body && content.body.length > 0 ? content.body : "Attachment"; + + var downloadAttr = undefined; + if (this.state.decryptedUrl) { + // If the file is encrypted then we MUST download it rather than displaying it + // because Firefox is vunerable to XSS attacks in data:// URLs + // and all browsers are vunerable to XSS attacks in blob: URLs + // created with window.URL.createObjectURL + // See https://bugzilla.mozilla.org/show_bug.cgi?id=255107 + // See https://w3c.github.io/FileAPI/#originOfBlobURL + // + // This is not a problem for unencrypted links because they are + // either fetched from a different domain so are safe because of + // the same-origin policy or they are fetch from the same domain, + // in which case we trust that the homeserver will set a + // Content-Security-Policy that disables script execution. + // It is reasonable to trust the homeserver in that case since + // it is the same domain that controls this javascript. + // + // We can't apply the same workaround for encrypted files because + // we can't supply HTTP headers when the user clicks on a blob: + // or data:// uri. + // + // We should probably provide a download attribute anyway so that + // the file will have the correct name when the user tries to + // download it. We can't provide a Content-Disposition header + // like we would for HTTP. + downloadAttr = fileName; + } + if (contentUrl) { if (this.props.tileShape === "file_grid") { return (
- - { content.body && content.body.length > 0 ? content.body : "Attachment" } + + { fileName }
{ content.info && content.info.size ? filesize(content.info.size) : "" } @@ -119,7 +149,7 @@ module.exports = React.createClass({ return (
- + Download {text} diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index c804b44d3e..afafbedcb9 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -18,6 +18,7 @@ limitations under the License. var React = require('react'); var filesize = require('filesize'); +var MFileBody = require('./MFileBody'); var MatrixClientPeg = require('../../../MatrixClientPeg'); var ImageUtils = require('../../../ImageUtils'); var Modal = require('../../../Modal'); @@ -172,30 +173,6 @@ module.exports = React.createClass({ var contentUrl = this._getContentUrl(); var thumbUrl = this._getThumbUrl(); - var download; - if (this.props.tileShape === "file_grid") { - download = ( -
- - {content.body} - -
- { content.info && content.info.size ? filesize(content.info.size) : "" } -
-
- ); - } - else { - download = ( - - ); - } - if (thumbUrl) { return ( @@ -205,7 +182,7 @@ module.exports = React.createClass({ onMouseEnter={this.onImageEnter} onMouseLeave={this.onImageLeave} /> - { download } + ); } else if (content.body) { diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index 821f10be88..4744a95026 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -19,6 +19,7 @@ limitations under the License. var React = require('react'); var filesize = require('filesize'); +import MFileBody from './MFileBody'; var MatrixClientPeg = require('../../../MatrixClientPeg'); var Modal = require('../../../Modal'); @@ -140,38 +141,13 @@ module.exports = React.createClass({ } } - var download; - if (this.props.tileShape === "file_grid") { - download = ( -
- - {content.body} - -
- { content.info && content.info.size ? filesize(content.info.size) : "" } -
-
- ); - } - else { - var TintableSvg = sdk.getComponent("elements.TintableSvg"); - download = ( - - ); - } - return ( - { download } + ); },