diff --git a/src/ContentMessages.js b/src/ContentMessages.js index fd18b22d30..d20266de50 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -149,7 +149,19 @@ class ContentMessages { dis.dispatch({action: 'upload_progress', upload: upload}); } }).then(function(url) { - content.url = url; + if (encryptInfo === null) { + // If the attachment isn't encrypted then include the URL directly. + content.url = url; + } else { + // If the attachment is encrypted then bundle the URL along + // with the information needed to decrypt the attachment and + // add it under a file key. + encryptInfo.url = url; + if (file.type) { + encryptInfo.mimetype = file.type; + } + content.file = encryptInfo; + } return matrixClient.sendMessage(roomId, content); }, function(err) { error = err; diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 526fc6a3a5..0e4be9a83f 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -33,11 +33,18 @@ module.exports = React.createClass({ mxEvent: React.PropTypes.object.isRequired, }, + getInitialState: function() { + return { + decryptedUrl: null, + }; + }, + + onClick: function onClick(ev) { if (ev.button == 0 && !ev.metaKey) { ev.preventDefault(); var content = this.props.mxEvent.getContent(); - var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(content.url); + var httpUrl = this._getContentUrl(); var ImageView = sdk.getComponent("elements.ImageView"); var params = { src: httpUrl, @@ -77,18 +84,68 @@ module.exports = React.createClass({ imgElement.src = this._getThumbUrl(); }, + _getContentUrl: function() { + var content = this.props.mxEvent.getContent(); + if (content.file !== undefined) { + return this.state.decryptedUrl; + } else { + return MatrixClientPeg.get().mxcUrlToHttp(content.url); + } + }, + _getThumbUrl: function() { var content = this.props.mxEvent.getContent(); - return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600); + if (content.file !== undefined) { + // TODO: Decrypt and use the thumbnail file if one is present. + return this.state.decryptedUrl; + } else { + return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600); + } }, componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); this.fixupHeight(); + var content = this.props.mxEvent.getContent(); + var self = this; + if (content.file !== undefined && this.state.decryptedUrl === null) { + // TODO: hook up an error handler to the promise. + this.decryptFile(content.file).catch(function (err) { + console.warn("Unable to decrypt attachment: ", err) + // Set a placeholder image when we can't decrypt the image. + self.refs.image.src = "img/warning.svg"; + }); + } + }, + + decryptFile: function(file) { + var url = MatrixClientPeg.get().mxcUrlToHttp(file.url); + var self = this; + // Download the encrypted file as an array buffer. + return fetch(url).then(function (response) { + return response.arrayBuffer(); + }).then(function (responseData) { + // Decrypt the array buffer using the information taken from + // the event content. + return encrypt.decryptAttachment(responseData, file); + }).then(function(dataArray) { + // Turn the array into a Blob and use createObjectUrl to make + // a url that we can use as an img src. + var blob = new Blob([dataArray], {type: file.mimetype}); + if (!self._unmounted) { + self.setState({ + decryptedUrl: window.URL.createObjectURL(blob), + }); + } + }); }, componentWillUnmount: function() { dis.unregister(this.dispatcherRef); + this._unmounted = true; + if (this.state.decryptedUrl) { + window.URL.revokeObjectURL(this.state.decryptedUrl); + } }, onAction: function(payload) { @@ -123,11 +180,27 @@ module.exports = React.createClass({ var content = this.props.mxEvent.getContent(); var cli = MatrixClientPeg.get(); + if (content.file !== undefined && this.state.decryptedUrl === null) { + + // Need to decrypt the attachment + // The attachment is decrypted in componentDidMount. + // For now add an img tag with a spinner. + return ( + + + + ); + } + + var contentUrl = this._getContentUrl(); + var thumbUrl = this._getThumbUrl(); + var download; if (this.props.tileShape === "file_grid") { download = (