Merge pull request #546 from matrix-org/revert-533-markjh/encrypted-attachments

Revert "Encrypt attachments in encrypted rooms,"
This commit is contained in:
Mark Haines 2016-11-11 12:00:43 +00:00 committed by GitHub
commit f6b3248c9d
7 changed files with 108 additions and 398 deletions

View file

@ -42,7 +42,6 @@
}, },
"dependencies": { "dependencies": {
"babel-runtime": "^6.11.6", "babel-runtime": "^6.11.6",
"browser-encrypt-attachment": "0.0.0",
"browser-request": "^0.3.3", "browser-request": "^0.3.3",
"classnames": "^2.1.2", "classnames": "^2.1.2",
"draft-js": "^0.8.1", "draft-js": "^0.8.1",
@ -55,7 +54,6 @@
"fuse.js": "^2.2.0", "fuse.js": "^2.2.0",
"glob": "^5.0.14", "glob": "^5.0.14",
"highlight.js": "^8.9.1", "highlight.js": "^8.9.1",
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3", "linkifyjs": "^2.1.3",
"lodash": "^4.13.1", "lodash": "^4.13.1",
"marked": "^0.3.5", "marked": "^0.3.5",

View file

@ -23,8 +23,6 @@ var MatrixClientPeg = require('./MatrixClientPeg');
var sdk = require('./index'); var sdk = require('./index');
var Modal = require('./Modal'); var Modal = require('./Modal');
var encrypt = require("browser-encrypt-attachment");
function infoForImageFile(imageFile) { function infoForImageFile(imageFile) {
var deferred = q.defer(); var deferred = q.defer();
@ -83,24 +81,6 @@ function infoForVideoFile(videoFile) {
return deferred.promise; 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 { class ContentMessages {
constructor() { constructor() {
@ -157,26 +137,10 @@ class ContentMessages {
this.inprogress.push(upload); this.inprogress.push(upload);
dis.dispatch({action: 'upload_started'}); dis.dispatch({action: 'upload_started'});
var encryptInfo = null;
var error; var error;
var self = this; var self = this;
return def.promise.then(function() { return def.promise.then(function() {
if (matrixClient.isRoomEncrypted(roomId)) { upload.promise = matrixClient.uploadContent(file);
// 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; return upload.promise;
}).progress(function(ev) { }).progress(function(ev) {
if (ev) { if (ev) {
@ -185,19 +149,7 @@ class ContentMessages {
dis.dispatch({action: 'upload_progress', upload: upload}); dis.dispatch({action: 'upload_progress', upload: upload});
} }
}).then(function(url) { }).then(function(url) {
if (encryptInfo === null) { content.url = url;
// 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); return matrixClient.sendMessage(roomId, content);
}, function(err) { }, function(err) {
error = err; error = err;

View file

@ -21,67 +21,29 @@ import MFileBody from './MFileBody';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index'; import sdk from '../../../index';
import { decryptFile } from '../../../utils/DecryptFile';
export default class MAudioBody extends React.Component { export default class MAudioBody extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
playing: false, playing: false
decryptedUrl: null,
} }
} }
onPlayToggle() { onPlayToggle() {
this.setState({ this.setState({
playing: !this.state.playing 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() { render() {
const content = this.props.mxEvent.getContent(); 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_MAudioBody">
<img src="img/spinner.gif" ref="image"
alt={content.body} />
</span>
);
}
const contentUrl = this._getContentUrl();
return ( return (
<span className="mx_MAudioBody"> <span className="mx_MAudioBody">
<audio src={contentUrl} controls /> <audio src={cli.mxcUrlToHttp(content.url)} controls />
<MFileBody {...this.props} decryptedUrl={this.state.decryptedUrl} /> <MFileBody {...this.props} />
</span> </span>
); );
} }

View file

@ -16,22 +16,15 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; var React = require('react');
import filesize from 'filesize'; var filesize = require('filesize');
import MatrixClientPeg from '../../../MatrixClientPeg'; var MatrixClientPeg = require('../../../MatrixClientPeg');
import sdk from '../../../index'; var sdk = require('../../../index');
import {decryptFile} from '../../../utils/DecryptFile'; var dis = require("../../../dispatcher");
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MFileBody', displayName: 'MFileBody',
getInitialState: function() {
return {
decryptedUrl: (this.props.decryptedUrl ? this.props.decryptedUrl : null),
};
},
presentableTextForFile: function(content) { presentableTextForFile: function(content) {
var linkText = 'Attachment'; var linkText = 'Attachment';
if (content.body && content.body.length > 0) { if (content.body && content.body.length > 0) {
@ -54,88 +47,22 @@ module.exports = React.createClass({
return linkText; 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() { 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"); var TintableSvg = sdk.getComponent("elements.TintableSvg");
if (content.file !== undefined && this.state.decryptedUrl === null) {
// Need to decrypt the attachment if (httpUrl) {
// 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 (this.props.tileShape === "file_grid") { if (this.props.tileShape === "file_grid") {
return ( return (
<span className="mx_MFileBody"> <span className="mx_MFileBody">
<div className="mx_MImageBody_download"> <div className="mx_MImageBody_download">
<a className="mx_ImageBody_downloadLink" href={contentUrl} target="_blank" rel="noopener" download={downloadAttr}> <a className="mx_ImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
{ fileName } { content.body && content.body.length > 0 ? content.body : "Attachment" }
</a> </a>
<div className="mx_MImageBody_size"> <div className="mx_MImageBody_size">
{ content.info && content.info.size ? filesize(content.info.size) : "" } { content.info && content.info.size ? filesize(content.info.size) : "" }
@ -148,7 +75,7 @@ module.exports = React.createClass({
return ( return (
<span className="mx_MFileBody"> <span className="mx_MFileBody">
<div className="mx_MImageBody_download"> <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"/> <TintableSvg src="img/download.svg" width="12" height="14"/>
Download {text} Download {text}
</a> </a>

View file

@ -16,15 +16,14 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; var React = require('react');
import MFileBody from './MFileBody'; var filesize = require('filesize');
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 MatrixClientPeg = require('../../../MatrixClientPeg');
var ImageUtils = require('../../../ImageUtils');
var Modal = require('../../../Modal');
var sdk = require('../../../index');
var dis = require("../../../dispatcher");
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MImageBody', displayName: 'MImageBody',
@ -34,20 +33,13 @@ module.exports = React.createClass({
mxEvent: React.PropTypes.object.isRequired, mxEvent: React.PropTypes.object.isRequired,
}, },
getInitialState: function() {
return {
decryptedUrl: null,
};
},
onClick: function onClick(ev) { onClick: function onClick(ev) {
if (ev.button == 0 && !ev.metaKey) { if (ev.button == 0 && !ev.metaKey) {
ev.preventDefault(); ev.preventDefault();
const content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
const httpUrl = this._getContentUrl(); var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(content.url);
const ImageView = sdk.getComponent("elements.ImageView"); var ImageView = sdk.getComponent("elements.ImageView");
const params = { var params = {
src: httpUrl, src: httpUrl,
mxEvent: this.props.mxEvent mxEvent: this.props.mxEvent
}; };
@ -63,7 +55,7 @@ module.exports = React.createClass({
}, },
_isGif: function() { _isGif: function() {
const content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
return (content && content.info && content.info.mimetype === "image/gif"); return (content && content.info && content.info.mimetype === "image/gif");
}, },
@ -72,7 +64,9 @@ module.exports = React.createClass({
return; return;
} }
var imgElement = e.target; var imgElement = e.target;
imgElement.src = this._getContentUrl(); imgElement.src = MatrixClientPeg.get().mxcUrlToHttp(
this.props.mxEvent.getContent().url
);
}, },
onImageLeave: function(e) { onImageLeave: function(e) {
@ -83,40 +77,14 @@ module.exports = React.createClass({
imgElement.src = this._getThumbUrl(); 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() { _getThumbUrl: function() {
const content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
if (content.file !== undefined) { return MatrixClientPeg.get().mxcUrlToHttp(content.url, 800, 600);
// 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() { componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this.fixupHeight(); 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() { componentWillUnmount: function() {
@ -135,13 +103,14 @@ module.exports = React.createClass({
return; return;
} }
const content = this.props.mxEvent.getContent(); var 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 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 // 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); //console.log("trying to fit image into timelineWidth of " + this.refs.body.offsetWidth + " or " + this.refs.body.clientWidth);
var thumbHeight = null;
if (content.info) { if (content.info) {
thumbHeight = ImageUtils.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight); thumbHeight = ImageUtils.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight);
} }
@ -150,35 +119,45 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); var TintableSvg = sdk.getComponent("elements.TintableSvg");
const content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
var cli = MatrixClientPeg.get();
if (content.file !== undefined && this.state.decryptedUrl === null) { var download;
if (this.props.tileShape === "file_grid") {
// Need to decrypt the attachment download = (
// The attachment is decrypted in componentDidMount. <div className="mx_MImageBody_download">
// For now add an img tag with a spinner. <a className="mx_MImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
return ( {content.body}
<span className="mx_MImageBody" ref="body"> </a>
<img className="mx_MImageBody_thumbnail" src="img/spinner.gif" ref="image" <div className="mx_MImageBody_size">
alt={content.body} /> { content.info && content.info.size ? filesize(content.info.size) : "" }
</span> </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(); var thumbUrl = this._getThumbUrl();
const thumbUrl = this._getThumbUrl();
if (thumbUrl) { if (thumbUrl) {
return ( return (
<span className="mx_MImageBody" ref="body"> <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" <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
alt={content.body} alt={content.body}
onMouseEnter={this.onImageEnter} onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} /> onMouseLeave={this.onImageLeave} />
</a> </a>
<MFileBody {...this.props} decryptedUrl={this.state.decryptedUrl} /> { download }
</span> </span>
); );
} else if (content.body) { } else if (content.body) {

View file

@ -16,24 +16,16 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; var React = require('react');
import MFileBody from './MFileBody'; var filesize = require('filesize');
import MatrixClientPeg from '../../../MatrixClientPeg';
import Model from '../../../Modal'; var MatrixClientPeg = require('../../../MatrixClientPeg');
import sdk from '../../../index'; var Modal = require('../../../Modal');
import { decryptFile } from '../../../utils/DecryptFile'; var sdk = require('../../../index');
import q from 'q';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MVideoBody', displayName: 'MVideoBody',
getInitialState: function() {
return {
decryptedUrl: null,
decryptedThumbnailUrl: null,
};
},
thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
if (!fullWidth || !fullHeight) { if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even // 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() { render: function() {
const content = this.props.mxEvent.getContent(); 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>
);
}
const contentUrl = this._getContentUrl();
const thumbUrl = this._getThumbUrl();
var height = null; var height = null;
var width = null; var width = null;
var poster = null; var poster = null;
var preload = "metadata"; var preload = "metadata";
if (content.info) { 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) { if (scale) {
width = Math.floor(content.info.w * scale); width = Math.floor(content.info.w * scale);
height = Math.floor(content.info.h * scale); height = Math.floor(content.info.h * scale);
} }
if (thumbUrl) { if (content.info.thumbnail_url) {
poster = thumbUrl; poster = cli.mxcUrlToHttp(content.info.thumbnail_url);
preload = "none"; 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 ( return (
<span className="mx_MVideoBody"> <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} controls preload={preload} autoPlay={false}
height={height} width={width} poster={poster}> height={height} width={width} poster={poster}>
</video> </video>
<MFileBody {...this.props} decryptedUrl={this.state.decryptedUrl} /> { download }
</span> </span>
); );
}, },

View file

@ -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);
});
}