Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Support local permalinks for unfederated instances #3500

Merged
merged 21 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
64aa669
Move matrix-to.js to utils/permalinks/RoomPermalinkCreator
turt2live Sep 30, 2019
f9d5e89
Move early parts of matrix.to bits into its own class
turt2live Sep 30, 2019
926e114
Move spec permalinks into their own class
turt2live Sep 30, 2019
f879185
Move permalink host check into permalink constructors
turt2live Sep 30, 2019
f183e96
Introduce a RiotPermalinkConstructor and fix the setting name
turt2live Sep 30, 2019
baf78da
Support spec-level permalink parsing
turt2live Sep 30, 2019
9bb1ebb
Support riot-level permalink parsing
turt2live Sep 30, 2019
3e5a39d
Add utility function for permalink parsing
turt2live Sep 30, 2019
199dfa7
Always check if the permalink is a spec permalink
turt2live Sep 30, 2019
8acaa3c
Update generated Riot permalinks
turt2live Sep 30, 2019
6656ef1
Rework /join to parse permalinks more safely
turt2live Sep 30, 2019
ff4eee5
Minor cleanup of getPermalinkConstructor
turt2live Sep 30, 2019
2cb0b49
Converge on permalink processing for HtmlUtils and linkify-matrix
turt2live Oct 1, 2019
2824f46
Update pill processing to handle better permalinks
turt2live Oct 1, 2019
ce0a534
Fix pills for CIDER too
turt2live Oct 1, 2019
6e6f8a1
Handle BigEmoji permalinks better
turt2live Oct 1, 2019
6f5ccd4
Minor comment updates
turt2live Oct 1, 2019
fc66e69
Rename RoomPermalinkCreator -> Permalinks due to scope
turt2live Oct 1, 2019
6b09c3e
Appease the linter
turt2live Oct 1, 2019
f903f43
Fix BigEmoji handling for permalinks
turt2live Oct 1, 2019
0862ad0
Fix permalinks test
turt2live Oct 1, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 9 additions & 25 deletions src/HtmlUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <[email protected]>
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,6 +34,7 @@ import url from 'url';

import EMOJIBASE from 'emojibase-data/en/compact.json';
import EMOJIBASE_REGEX from 'emojibase-regex';
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";

linkifyMatrix(linkify);

Expand Down Expand Up @@ -158,30 +160,10 @@ const transformTags = { // custom to matrix
if (attribs.href) {
attribs.target = '_blank'; // by default

let m;
// FIXME: horrible duplication with linkify-matrix
m = attribs.href.match(linkifyMatrix.VECTOR_URL_PATTERN);
if (m) {
attribs.href = m[1];
const transformed = tryTransformPermalinkToLocalHref(attribs.href);
if (transformed !== attribs.href || attribs.href.match(linkifyMatrix.VECTOR_URL_PATTERN)) {
attribs.href = transformed;
delete attribs.target;
} else {
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
if (m) {
const entity = m[1];
switch (entity[0]) {
case '@':
attribs.href = '#/user/' + entity;
break;
case '+':
attribs.href = '#/group/' + entity;
break;
case '#':
case '!':
attribs.href = '#/room/' + entity;
break;
}
delete attribs.target;
}
}
}
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
Expand Down Expand Up @@ -465,10 +447,12 @@ export function bodyToHtml(content, highlights, opts={}) {
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length &&
// Prevent user pills expanding for users with only emoji in
// their username
// their username. Permalinks (links in pills) can be any URL
// now, so we just check for an HTTP-looking thing.
(
content.formatted_body == undefined ||
!content.formatted_body.includes("https://matrix.to/")
(!content.formatted_body.includes("http:") &&
!content.formatted_body.includes("https:"))
);
}

Expand Down
49 changes: 28 additions & 21 deletions src/SlashCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import dis from './dispatcher';
import sdk from './index';
import {_t, _td} from './languageHandler';
import Modal from './Modal';
import {MATRIXTO_URL_PATTERN} from "./linkify-matrix";
import * as querystring from "querystring";
import MultiInviter from './utils/MultiInviter';
import { linkifyAndSanitizeHtml } from './HtmlUtils';
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
Expand All @@ -34,6 +32,7 @@ import Promise from "bluebird";
import { getAddressType } from './UserAddress';
import { abbreviateUrl } from './utils/UrlUtils';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";

const singleMxcUpload = async () => {
return new Promise((resolve) => {
Expand Down Expand Up @@ -441,7 +440,19 @@ export const CommandMap = {
const params = args.split(' ');
if (params.length < 1) return reject(this.getUsage());

const matrixToMatches = params[0].match(MATRIXTO_URL_PATTERN);
let isPermalink = false;
if (params[0].startsWith("http:") || params[0].startsWith("https:")) {
// It's at least a URL - try and pull out a hostname to check against the
// permalink handler
const parsedUrl = new URL(params[0]);
const hostname = parsedUrl.host || parsedUrl.hostname; // takes first non-falsey value

// if we're using a Riot permalink handler, this will catch it before we get much further.
// see below where we make assumptions about parsing the URL.
if (isPermalinkHost(hostname)) {
isPermalink = true;
}
}
if (params[0][0] === '#') {
let roomAlias = params[0];
if (!roomAlias.includes(':')) {
Expand Down Expand Up @@ -469,29 +480,25 @@ export const CommandMap = {
auto_join: true,
});
return success();
} else if (matrixToMatches) {
let entity = matrixToMatches[1];
let eventId = null;
let viaServers = [];

if (entity[0] !== '!' && entity[0] !== '#') return reject(this.getUsage());
} else if (isPermalink) {
const permalinkParts = parsePermalink(params[0]);

if (entity.indexOf('?') !== -1) {
const parts = entity.split('?');
entity = parts[0];

const parsed = querystring.parse(parts[1]);
viaServers = parsed["via"];
if (typeof viaServers === 'string') viaServers = [viaServers];
// This check technically isn't needed because we already did our
// safety checks up above. However, for good measure, let's be sure.
if (!permalinkParts) {
return reject(this.getUsage());
}

// We quietly support event ID permalinks too
if (entity.indexOf('/$') !== -1) {
const parts = entity.split("/$");
entity = parts[0];
eventId = `$${parts[1]}`;
// If for some reason someone wanted to join a group or user, we should
// stop them now.
if (!permalinkParts.roomIdOrAlias) {
return reject(this.getUsage());
}

const entity = permalinkParts.roomIdOrAlias;
const viaServers = permalinkParts.viaServers;
const eventId = permalinkParts.eventId;

const dispatch = {
action: 'view_room',
auto_join: true,
Expand Down
2 changes: 1 addition & 1 deletion src/autocomplete/CommunityProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import sdk from '../index';
import _sortBy from 'lodash/sortBy';
import {makeGroupPermalink} from "../matrix-to";
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
import type {Completion, SelectionRange} from "./Autocompleter";
import FlairStore from "../stores/FlairStore";

Expand Down
2 changes: 1 addition & 1 deletion src/autocomplete/RoomProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {PillCompletion} from './Components';
import {getDisplayAliasForRoom} from '../Rooms';
import sdk from '../index';
import _sortBy from 'lodash/sortBy';
import {makeRoomPermalink} from "../matrix-to";
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
import type {Completion, SelectionRange} from "./Autocompleter";

const ROOM_REGEX = /\B#\S*/g;
Expand Down
2 changes: 1 addition & 1 deletion src/autocomplete/UserProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import _sortBy from 'lodash/sortBy';
import MatrixClientPeg from '../MatrixClientPeg';

import type {MatrixEvent, Room, RoomMember, RoomState} from 'matrix-js-sdk';
import {makeUserPermalink} from "../matrix-to";
import {makeUserPermalink} from "../utils/permalinks/Permalinks";
import type {Completion, SelectionRange} from "./Autocompleter";

const USER_REGEX = /\B@\S*/g;
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/GroupView.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import classnames from 'classnames';
import GroupStore from '../../stores/GroupStore';
import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to";
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";

const LONG_DESC_PLACEHOLDER = _td(
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/RoomView.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import Promise from 'bluebird';
import classNames from 'classnames';
import {Room} from "matrix-js-sdk";
import { _t } from '../../languageHandler';
import {RoomPermalinkCreator} from '../../matrix-to';
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';

import MatrixClientPeg from '../../MatrixClientPeg';
import ContentMessages from '../../ContentMessages';
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/dialogs/ShareDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import QRCode from 'qrcode-react';
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../matrix-to";
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import * as ContextualMenu from "../../structures/ContextualMenu";

const socials = [
Expand Down
32 changes: 15 additions & 17 deletions src/components/views/elements/Pill.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -22,23 +23,21 @@ import classNames from 'classnames';
import { Room, RoomMember, MatrixClient } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
import { getDisplayAliasForRoom } from '../../../Rooms';
import FlairStore from "../../../stores/FlairStore";

const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";

// For URLs of matrix.to links in the timeline which have been reformatted by
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;

const Pill = createReactClass({
statics: {
isPillUrl: (url) => {
return !!REGEX_MATRIXTO.exec(url);
return !!getPrimaryPermalinkEntity(url);
},
isMessagePillUrl: (url) => {
return !!REGEX_LOCAL_MATRIXTO.exec(url);
return !!REGEX_LOCAL_PERMALINK.exec(url);
},
roomNotifPos: (text) => {
return text.indexOf("@room");
Expand Down Expand Up @@ -95,22 +94,21 @@ const Pill = createReactClass({
},

async componentWillReceiveProps(nextProps) {
let regex = REGEX_MATRIXTO;
if (nextProps.inMessage) {
regex = REGEX_LOCAL_MATRIXTO;
}

let matrixToMatch;
let resourceId;
let prefix;

if (nextProps.url) {
// Default to the empty array if no match for simplicity
// resource and prefix will be undefined instead of throwing
matrixToMatch = regex.exec(nextProps.url) || [];
if (nextProps.inMessage) {
// Default to the empty array if no match for simplicity
// resource and prefix will be undefined instead of throwing
const matrixToMatch = REGEX_LOCAL_PERMALINK.exec(nextProps.url) || [];

resourceId = matrixToMatch[1]; // The room/user ID
prefix = matrixToMatch[2]; // The first character of prefix
resourceId = matrixToMatch[1]; // The room/user ID
prefix = matrixToMatch[2]; // The first character of prefix
} else {
resourceId = getPrimaryPermalinkEntity(nextProps.url);
prefix = resourceId ? resourceId[0] : undefined;
}
}

const pillType = this.props.type || {
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/elements/ReplyThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import {wantsDateSeparator} from '../../../DateUtils';
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
import {makeUserPermalink, RoomPermalinkCreator} from "../../../matrix-to";
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";

// This component does no cycle detection, simply because the only way to make such a cycle would be to
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/RoomCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';

import dis from '../../../dispatcher';
import { RoomPermalinkCreator } from '../../../matrix-to';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';

Expand Down
6 changes: 3 additions & 3 deletions src/components/views/messages/TextualBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ import { _t } from '../../../languageHandler';
import * as ContextualMenu from '../../structures/ContextualMenu';
import SettingsStore from "../../../settings/SettingsStore";
import ReplyThread from "../elements/ReplyThread";
import {host as matrixtoHost} from '../../../matrix-to';
import {pillifyLinks} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";

module.exports = createReactClass({
displayName: 'TextualBody',
Expand Down Expand Up @@ -248,10 +248,10 @@ module.exports = createReactClass({
const url = node.getAttribute("href");
const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];

// never preview matrix.to links (if anything we should give a smart
// never preview permalinks (if anything we should give a smart
// preview of the room/user they point to: nobody needs to be reminded
// what the matrix.to site looks like).
if (host === matrixtoHost) return false;
if (isPermalinkHost(host)) return false;

if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) {
// it's a "foo.pl" style link
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/MessageComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import sdk from '../../../index';
import dis from '../../../dispatcher';
import RoomViewStore from '../../../stores/RoomViewStore';
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../matrix-to';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import classNames from 'classnames';
import E2EIcon from './E2EIcon';
Expand Down
15 changes: 5 additions & 10 deletions src/components/views/rooms/MessageComposerInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@ import Markdown from '../../../Markdown';
import MessageComposerStore from '../../../stores/MessageComposerStore';
import ContentMessages from '../../../ContentMessages';

import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix';

import EMOJIBASE from 'emojibase-data/en/compact.json';
import EMOTICON_REGEX from 'emojibase-regex/emoticon';

import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import {makeUserPermalink} from "../../../matrix-to";
import {getPrimaryPermalinkEntity, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import ReplyPreview from "./ReplyPreview";
import RoomViewStore from '../../../stores/RoomViewStore';
import ReplyThread from "../elements/ReplyThread";
Expand Down Expand Up @@ -224,18 +222,15 @@ export default class MessageComposerInput extends React.Component {
// special case links
if (tag === 'a') {
const href = el.getAttribute('href');
let m;
if (href) {
m = href.match(MATRIXTO_URL_PATTERN);
}
if (m) {
const permalinkEntity = getPrimaryPermalinkEntity(href);
if (permalinkEntity) {
return {
object: 'inline',
type: 'pill',
data: {
href,
completion: el.innerText,
completionId: m[1],
completionId: permalinkEntity,
},
};
} else {
Expand Down Expand Up @@ -541,7 +536,7 @@ export default class MessageComposerInput extends React.Component {

const textWithMdPills = this.plainWithMdPills.serialize(editorState);
const markdown = new Markdown(textWithMdPills);
// HTML deserialize has custom rules to turn matrix.to links into pill objects.
// HTML deserialize has custom rules to turn permalinks into pill objects.
return this.html.deserialize(markdown.toHTML());
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/ReplyPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
import PropTypes from "prop-types";
import {RoomPermalinkCreator} from "../../../matrix-to";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";

function cancelQuoting() {
dis.dispatch({
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/SlateMessageComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../matrix-to';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import classNames from 'classnames';

Expand Down
Loading