From aab57d091d7fea357bd26af63cde458df5ada304 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 6 Feb 2018 14:39:13 +0000 Subject: [PATCH 01/11] Make ratelimitedfunc time from the function's end Otherwise any function tghat takes longer than the delay to execute will become eligible for execution again immediately after finishing and therefore be able to spin. This should help with https://github.com/vector-im/riot-web/issues/6060 (at least in the respect that it makes ratelimitedfunc do its job) even if it's not the reason Riot started getting wedged. --- src/ratelimitedfunc.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ratelimitedfunc.js b/src/ratelimitedfunc.js index d8c30f5d03..20f6db79b8 100644 --- a/src/ratelimitedfunc.js +++ b/src/ratelimitedfunc.js @@ -35,13 +35,17 @@ module.exports = function(f, minIntervalMs) { if (self.lastCall < now - minIntervalMs) { f.apply(this); - self.lastCall = now; + // get the time again now the function has finished, so if it + // took longer than the delay time to execute, it doesn't + // immediately become eligible to run again. + self.lastCall = Date.now(); } else if (self.scheduledCall === undefined) { self.scheduledCall = setTimeout( () => { self.scheduledCall = undefined; f.apply(this); - self.lastCall = now; + // get time again as per above + self.lastCall = Date.now(); }, (self.lastCall + minIntervalMs) - now, ); From 424c367ecc7c2b7f607d5d973056365591a7aac6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 6 Feb 2018 18:45:43 +0000 Subject: [PATCH 02/11] Fix the reject/accept call buttons in canary (mk2) Fixes https://github.com/vector-im/riot-web/issues/6081 by making the accept/reject buttons AccessibleButtons which they should be anyway (presumably the role=button makes chrome do the right thing with the events). Also swallow the onClick event otherwise that propagates out to the room header and causes it to expand/collapse. --- src/components/views/voip/IncomingCallBox.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/voip/IncomingCallBox.js b/src/components/views/voip/IncomingCallBox.js index 8d75029baa..c0dff4e8a3 100644 --- a/src/components/views/voip/IncomingCallBox.js +++ b/src/components/views/voip/IncomingCallBox.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,6 +19,7 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; +import AccessibleButton from '../elements/AccessibleButton'; module.exports = React.createClass({ displayName: 'IncomingCallBox', @@ -26,14 +28,16 @@ module.exports = React.createClass({ incomingCall: PropTypes.object, }, - onAnswerClick: function() { + onAnswerClick: function(e) { + e.stopPropagation(); dis.dispatch({ action: 'answer', room_id: this.props.incomingCall.roomId, }); }, - onRejectClick: function() { + onRejectClick: function(e) { + e.stopPropagation(); dis.dispatch({ action: 'hangup', room_id: this.props.incomingCall.roomId, @@ -67,14 +71,14 @@ module.exports = React.createClass({
-
+ { _t("Decline") } -
+
-
+ { _t("Accept") } -
+
From c1649d1b754777d665301c5b319cbba5ee5075eb Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Wed, 7 Feb 2018 09:45:36 +0000 Subject: [PATCH 03/11] Give dialogs a matrixClient context Dialogs are mounted outside of the main react tree of MatrixChat, so they won't have its child context. --- src/components/views/dialogs/BaseDialog.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index e879808dc2..66e5fcb0c0 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -17,9 +17,12 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import { MatrixClient } from 'matrix-js-sdk'; + import { KeyCode } from '../../../Keyboard'; import AccessibleButton from '../elements/AccessibleButton'; import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; /** * Basic container for modal dialogs. @@ -51,6 +54,20 @@ export default React.createClass({ children: PropTypes.node, }, + childContextTypes: { + matrixClient: PropTypes.instanceOf(MatrixClient), + }, + + getChildContext: function() { + return { + matrixClient: this._matrixClient, + }; + }, + + componentWillMount() { + this._matrixClient = MatrixClientPeg.get(); + }, + _onKeyDown: function(e) { if (this.props.onKeyDown) { this.props.onKeyDown(e); From 0a5bf079139dedb3cc3b897ea68513a7ab146487 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 7 Feb 2018 10:13:19 +0000 Subject: [PATCH 04/11] Use getComponent --- src/components/views/voip/IncomingCallBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/IncomingCallBox.js b/src/components/views/voip/IncomingCallBox.js index c0dff4e8a3..a04cf4421e 100644 --- a/src/components/views/voip/IncomingCallBox.js +++ b/src/components/views/voip/IncomingCallBox.js @@ -19,7 +19,6 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; -import AccessibleButton from '../elements/AccessibleButton'; module.exports = React.createClass({ displayName: 'IncomingCallBox', @@ -63,6 +62,7 @@ module.exports = React.createClass({ } } + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return (
From 2a68e3ea392f84ef11b1efb0b5d58a2dd837bc92 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 7 Feb 2018 11:42:50 +0000 Subject: [PATCH 05/11] import sdk --- src/components/views/voip/IncomingCallBox.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/voip/IncomingCallBox.js b/src/components/views/voip/IncomingCallBox.js index a04cf4421e..ae003ff6f3 100644 --- a/src/components/views/voip/IncomingCallBox.js +++ b/src/components/views/voip/IncomingCallBox.js @@ -19,6 +19,7 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; +import sdk from '../../../index' module.exports = React.createClass({ displayName: 'IncomingCallBox', From 8eb4137ec315293b546a0ec305e76774c16e0ae3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 7 Feb 2018 11:51:41 +0000 Subject: [PATCH 06/11] missing semicolon --- src/components/views/voip/IncomingCallBox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/IncomingCallBox.js b/src/components/views/voip/IncomingCallBox.js index ae003ff6f3..6cbaabe602 100644 --- a/src/components/views/voip/IncomingCallBox.js +++ b/src/components/views/voip/IncomingCallBox.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; -import sdk from '../../../index' +import sdk from '../../../index'; module.exports = React.createClass({ displayName: 'IncomingCallBox', From 45ad46b4687242f296a12a9c937ddf46577d2574 Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Wed, 7 Feb 2018 15:51:03 +0000 Subject: [PATCH 07/11] Fix HS/IS URL reset when switching to Registration --- src/components/structures/MatrixChat.js | 43 ++++++++++++++----- src/components/structures/login/Login.js | 3 ++ .../structures/login/Registration.js | 2 + 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d6d0b00c84..b37da0144f 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -618,18 +618,26 @@ export default React.createClass({ }, _startRegistration: function(params) { - this.setStateForNewView({ + const newState = { view: VIEWS.REGISTER, - // these params may be undefined, but if they are, - // unset them from our state: we don't want to - // resume a previous registration session if the - // user just clicked 'register' - register_client_secret: params.client_secret, - register_session_id: params.session_id, - register_hs_url: params.hs_url, - register_is_url: params.is_url, - register_id_sid: params.sid, - }); + }; + + // Only honour params if they are all present, otherwise we reset + // HS and IS URLs when switching to registration. + if (params.client_secret && + params.session_id && + params.hs_url && + params.is_url && + params.sid + ) { + newState.register_client_secret = params.client_secret; + newState.register_session_id = params.session_id; + newState.register_hs_url = params.hs_url; + newState.register_is_url = params.is_url; + newState.register_id_sid = params.sid; + } + + this.setStateForNewView(newState); this.notifyNewScreen('register'); }, @@ -1501,6 +1509,17 @@ export default React.createClass({ } }, + onServerConfigChange(config) { + const newState = {}; + if (config.hsUrl) { + newState.register_hs_url = config.hsUrl; + } + if (config.isUrl) { + newState.register_is_url = config.isUrl; + } + this.setState(newState); + }, + _makeRegistrationUrl: function(params) { if (this.props.startingFragmentQueryParams.referrer) { params.referrer = this.props.startingFragmentQueryParams.referrer; @@ -1589,6 +1608,7 @@ export default React.createClass({ onLoginClick={this.onLoginClick} onRegisterClick={this.onRegisterClick} onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null} + onServerConfigChange={this.onServerConfigChange} /> ); } @@ -1623,6 +1643,7 @@ export default React.createClass({ onForgotPasswordClick={this.onForgotPasswordClick} enableGuest={this.props.enableGuest} onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null} + onServerConfigChange={this.onServerConfigChange} /> ); } diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index f4c08e8362..5042ca1fd0 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -58,6 +58,7 @@ module.exports = React.createClass({ // login shouldn't care how password recovery is done. onForgotPasswordClick: PropTypes.func, onCancelClick: PropTypes.func, + onServerConfigChange: PropTypes.func.isRequired, }, getInitialState: function() { @@ -218,6 +219,8 @@ module.exports = React.createClass({ if (config.isUrl !== undefined) { newState.enteredIdentityServerUrl = config.isUrl; } + + this.props.onServerConfigChange(config); this.setState(newState, function() { self._initLoginLogic(config.hsUrl || null, config.isUrl); }); diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index b8a85c5f82..62a3ee4f68 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -61,6 +61,7 @@ module.exports = React.createClass({ // registration shouldn't know or care how login is done. onLoginClick: PropTypes.func.isRequired, onCancelClick: PropTypes.func, + onServerConfigChange: PropTypes.func.isRequired, }, getInitialState: function() { @@ -131,6 +132,7 @@ module.exports = React.createClass({ if (config.isUrl !== undefined) { newState.isUrl = config.isUrl; } + this.props.onServerConfigChange(config); this.setState(newState, function() { this._replaceClient(); }); From 5823b32ab14c52be42ffac359b8f18d3ef2f10f7 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 8 Feb 2018 10:01:24 +0000 Subject: [PATCH 08/11] RoomView: guard against unmounting during peeking it's possible for the user to change room before the peek operation completes. Check if we've been unmounted before setting state. --- src/components/structures/RoomView.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 27a70fdb10..5304f38901 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -264,12 +264,19 @@ module.exports = React.createClass({ isPeeking: true, // this will change to false if peeking fails }); MatrixClientPeg.get().peekInRoom(roomId).then((room) => { + if (this.unmounted) { + return; + } this.setState({ room: room, peekLoading: false, }); this._onRoomLoaded(room); }, (err) => { + if (this.unmounted) { + return; + } + // Stop peeking if anything went wrong this.setState({ isPeeking: false, @@ -286,7 +293,7 @@ module.exports = React.createClass({ } else { throw err; } - }).done(); + }); } } else if (room) { // Stop peeking because we have joined this room previously From 3e4175f3e0e32db28f39935d1a3f45212104328e Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 9 Feb 2018 12:20:05 +0000 Subject: [PATCH 09/11] Add isUrlPermitted function --- src/HtmlUtils.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 0c262fe89a..5c6cbd6c1b 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import escape from 'lodash/escape'; import emojione from 'emojione'; import classNames from 'classnames'; import MatrixClientPeg from './MatrixClientPeg'; +import url from 'url'; emojione.imagePathSVG = 'emojione/svg/'; // Store PNG path for displaying many flags at once (for increased performance over SVG) @@ -44,6 +45,8 @@ const SYMBOL_PATTERN = /([\u2100-\u2bff])/; const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi"); const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; +const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet']; + /* * Return true if the given string contains emoji * Uses a much, much simpler regex than emojione's so will give false @@ -152,6 +155,25 @@ export function sanitizedHtmlNode(insaneHtml) { return
; } +/** + * Tests if a URL from an untrusted source may be safely put into the DOM + * The biggest threat here is javascript: URIs. + * Note that the HTML sanitiser library has its own internal logic for + * doing this, to which we pass the same list of schemes. This is used in + * other places we need to sanitise URLs. + * @return true if permitted, otherwise false + */ +export function isUrlPermitted(inputUrl) { + try { + const parsed = url.parse(inputUrl); + if (!parsed.protocol) return false; + // URL parser protocol includes the trailing colon + return PERMITTED_URL_SCHEMES.includes(parsed.protocol.slice(0, -1)); + } catch (e) { + return false; + } +} + const sanitizeHtmlParams = { allowedTags: [ 'font', // custom to matrix for IRC-style font coloring @@ -172,7 +194,7 @@ const sanitizeHtmlParams = { // Lots of these won't come up by default because we don't allow them selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'], // URL schemes we permit - allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'magnet'], + allowedSchemes: PERMITTED_URL_SCHEMES, allowProtocolRelative: false, From e9e0d65401c3ff6b840b2df66a92fab269e76df3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 9 Feb 2018 12:33:59 +0000 Subject: [PATCH 10/11] Prepare changelog for v0.11.4 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87459882c9..055e25b805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [0.11.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.11.4) (2018-02-09) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.11.3...v0.11.4) + + * Add isUrlPermitted function to sanity check URLs + Changes in [0.11.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.11.3) (2017-12-04) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.11.2...v0.11.3) From 4bf5e44b2043bbe95faa66943878acad23dfb823 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 9 Feb 2018 12:34:00 +0000 Subject: [PATCH 11/11] v0.11.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c81db2153..36f137e4f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.11.3", + "version": "0.11.4", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": {