mirror of https://github.com/vector-im/riot-web
				
				
				
			Merge remote-tracking branch 'origin/master' into develop
						commit
						23e764604c
					
				| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +53,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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
            }
 | 
			
		||||
            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;
 | 
			
		||||
            }
 | 
			
		||||
            return matrixClient.sendMessage(roomId, content);
 | 
			
		||||
        }, function(err) {
 | 
			
		||||
            error = err;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
                <span className="mx_MAudioBody">
 | 
			
		||||
                <img src="img/spinner.gif" ref="image"
 | 
			
		||||
                    alt={content.body} />
 | 
			
		||||
                </span>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const contentUrl = this._getContentUrl();
 | 
			
		||||
        var content = this.props.mxEvent.getContent();
 | 
			
		||||
        var cli = MatrixClientPeg.get();
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <span className="mx_MAudioBody">
 | 
			
		||||
                <audio src={contentUrl} controls />
 | 
			
		||||
                <MFileBody {...this.props} decryptedUrl={this.state.decryptedUrl} />
 | 
			
		||||
                <audio src={cli.mxcUrlToHttp(content.url)} controls />
 | 
			
		||||
                <MFileBody {...this.props} />
 | 
			
		||||
            </span>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
                <span className="mx_MFileBody" ref="body">
 | 
			
		||||
                <img src="img/spinner.gif" ref="image"
 | 
			
		||||
                    alt={content.body} />
 | 
			
		||||
                </span>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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 (
 | 
			
		||||
                    <span className="mx_MFileBody">
 | 
			
		||||
                        <div className="mx_MImageBody_download">
 | 
			
		||||
                            <a className="mx_ImageBody_downloadLink" href={contentUrl} target="_blank" rel="noopener" download={downloadAttr}>
 | 
			
		||||
                                { fileName }
 | 
			
		||||
                            <a className="mx_ImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
 | 
			
		||||
                                { content.body && content.body.length > 0 ? content.body : "Attachment" }
 | 
			
		||||
                            </a>
 | 
			
		||||
                            <div className="mx_MImageBody_size">
 | 
			
		||||
                                { content.info && content.info.size ? filesize(content.info.size) : "" }
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +75,7 @@ module.exports = React.createClass({
 | 
			
		|||
                return (
 | 
			
		||||
                    <span className="mx_MFileBody">
 | 
			
		||||
                        <div className="mx_MImageBody_download">
 | 
			
		||||
                            <a href={contentUrl} target="_blank" rel="noopener" download={downloadAttr}>
 | 
			
		||||
                            <a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
 | 
			
		||||
                                <TintableSvg src="img/download.svg" width="12" height="14"/>
 | 
			
		||||
                                Download {text}
 | 
			
		||||
                            </a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
        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 (
 | 
			
		||||
                <span className="mx_MImageBody" ref="body">
 | 
			
		||||
                <img className="mx_MImageBody_thumbnail" src="img/spinner.gif" ref="image"
 | 
			
		||||
                    alt={content.body} />
 | 
			
		||||
                </span>
 | 
			
		||||
        var download;
 | 
			
		||||
        if (this.props.tileShape === "file_grid") {
 | 
			
		||||
            download = (
 | 
			
		||||
                <div className="mx_MImageBody_download">
 | 
			
		||||
                    <a className="mx_MImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
 | 
			
		||||
                        {content.body}
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <div className="mx_MImageBody_size">
 | 
			
		||||
                        { content.info && content.info.size ? filesize(content.info.size) : "" }
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            download = (
 | 
			
		||||
                <div className="mx_MImageBody_download">
 | 
			
		||||
                    <a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
 | 
			
		||||
                        <TintableSvg src="img/download.svg" width="12" height="14"/>
 | 
			
		||||
                        Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const contentUrl = this._getContentUrl();
 | 
			
		||||
        const thumbUrl = this._getThumbUrl();
 | 
			
		||||
 | 
			
		||||
        var thumbUrl = this._getThumbUrl();
 | 
			
		||||
        if (thumbUrl) {
 | 
			
		||||
            return (
 | 
			
		||||
                <span className="mx_MImageBody" ref="body">
 | 
			
		||||
                    <a href={contentUrl} onClick={ this.onClick }>
 | 
			
		||||
                    <a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }>
 | 
			
		||||
                        <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
 | 
			
		||||
                            alt={content.body}
 | 
			
		||||
                            onMouseEnter={this.onImageEnter}
 | 
			
		||||
                            onMouseLeave={this.onImageLeave} />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <MFileBody {...this.props} decryptedUrl={this.state.decryptedUrl} />
 | 
			
		||||
                    { download }
 | 
			
		||||
                </span>
 | 
			
		||||
            );
 | 
			
		||||
        } else if (content.body) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
                <span className="mx_MImageBody" ref="body">
 | 
			
		||||
                <img className="mx_MImageBody_thumbnail" src="img/spinner.gif" ref="image"
 | 
			
		||||
                    alt={content.body} />
 | 
			
		||||
                </span>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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 = (
 | 
			
		||||
                <div className="mx_MImageBody_download">
 | 
			
		||||
                    <a className="mx_MImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
 | 
			
		||||
                        {content.body}
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <div className="mx_MImageBody_size">
 | 
			
		||||
                        { content.info && content.info.size ? filesize(content.info.size) : "" }
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            var TintableSvg = sdk.getComponent("elements.TintableSvg");
 | 
			
		||||
            download = (
 | 
			
		||||
                <div className="mx_MImageBody_download">
 | 
			
		||||
                    <a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
 | 
			
		||||
                        <TintableSvg src="img/download.svg" width="12" height="14"/>
 | 
			
		||||
                        Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <span className="mx_MVideoBody">
 | 
			
		||||
                <video className="mx_MVideoBody" src={contentUrl} alt={content.body}
 | 
			
		||||
                <video className="mx_MVideoBody" src={cli.mxcUrlToHttp(content.url)} alt={content.body}
 | 
			
		||||
                    controls preload={preload} autoPlay={false}
 | 
			
		||||
                    height={height} width={width} poster={poster}>
 | 
			
		||||
                </video>
 | 
			
		||||
                <MFileBody {...this.props} decryptedUrl={this.state.decryptedUrl} />
 | 
			
		||||
                { download }
 | 
			
		||||
            </span>
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue