diff --git a/package.json b/package.json index 7879363451..a7f65919a5 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ }, "dependencies": { "babel-runtime": "^6.11.6", - "browser-encrypt-attachment": "0.0.0", "browser-request": "^0.3.3", "classnames": "^2.1.2", "draft-js": "^0.8.1", @@ -55,7 +54,6 @@ "fuse.js": "^2.2.0", "glob": "^5.0.14", "highlight.js": "^8.9.1", - "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.3", "lodash": "^4.13.1", "marked": "^0.3.5", diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 28c28e875e..fd18b22d30 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -23,8 +23,6 @@ var MatrixClientPeg = require('./MatrixClientPeg'); var sdk = require('./index'); var Modal = require('./Modal'); -var encrypt = require("browser-encrypt-attachment"); - function infoForImageFile(imageFile) { var deferred = q.defer(); @@ -83,24 +81,6 @@ function infoForVideoFile(videoFile) { return deferred.promise; } -/** - * Read the file as an ArrayBuffer. - * @return {Promise} A promise that resolves with an ArrayBuffer when the file - * is read. - */ -function readFileAsArrayBuffer(file) { - const deferred = q.defer(); - const reader = new FileReader(); - reader.onload = function(e) { - deferred.resolve(e.target.result); - }; - reader.onerror = function(e) { - deferred.reject(e); - }; - reader.readAsArrayBuffer(file); - return deferred.promise; -} - class ContentMessages { constructor() { @@ -157,26 +137,10 @@ class ContentMessages { this.inprogress.push(upload); dis.dispatch({action: 'upload_started'}); - var encryptInfo = null; var error; var self = this; return def.promise.then(function() { - if (matrixClient.isRoomEncrypted(roomId)) { - // If the room is encrypted then encrypt the file before uploading it. - // First read the file into memory. - upload.promise = readFileAsArrayBuffer(file).then(function(data) { - // Then encrypt the file. - return encrypt.encryptAttachment(data); - }).then(function(encryptResult) { - // Record the information needed to decrypt the attachment. - encryptInfo = encryptResult.info; - // Pass the encrypted data as a Blob to the uploader. - var blob = new Blob([encryptResult.data]); - return matrixClient.uploadContent(blob); - }); - } else { - upload.promise = matrixClient.uploadContent(file); - } + upload.promise = matrixClient.uploadContent(file); return upload.promise; }).progress(function(ev) { if (ev) { @@ -185,19 +149,7 @@ class ContentMessages { dis.dispatch({action: 'upload_progress', upload: upload}); } }).then(function(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; - } + content.url = url; return matrixClient.sendMessage(roomId, content); }, function(err) { error = err; diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index ff753621c7..6113fa7c6c 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -21,67 +21,29 @@ import MFileBody from './MFileBody'; import MatrixClientPeg from '../../../MatrixClientPeg'; import sdk from '../../../index'; -import { decryptFile } from '../../../utils/DecryptFile'; export default class MAudioBody extends React.Component { constructor(props) { super(props); this.state = { - playing: false, - decryptedUrl: null, + playing: false } } + onPlayToggle() { this.setState({ playing: !this.state.playing }); } - _getContentUrl() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined) { - return this.state.decryptedUrl; - } else { - return MatrixClientPeg.get().mxcUrlToHttp(content.url); - } - } - - componentDidMount() { - var content = this.props.mxEvent.getContent(); - if (content.file !== undefined && this.state.decryptedUrl === null) { - decryptFile(content.file).done((url) => { - this.setState({ - decryptedUrl: url - }); - }, (err) => { - console.warn("Unable to decrypt attachment: ", err) - // Set a placeholder image when we can't decrypt the image. - this.refs.image.src = "img/warning.svg"; - }); - } - } - render() { - const content = this.props.mxEvent.getContent(); - - 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 ( - - {content.body} - - ); - } - - const contentUrl = this._getContentUrl(); + var content = this.props.mxEvent.getContent(); + var cli = MatrixClientPeg.get(); return ( - ); } diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index e8c97e5f44..c37cd32c4e 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -16,22 +16,15 @@ limitations under the License. 'use strict'; -import React from 'react'; -import filesize from 'filesize'; -import MatrixClientPeg from '../../../MatrixClientPeg'; -import sdk from '../../../index'; -import {decryptFile} from '../../../utils/DecryptFile'; - +var React = require('react'); +var filesize = require('filesize'); +var MatrixClientPeg = require('../../../MatrixClientPeg'); +var sdk = require('../../../index'); +var dis = require("../../../dispatcher"); module.exports = React.createClass({ displayName: 'MFileBody', - getInitialState: function() { - return { - decryptedUrl: (this.props.decryptedUrl ? this.props.decryptedUrl : null), - }; - }, - presentableTextForFile: function(content) { var linkText = 'Attachment'; if (content.body && content.body.length > 0) { @@ -54,88 +47,22 @@ module.exports = React.createClass({ return linkText; }, - _getContentUrl: function() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined) { - return this.state.decryptedUrl; - } else { - return MatrixClientPeg.get().mxcUrlToHttp(content.url); - } - }, - - componentDidMount: function() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined && this.state.decryptedUrl === null) { - decryptFile(content.file).done((url) => { - this.setState({ - decryptedUrl: url, - }); - }, (err) => { - console.warn("Unable to decrypt attachment: ", err) - // Set a placeholder image when we can't decrypt the image. - this.refs.image.src = "img/warning.svg"; - }); - } - }, - render: function() { - const content = this.props.mxEvent.getContent(); + var content = this.props.mxEvent.getContent(); + var cli = MatrixClientPeg.get(); - const text = this.presentableTextForFile(content); + var httpUrl = cli.mxcUrlToHttp(content.url); + var text = this.presentableTextForFile(content); var TintableSvg = sdk.getComponent("elements.TintableSvg"); - 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 ( - - {content.body} - - ); - } - - const contentUrl = this._getContentUrl(); - - const 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 (httpUrl) { if (this.props.tileShape === "file_grid") { return (
- - { fileName } + + { content.body && content.body.length > 0 ? content.body : "Attachment" }
{ content.info && content.info.size ? filesize(content.info.size) : "" } @@ -148,7 +75,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 4a3cfce591..526fc6a3a5 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -16,15 +16,14 @@ limitations under the License. 'use strict'; -import React from 'react'; -import MFileBody from './MFileBody'; -import MatrixClientPeg from '../../../MatrixClientPeg'; -import ImageUtils from '../../../ImageUtils'; -import Modal from '../../../Modal'; -import sdk from '../../../index'; -import dis from '../../../dispatcher'; -import {decryptFile} from '../../../utils/DecryptFile'; +var React = require('react'); +var filesize = require('filesize'); +var MatrixClientPeg = require('../../../MatrixClientPeg'); +var ImageUtils = require('../../../ImageUtils'); +var Modal = require('../../../Modal'); +var sdk = require('../../../index'); +var dis = require("../../../dispatcher"); module.exports = React.createClass({ displayName: 'MImageBody', @@ -34,20 +33,13 @@ 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(); - const content = this.props.mxEvent.getContent(); - const httpUrl = this._getContentUrl(); - const ImageView = sdk.getComponent("elements.ImageView"); - const params = { + var content = this.props.mxEvent.getContent(); + var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(content.url); + var ImageView = sdk.getComponent("elements.ImageView"); + var params = { src: httpUrl, mxEvent: this.props.mxEvent }; @@ -63,7 +55,7 @@ module.exports = React.createClass({ }, _isGif: function() { - const content = this.props.mxEvent.getContent(); + var content = this.props.mxEvent.getContent(); return (content && content.info && content.info.mimetype === "image/gif"); }, @@ -72,7 +64,9 @@ module.exports = React.createClass({ return; } var imgElement = e.target; - imgElement.src = this._getContentUrl(); + imgElement.src = MatrixClientPeg.get().mxcUrlToHttp( + this.props.mxEvent.getContent().url + ); }, onImageLeave: function(e) { @@ -83,40 +77,14 @@ module.exports = React.createClass({ imgElement.src = this._getThumbUrl(); }, - _getContentUrl: function() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined) { - return this.state.decryptedUrl; - } else { - return MatrixClientPeg.get().mxcUrlToHttp(content.url); - } - }, - _getThumbUrl: function() { - const content = this.props.mxEvent.getContent(); - 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); - } + var content = this.props.mxEvent.getContent(); + return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600); }, componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); this.fixupHeight(); - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined && this.state.decryptedUrl === null) { - decryptFile(content.file).done((url) => { - this.setState({ - decryptedUrl: url, - }); - }, (err) => { - console.warn("Unable to decrypt attachment: ", err) - // Set a placeholder image when we can't decrypt the image. - this.refs.image.src = "img/warning.svg"; - }); - } }, componentWillUnmount: function() { @@ -135,13 +103,14 @@ module.exports = React.createClass({ return; } - const content = this.props.mxEvent.getContent(); - const timelineWidth = this.refs.body.offsetWidth; - const maxHeight = 600; // let images take up as much width as they can so long as the height doesn't exceed 600px. + var content = this.props.mxEvent.getContent(); + + var thumbHeight = null; + var timelineWidth = this.refs.body.offsetWidth; + var maxHeight = 600; // let images take up as much width as they can so long as the height doesn't exceed 600px. // the alternative here would be 600*timelineWidth/800; to scale them down to fit inside a 4:3 bounding box //console.log("trying to fit image into timelineWidth of " + this.refs.body.offsetWidth + " or " + this.refs.body.clientWidth); - var thumbHeight = null; if (content.info) { thumbHeight = ImageUtils.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight); } @@ -150,35 +119,45 @@ module.exports = React.createClass({ }, render: function() { - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - const content = this.props.mxEvent.getContent(); + var TintableSvg = sdk.getComponent("elements.TintableSvg"); + 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 ( - - {content.body} - + var download; + if (this.props.tileShape === "file_grid") { + download = ( +
+ + {content.body} + +
+ { content.info && content.info.size ? filesize(content.info.size) : "" } +
+
+ ); + } + else { + download = ( + ); } - const contentUrl = this._getContentUrl(); - const thumbUrl = this._getThumbUrl(); - + var thumbUrl = this._getThumbUrl(); if (thumbUrl) { return ( - + {content.body} - + { download } ); } else if (content.body) { diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index a32348ea1a..d29a3ea53e 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -16,24 +16,16 @@ limitations under the License. 'use strict'; -import React from 'react'; -import MFileBody from './MFileBody'; -import MatrixClientPeg from '../../../MatrixClientPeg'; -import Model from '../../../Modal'; -import sdk from '../../../index'; -import { decryptFile } from '../../../utils/DecryptFile'; -import q from 'q'; +var React = require('react'); +var filesize = require('filesize'); + +var MatrixClientPeg = require('../../../MatrixClientPeg'); +var Modal = require('../../../Modal'); +var sdk = require('../../../index'); module.exports = React.createClass({ displayName: 'MVideoBody', - getInitialState: function() { - return { - decryptedUrl: null, - decryptedThumbnailUrl: null, - }; - }, - thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { if (!fullWidth || !fullHeight) { // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even @@ -56,92 +48,59 @@ module.exports = React.createClass({ } }, - _getContentUrl: function() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined) { - return this.state.decryptedUrl; - } else { - return MatrixClientPeg.get().mxcUrlToHttp(content.url); - } - }, - - _getThumbUrl: function() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined) { - return this.state.decryptedThumbnailUrl; - } else if (content.info.thumbnail_url) { - return MatrixClientPeg.get().mxcUrlToHttp(content.info.thumbnail_url); - } else { - return null; - } - }, - - componentDidMount: function() { - const content = this.props.mxEvent.getContent(); - if (content.file !== undefined && this.state.decryptedUrl === null) { - var thumbnailPromise = q(null); - if (content.info.thumbnail_file) { - thumbnailPromise = decryptFile( - content.info.thumbnail_file - ); - } - thumbnailPromise.then((thumbnailUrl) => { - decryptFile(content.file).then((contentUrl) => { - this.setState({ - decryptedUrl: contentUrl, - decryptedThumbnailUrl: thumbnailUrl, - }); - }); - }).catch((err) => { - console.warn("Unable to decrypt attachment: ", err) - // Set a placeholder image when we can't decrypt the image. - this.refs.image.src = "img/warning.svg"; - }).done(); - } - }, - render: function() { - const content = this.props.mxEvent.getContent(); - - 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 ( - - {content.body} - - ); - } - - const contentUrl = this._getContentUrl(); - const thumbUrl = this._getThumbUrl(); + var content = this.props.mxEvent.getContent(); + var cli = MatrixClientPeg.get(); var height = null; var width = null; var poster = null; var preload = "metadata"; if (content.info) { - const scale = this.thumbScale(content.info.w, content.info.h, 480, 360); + var scale = this.thumbScale(content.info.w, content.info.h, 480, 360); if (scale) { width = Math.floor(content.info.w * scale); height = Math.floor(content.info.h * scale); } - if (thumbUrl) { - poster = thumbUrl; + if (content.info.thumbnail_url) { + poster = cli.mxcUrlToHttp(content.info.thumbnail_url); preload = "none"; } } + 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 } ); }, diff --git a/src/utils/DecryptFile.js b/src/utils/DecryptFile.js deleted file mode 100644 index 38eab1d073..0000000000 --- a/src/utils/DecryptFile.js +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Pull in the encryption lib so that we can decrypt attachments. -import encrypt from 'browser-encrypt-attachment'; -// Pull in a fetch polyfill so we can download encrypted attachments. -import 'isomorphic-fetch'; -// Grab the client so that we can turn mxc:// URLs into https:// URLS. -import MatrixClientPeg from '../MatrixClientPeg'; -import q from 'q'; - - -/** - * Read blob as a data:// URI. - * @return {Promise} A promise that resolves with the data:// URI. - */ -function readBlobAsDataUri(file) { - var deferred = q.defer(); - var reader = new FileReader(); - reader.onload = function(e) { - deferred.resolve(e.target.result); - }; - reader.onerror = function(e) { - deferred.reject(e); - }; - reader.readAsDataURL(file); - return deferred.promise; -} - - -/** - * Decrypt a file attached to a matrix event. - * @param file {Object} The json taken from the matrix event. - * This passed to [link]{@link https://github.com/matrix-org/browser-encrypt-attachments} - * as the encryption info object, so will also have the those keys in addition to - * the keys below. - * @param file.url {string} An mxc:// URL for the encrypted file. - * @param file.mimetype {string} The MIME-type of the plaintext file. - */ -export function decryptFile(file) { - const url = MatrixClientPeg.get().mxcUrlToHttp(file.url); - // Download the encrypted file as an array buffer. - return q(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 give it the correct MIME-type. - var blob = new Blob([dataArray], {type: file.mimetype}); - return readBlobAsDataUri(blob); - }); -}