Skip to content

Commit

Permalink
Permalinks should be able to decode and encode via servers. (#300)
Browse files Browse the repository at this point in the history
* Permalinks should be able to decode and encode via servers.

Previously it was skipping `&via` and just joining `via` when there were more servers.
And it also just would not attempt to parse via servers when there was no event id to go with a room.

#299
Signed-off-by: gnuxie <[email protected]>

* Accurate documentation.

* Use encodeURIComponent for Permalink components.

* Edge cases in Permalinks.

* Test Permalinks are URI encoded but can still decode bodged URIs.

* Revert "Use encodeURIComponent for Permalink components."

This reverts commit f4e106c.

* Revert "Test Permalinks are URI encoded but can still decode bodged URIs."

This reverts commit 2f7c724.

* Update test/helpers/PermalinksTest.ts

---------

Signed-off-by: gnuxie <[email protected]>
Co-authored-by: Travis Ralston <[email protected]>
Co-authored-by: Travis Ralston <[email protected]>
  • Loading branch information
3 people authored Mar 19, 2023
1 parent b9911ef commit 8f7ef01
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 32 deletions.
43 changes: 18 additions & 25 deletions src/helpers/Permalinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
*/
export interface PermalinkParts {
/**
* The room ID or alias the permalink references. May be null.
* The room ID or alias the permalink references. May be undefined.
*/
roomIdOrAlias: string;

/**
* The user ID the permalink references. May be null.
* The user ID the permalink references. May be undefined.
*/
userId: string;

/**
* The event ID the permalink references. May be null.
* The event ID the permalink references. May be undefined.
*/
eventId: string;

/**
* The servers the permalink is routed through. May be null or empty.
* The servers the permalink is routed through. May be undefined or empty.
*/
viaServers: string[];
}
Expand All @@ -38,7 +38,7 @@ export class Permalinks {
private static encodeViaArgs(servers: string[]): string {
if (!servers || !servers.length) return "";

return `?via=${servers.join("via=")}`;
return `?via=${servers.join("&via=")}`;
}

/**
Expand Down Expand Up @@ -77,32 +77,25 @@ export class Permalinks {
* @returns {PermalinkParts} The parts of the permalink.
*/
public static parseUrl(matrixTo: string): PermalinkParts {
if (!matrixTo || !matrixTo.startsWith("https://matrix.to/#/")) {
const matrixToRegexp = /^https:\/\/matrix\.to\/#\/(?<entity>[^/?]+)\/?(?<eventId>[^?]+)?(?<query>\?[^]*)?$/;

const url = matrixToRegexp.exec(matrixTo)?.groups;
if (!url) {
throw new Error("Not a valid matrix.to URL");
}

const parts = matrixTo.substring("https://matrix.to/#/".length).split("/");

const entity = decodeURIComponent(parts[0]);
const entity = decodeURIComponent(url.entity);
if (entity[0] === '@') {
return { userId: entity, roomIdOrAlias: undefined, eventId: undefined, viaServers: undefined };
} else if (entity[0] === '#' || entity[0] === '!') {
if (parts.length === 1) {
return { roomIdOrAlias: entity, userId: undefined, eventId: undefined, viaServers: [] };
}

// rejoin the rest because v3 room can have slashes
const eventIdAndQuery = decodeURIComponent(parts.length > 1 ? parts.slice(1).join('/') : "");
const secondaryParts = eventIdAndQuery.split('?');

const eventId = secondaryParts[0];
const query = secondaryParts.length > 1 ? secondaryParts[1] : "";

const via = query.split("via=").filter(p => !!p);

return { roomIdOrAlias: entity, eventId: eventId, viaServers: via, userId: undefined };
return {
userId: undefined,
roomIdOrAlias: entity,
eventId: url.eventId && decodeURIComponent(url.eventId),
viaServers: new URLSearchParams(url.query).getAll('via'),
};
} else {
throw new Error("Unexpected entity");
}

throw new Error("Unexpected entity");
}
}
29 changes: 22 additions & 7 deletions test/helpers/PermalinksTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ describe('Permalinks', () => {
it('should generate a URL for a room ID with via', () => {
const roomId = "!test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomId}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomId}?via=${via.join("&via=")}`;
expect(Permalinks.forRoom(roomId, via)).toBe(expected);
});

it('should generate a URL for a room alias with via', () => {
const roomAlias = "#test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomAlias}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomAlias}?via=${via.join("&via=")}`;
expect(Permalinks.forRoom(roomAlias, via)).toBe(expected);
});
});
Expand All @@ -48,15 +48,15 @@ describe('Permalinks', () => {
const roomId = "!test:example.org";
const eventId = "$test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomId}/${eventId}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomId}/${eventId}?via=${via.join("&via=")}`;
expect(Permalinks.forEvent(roomId, eventId, via)).toBe(expected);
});

it('should generate a URL for an event ID with room alias with via', () => {
const roomAlias = "#test:example.org";
const eventId = "$test:example.org";
const via = ['one.example.org', 'two.example.org'];
const expected = `https://matrix.to/#/${roomAlias}/${eventId}?via=${via.join("via=")}`;
const expected = `https://matrix.to/#/${roomAlias}/${eventId}?via=${via.join("&via=")}`;
expect(Permalinks.forEvent(roomAlias, eventId, via)).toBe(expected);
});
});
Expand All @@ -81,6 +81,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${userId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forUser(userId))).toMatchObject(expected);
});

it('should parse room alias URLs', () => {
Expand All @@ -94,6 +95,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forRoom(roomId))).toMatchObject(expected);
});

it('should parse room ID URLs', () => {
Expand All @@ -107,6 +109,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forRoom(roomId))).toMatchObject(expected);
});

it('should parse room alias permalink URLs', () => {
Expand All @@ -116,6 +119,7 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId))).toMatchObject(expected);
});

it('should parse room ID permalink URLs', () => {
Expand All @@ -125,26 +129,37 @@ describe('Permalinks', () => {
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId))).toMatchObject(expected);
});

it('should parse room alias permalink URLs with via servers', () => {
const roomId = "#test:example.org";
const eventId = "$ev:example.org";
const via = ["one.example.org", "two.example.org"];
const expected: PermalinkParts = { userId: undefined, roomIdOrAlias: roomId, viaServers: via, eventId };
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("via=")}`);

const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("&via=")}`);
expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId, via))).toMatchObject(expected);
});

it('should parse room ID permalink URLs with via servers', () => {
const roomId = "!test:example.org";
const eventId = "$ev:example.org";
const via = ["one.example.org", "two.example.org"];
const expected: PermalinkParts = { userId: undefined, roomIdOrAlias: roomId, viaServers: via, eventId };
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("via=")}`);
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/${eventId}?via=${via.join("&via=")}`);

expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forEvent(roomId, eventId, via))).toMatchObject(expected);
});

it('should parse room ID URLs with via servers', () => {
const roomId = "!example:example.org";
const via = ["example.org"];
const expected: PermalinkParts = { userId: undefined, roomIdOrAlias: roomId, viaServers: via, eventId: undefined };
const parsed = Permalinks.parseUrl(`https://matrix.to/#/${roomId}/?via=${via.join("&via=")}`);
expect(parsed).toMatchObject(<any>expected);
expect(Permalinks.parseUrl(Permalinks.forRoom(roomId, via))).toMatchObject(expected);
});
});
});

0 comments on commit 8f7ef01

Please sign in to comment.