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

Parse matrix-schemed URIs #7453

Merged
merged 55 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b44063d
Upgrade linkifyjs to v3.0
Nov 17, 2021
4d234ef
Fix incorrect test
Dec 3, 2021
10d018d
Try to parse matrix-schemed URIs
turt2live Dec 31, 2021
78a1ca0
Merge branch 'develop' into travis/linkify-matrix-scheme
toger5 Jan 14, 2022
a6076a5
line length
toger5 Jan 14, 2022
5296866
make pill @ and matrix.to links behave the same
toger5 Jan 17, 2022
3985653
remove local conversion on permalink conversion
toger5 Jan 17, 2022
716dcc3
fix options
toger5 Jan 17, 2022
d614204
Merge branch 'develop' into palid/upgrade-linkify
toger5 Jan 17, 2022
ff3902d
upgrade linkify to 3.0.5
toger5 Jan 17, 2022
01b10ab
fix linkify matrixSymbol parser
toger5 Jan 18, 2022
656d544
Merge branch 'develop' into palid/upgrade-linkify
toger5 Jan 18, 2022
12c3016
fix dot in localname
toger5 Jan 18, 2022
5f2bc30
fix too many colens
toger5 Jan 18, 2022
a2ed29b
remove registerCustomProtocol
toger5 Jan 18, 2022
b232948
remove unecassary import
toger5 Jan 18, 2022
eeb4fbc
Merge branch 'palid/upgrade-linkify' into travis/linkify-matrix-scheme
toger5 Jan 18, 2022
f6eaeb1
add matrix: parser
toger5 Jan 18, 2022
4d83b76
add protocol and register plugin
toger5 Jan 18, 2022
53aff56
handle matrix: links properly with EntityToPermalink
toger5 Jan 18, 2022
f52e778
adapt tryTransformEntityToPermalink to docs
toger5 Jan 18, 2022
80c315a
make tryTransformEntityToPermalink doc conforment
toger5 Jan 18, 2022
b0a3801
Merge branch 'develop' into travis/linkify-matrix-scheme
toger5 Jan 18, 2022
8bfd09e
Merge branch 'develop' into toger5/fix_links_to_not_open_new_tab
toger5 Jan 18, 2022
a792eee
turns tryTransformEntityToPermalink needs to return the link
toger5 Jan 18, 2022
cbd8b06
adapt tests
toger5 Jan 18, 2022
84d17de
fix tests
toger5 Jan 18, 2022
68c17b9
remove (now) unused function
toger5 Jan 18, 2022
13c7aad
update target condition
toger5 Jan 18, 2022
3479cbb
improove target check
toger5 Jan 18, 2022
7e94983
Merge branch 'toger5/fix_links_to_not_open_new_tab' into travis/linki…
toger5 Jan 18, 2022
44802a0
linter (oh we only need the local permalinks for the widgets now)
toger5 Jan 18, 2022
61bd6e9
Merge branch 'toger5/fix_links_to_not_open_new_tab' into travis/linki…
toger5 Jan 18, 2022
f7b9fd7
Merge branch 'develop' into travis/linkify-matrix-scheme
toger5 Jan 19, 2022
2f8565f
fix "add _blank" logic
toger5 Jan 19, 2022
3751b57
workarounds...
toger5 Jan 19, 2022
b4a9f67
fix other permalinks not beeing handled in app by adding prevent default
toger5 Jan 19, 2022
e0c12d5
update linkify to current v4
toger5 Jan 20, 2022
5022748
remove all the now deprecated code
toger5 Jan 20, 2022
5ebf5ba
port to linkify v4
toger5 Jan 20, 2022
716c2df
fix matrix Schma (@ | # | !) link handling
toger5 Jan 20, 2022
40f1a9f
fix parsing names and rooms with -
toger5 Jan 20, 2022
d81d6e4
upgrade linkify package imports
toger5 Jan 20, 2022
b4bacb9
update in package.json as well
toger5 Jan 20, 2022
de8230f
fix tests
toger5 Jan 20, 2022
12eb7c7
add test for hyphens in localnames
toger5 Jan 20, 2022
420695c
remove unnecassary tokens (all covered in domain now)
toger5 Jan 20, 2022
76c9ab8
remove Type.MatrixURI
toger5 Jan 20, 2022
088ace6
add tests for matirxUri's
toger5 Jan 20, 2022
88025f9
update linkifyjs to rc3 for type fixes
toger5 Jan 20, 2022
950641a
update yarn lock for type fixes
toger5 Jan 20, 2022
bff87b0
Fix init issue caused by duplication in the linkify-element package
toger5 Jan 20, 2022
f14e7e0
type fixes round 2
toger5 Jan 20, 2022
934cb7d
remove @types/linkifyjs
toger5 Jan 20, 2022
c90f7c6
Update src/utils/permalinks/MatrixSchemePermalinkConstructor.ts
toger5 Jan 20, 2022
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
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/linkify-element": "^4.0.0-rc.5",
"@matrix-org/linkify-string": "^4.0.0-rc.5",
"@matrix-org/linkifyjs": "^4.0.0-rc.5",
"@sentry/browser": "^6.11.0",
"@sentry/tracing": "^6.11.0",
"@types/geojson": "^7946.0.8",
Expand Down Expand Up @@ -85,9 +88,6 @@
"is-ip": "^3.1.0",
"jszip": "^3.7.0",
"katex": "^0.12.0",
"linkify-element": "^3.0.4",
"linkify-string": "^3.0.4",
"linkifyjs": "^3.0.5",
"lodash": "^4.17.20",
"maplibre-gl": "^1.15.2",
"matrix-analytics-events": "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1",
Expand Down Expand Up @@ -147,7 +147,6 @@
"@types/file-saver": "^2.0.3",
"@types/flux": "^3.1.9",
"@types/jest": "^26.0.20",
"@types/linkifyjs": "^2.1.3",
"@types/lodash": "^4.14.168",
"@types/modernizr": "^3.5.3",
"@types/node": "^14.14.22",
Expand Down
7 changes: 4 additions & 3 deletions src/HtmlUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,10 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
if (attribs.href) {
attribs.target = '_blank'; // by default

const transformed = tryTransformPermalinkToLocalHref(attribs.href);
if (transformed !== attribs.href || attribs.href.match(ELEMENT_URL_PATTERN)) {
attribs.href = transformed;
const transformed = tryTransformPermalinkToLocalHref(attribs.href); // only used to check if it is a link that can be handled locally
if (transformed !== attribs.href || // it could be converted so handle locally symbols e.g. @user:server.tdl, matrix: and matrix.to
attribs.href.match(ELEMENT_URL_PATTERN) // for https:vector|riot...
) {
delete attribs.target;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/elements/Pill.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import FlairStore from "../../../stores/FlairStore";
import { getPrimaryPermalinkEntity, parseAppLocalLink } from "../../../utils/permalinks/Permalinks";
import { getPrimaryPermalinkEntity, parsePermalink } from "../../../utils/permalinks/Permalinks";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { Action } from "../../../dispatcher/actions";
import { mediaFromMxc } from "../../../customisations/Media";
Expand Down Expand Up @@ -85,7 +85,7 @@ class Pill extends React.Component {

if (nextProps.url) {
if (nextProps.inMessage) {
const parts = parseAppLocalLink(nextProps.url);
const parts = parsePermalink(nextProps.url);
resourceId = parts.primaryEntityId; // The room/user ID
prefix = parts.sigil; // The first character of prefix
} else {
Expand Down
70 changes: 41 additions & 29 deletions src/linkify-matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import * as linkifyjs from 'linkifyjs';
import linkifyElement from 'linkify-element';
import linkifyString from 'linkify-string';
import * as linkifyjs from '@matrix-org/linkifyjs';
import linkifyElement from '@matrix-org/linkify-element';
import linkifyString from '@matrix-org/linkify-string';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { registerPlugin } from 'linkifyjs';
import { registerCustomProtocol, registerPlugin } from '@matrix-org/linkifyjs';

import { baseUrl } from "./utils/permalinks/SpecPermalinkConstructor";
//linkifyjs/src/core/fsm
import { baseUrl } from "./utils/permalinks/MatrixToPermalinkConstructor";
import {
parsePermalink,
tryTransformEntityToPermalink,
Expand All @@ -31,7 +32,7 @@ import dis from './dispatcher/dispatcher';
import { Action } from './dispatcher/actions';
import { ViewUserPayload } from './dispatcher/payloads/ViewUserPayload';

enum Type {
export enum Type {
URL = "url",
UserId = "userid",
RoomAlias = "roomalias",
Expand All @@ -53,41 +54,29 @@ function matrixOpaqueIdLinkifyParser({
name: Type;
}) {
const {
DOMAIN,
DOT,
// A generic catchall text token
TEXT,
// IPV4 necessity
NUM,
TLD,
COLON,
SYM,
HYPHEN,
UNDERSCORE,
// because 'localhost' is tokenised to the localhost token,
// usernames @localhost:foo.com are otherwise not matched!
LOCALHOST,
domain,
} = scanner.tokens;

const S_START = parser.start;
const matrixSymbol = utils.createTokenClass(name, { isLink: true });

const localpartTokens = [
DOMAIN,
// IPV4 necessity
NUM,
TLD,

// because 'localhost' is tokenised to the localhost token,
// usernames @localhost:foo.com are otherwise not matched!
LOCALHOST,
SYM,
UNDERSCORE,
TEXT,
];
const domainpartTokens = [DOMAIN, NUM, TLD, LOCALHOST];
const localpartTokens = [domain, TLD, LOCALHOST, SYM, UNDERSCORE, HYPHEN];
const domainpartTokens = [domain, TLD, LOCALHOST, HYPHEN];

const INITIAL_STATE = S_START.tt(token);

const LOCALPART_STATE = INITIAL_STATE.tt(DOMAIN);
const LOCALPART_STATE = INITIAL_STATE.tt(domain);
for (const token of localpartTokens) {
INITIAL_STATE.tt(token, LOCALPART_STATE);
LOCALPART_STATE.tt(token, LOCALPART_STATE);
Expand All @@ -98,7 +87,7 @@ function matrixOpaqueIdLinkifyParser({
}

const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON);
const DOMAINPART_STATE = DOMAINPART_STATE_DOT.tt(DOMAIN);
const DOMAINPART_STATE = DOMAINPART_STATE_DOT.tt(domain);
DOMAINPART_STATE.tt(DOT, DOMAINPART_STATE_DOT);
for (const token of domainpartTokens) {
DOMAINPART_STATE.tt(token, DOMAINPART_STATE);
Expand All @@ -117,20 +106,23 @@ function matrixOpaqueIdLinkifyParser({
}

function onUserClick(event: MouseEvent, userId: string) {
event.preventDefault();
const member = new RoomMember(null, userId);
if (!member) { return; }
dis.dispatch<ViewUserPayload>({
action: Action.ViewUser,
member: member,
});
}

function onAliasClick(event: MouseEvent, roomAlias: string) {
event.preventDefault();
dis.dispatch({
action: Action.ViewRoom,
room_alias: roomAlias,
});
}

function onGroupClick(event: MouseEvent, groupId: string) {
event.preventDefault();
dis.dispatch({ action: 'view_group', group_id: groupId });
Expand Down Expand Up @@ -168,6 +160,19 @@ export const options = {
onUserClick(e, permalink.userId);
},
};
} else {
// for events, rooms etc. (anything other then users)
const localHref = tryTransformPermalinkToLocalHref(href);
if (localHref !== href) {
// it could be converted to a localHref -> therefore handle locally
return {
// @ts-ignore see https://linkify.js.org/docs/options.html
click: function(e) {
e.preventDefault();
window.location.hash = localHref;
},
};
}
}
} catch (e) {
// OK fine, it's not actually a permalink
Expand All @@ -178,21 +183,24 @@ export const options = {
return {
// @ts-ignore see https://linkify.js.org/docs/options.html
click: function(e) {
onUserClick(e, href);
const userId = parsePermalink(href).userId;
onUserClick(e, userId);
},
};
case Type.RoomAlias:
return {
// @ts-ignore see https://linkify.js.org/docs/options.html
click: function(e) {
onAliasClick(e, href);
const alias = parsePermalink(href).roomIdOrAlias;
onAliasClick(e, alias);
},
};
case Type.GroupId:
return {
// @ts-ignore see https://linkify.js.org/docs/options.html
click: function(e) {
onGroupClick(e, href);
const groupId = parsePermalink(href).groupId;
onGroupClick(e, groupId);
},
};
}
Expand All @@ -219,7 +227,9 @@ export const options = {
if (type === Type.URL) {
try {
const transformed = tryTransformPermalinkToLocalHref(href);
if (transformed !== href || decodeURIComponent(href).match(ELEMENT_URL_PATTERN)) {
if (transformed !== href || // if it could be converted to handle locally for matrix symbols e.g. @user:server.tdl and matrix.to
decodeURIComponent(href).match(ELEMENT_URL_PATTERN) // for https:vector|riot...
) {
return null;
} else {
return '_blank';
Expand Down Expand Up @@ -266,6 +276,8 @@ registerPlugin(Type.UserId, ({ scanner, parser, utils }) => {
});
});

registerCustomProtocol("matrix", true);

export const linkify = linkifyjs;
export const _linkifyElement = linkifyElement;
export const _linkifyString = linkifyString;
105 changes: 105 additions & 0 deletions src/utils/permalinks/MatrixSchemePermalinkConstructor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Copyright 2022 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.
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.
*/

import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";

/**
* Generates matrix: scheme permalinks
*/
export default class MatrixSchemePermalinkConstructor extends PermalinkConstructor {
constructor() {
super();
}

private encodeEntity(entity: string): string {
if (entity[0] === "!") {
return `roomid/${entity.slice(1)}`;
} else if (entity[0] === "#") {
return `r/${entity.slice(1)}`;
} else if (entity[0] === "@") {
return `u/${entity.slice(1)}`;
} else if (entity[0] === "$") {
return `e/${entity.slice(1)}`;
}

throw new Error("Cannot encode entity: " + entity);
}

forEvent(roomId: string, eventId: string, serverCandidates: string[]): string {
return `matrix:${this.encodeEntity(roomId)}` +
`/${this.encodeEntity(eventId)}${this.encodeServerCandidates(serverCandidates)}`;
}

forRoom(roomIdOrAlias: string, serverCandidates: string[]): string {
return `matrix:${this.encodeEntity(roomIdOrAlias)}${this.encodeServerCandidates(serverCandidates)}`;
}

forUser(userId: string): string {
return `matrix:${this.encodeEntity(userId)}`;
}

forGroup(groupId: string): string {
throw new Error("Deliberately not implemented");
}

forEntity(entityId: string): string {
return `matrix:${this.encodeEntity(entityId)}`;
}

isPermalinkHost(testHost: string): boolean {
// TODO: Change API signature to accept the URL for checking
return testHost === "";
}

encodeServerCandidates(candidates: string[]) {
if (!candidates || candidates.length === 0) return '';
return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
}

parsePermalink(fullUrl: string): PermalinkParts {
if (!fullUrl || !fullUrl.startsWith("matrix:")) {
throw new Error("Does not appear to be a permalink");
}

const parts = fullUrl.substring("matrix:".length).split('/');

const identifier = parts[0];
const entityNoSigil = parts[1];
if (identifier === 'u') {
// Probably a user, no further parsing needed.
return PermalinkParts.forUser(`@${entityNoSigil}`);
} else if (identifier === 'r' || identifier === 'roomid') {
const sigil = identifier === 'r' ? '#' : '!';

if (parts.length === 2) { // room without event permalink
const [roomId, query = ""] = entityNoSigil.split("?");
const via = query.split(/&?via=/g).filter(p => !!p);
return PermalinkParts.forRoom(`${sigil}${roomId}`, via);
}

if (parts[2] === 'e') { // event permalink
const eventIdAndQuery = parts.length > 3 ? parts.slice(3).join('/') : "";
const [eventId, query = ""] = eventIdAndQuery.split("?");
const via = query.split(/&?via=/g).filter(p => !!p);
return PermalinkParts.forEvent(`${sigil}${entityNoSigil}`, `$${eventId}`, via);
}

throw new Error("Faulty room permalink");
} else {
throw new Error("Unknown entity type in permalink");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const baseUrl = `https://${host}`;
/**
* Generates matrix.to permalinks
*/
export default class SpecPermalinkConstructor extends PermalinkConstructor {
export default class MatrixToPermalinkConstructor extends PermalinkConstructor {
constructor() {
super();
}
Expand Down
Loading