feat: Add identifier string parsing

This commit is contained in:
Sorunome 2021-01-01 15:15:43 +01:00
parent 4d2c4df4d4
commit 19d96595cc
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
2 changed files with 183 additions and 0 deletions

View File

@ -38,4 +38,115 @@ extension MatrixIdExtension on String {
String get domain => isValidMatrixId ? _getParts().last : null;
bool equals(String other) => toLowerCase() == other?.toLowerCase();
/// Separate a matrix identifier string into a primary indentifier, a secondary identifier,
/// a query string and already parsed `via` parameters. A matrix identifier string
/// can be an mxid, a matrix.to-url or a matrix-uri.
MatrixIdentifierStringExtensionResults parseIdentifierIntoParts() {
const matrixUriPrefix = 'matrix:';
final via = <String>{};
String action;
final parseQueryString = (qs) {
if (qs != null) {
// as there might be multiple "via" tags we can't just use Uri.splitQueryString, we need to do our own thing
for (final parameterStr in qs.split('&')) {
final index = parameterStr.indexOf('=');
if (index == -1) {
continue;
}
final parameter =
Uri.decodeQueryComponent(parameterStr.substring(0, index));
final value =
Uri.decodeQueryComponent(parameterStr.substring(index + 1));
if (parameter == 'via') {
via.add(value);
}
if (parameter == 'action') {
action = value;
}
}
}
};
// check if we have a "matrix:" uri
if (toLowerCase().startsWith(matrixUriPrefix)) {
final uri = Uri.tryParse(this);
if (uri == null) {
return null;
}
final pathSegments = uri.pathSegments;
final identifiers = <String>[];
for (var i = 0; i < pathSegments.length - 1; i += 2) {
final thisSigil = {
'user': '@',
'roomid': '!',
'room': '#',
'group': '+',
'event': '\$',
}[pathSegments[i].toLowerCase()];
if (thisSigil == null) {
break;
}
final identifier = thisSigil + pathSegments[i + 1];
if (!identifier.isValidMatrixId) {
return null;
}
identifiers.add(identifier);
}
if (identifiers.isEmpty) {
return null;
}
final queryString = uri.query.isNotEmpty ? uri.query : null;
parseQueryString(queryString);
return MatrixIdentifierStringExtensionResults(
primaryIdentifier: identifiers.first,
secondaryIdentifier: identifiers.length > 1 ? identifiers[1] : null,
queryString: queryString,
via: via,
action: action,
);
}
const matrixToPrefix = 'https://matrix.to/#/';
// matrix identifiers and matrix.to URLs are parsed similarly, so we do them here
var s = this;
if (toLowerCase().startsWith(matrixToPrefix)) {
// as we decode a component we may only call it on the url part *before* the "query" part
final parts = substring(matrixToPrefix.length).split('?');
s = Uri.decodeComponent(parts.removeAt(0)) + '?' + parts.join('?');
}
final match = RegExp(r'^([#!@+][^:]*:[^\/?]*)(?:\/(\$[^?]*))?(?:\?(.*))?$')
.firstMatch(s);
if (match == null ||
!match.group(1).isValidMatrixId ||
!(match.group(2)?.isValidMatrixId ?? true)) {
return null;
}
final queryString =
match.group(3)?.isNotEmpty ?? false ? match.group(3) : null;
parseQueryString(queryString);
return MatrixIdentifierStringExtensionResults(
primaryIdentifier: match.group(1),
secondaryIdentifier: match.group(2),
queryString: queryString,
via: via,
action: action,
);
}
}
class MatrixIdentifierStringExtensionResults {
final String primaryIdentifier;
final String secondaryIdentifier;
final String queryString;
final Set<String> via;
final String action;
MatrixIdentifierStringExtensionResults(
{this.primaryIdentifier,
this.secondaryIdentifier,
this.queryString,
this.via,
this.action});
}

View File

@ -46,5 +46,77 @@ void main() {
expect('@user:domain:8448'.localpart, 'user');
expect('@user:domain:8448'.domain, 'domain:8448');
});
test('parseIdentifierIntoParts', () {
var res = '#alias:beep'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, null);
expect(res.queryString, null);
res = 'blha'.parseIdentifierIntoParts();
expect(res, null);
res = '#alias:beep/\$event'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, '\$event');
expect(res.queryString, null);
res = '#alias:beep?blubb'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, null);
expect(res.queryString, 'blubb');
res = '#alias:beep/\$event?blubb'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, '\$event');
expect(res.queryString, 'blubb');
res = '#/\$?:beep/\$event?blubb?b'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#/\$?:beep');
expect(res.secondaryIdentifier, '\$event');
expect(res.queryString, 'blubb?b');
res = 'https://matrix.to/#/#alias:beep'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, null);
expect(res.queryString, null);
res = 'https://matrix.to/#/%23alias%3abeep'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, null);
expect(res.queryString, null);
res = 'https://matrix.to/#/%23alias%3abeep?boop%F0%9F%A7%A1%F0%9F%A6%8A'
.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#alias:beep');
expect(res.secondaryIdentifier, null);
expect(res.queryString, 'boop%F0%9F%A7%A1%F0%9F%A6%8A');
res = 'https://matrix.to/#/#alias:beep?via=fox.com&via=fox.org'
.parseIdentifierIntoParts();
expect(res.via, <String>{'fox.com', 'fox.org'});
res = 'matrix:user/her:example.org'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '@her:example.org');
expect(res.secondaryIdentifier, null);
res = 'matrix:user/bad'.parseIdentifierIntoParts();
expect(res, null);
res = 'matrix:roomid/rid:example.org'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '!rid:example.org');
expect(res.secondaryIdentifier, null);
expect(res.action, null);
res = 'matrix:room/us:example.org?action=chat'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#us:example.org');
expect(res.secondaryIdentifier, null);
expect(res.action, 'chat');
res = 'matrix:room/us:example.org/event/lol823y4bcp3qo4'
.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '#us:example.org');
expect(res.secondaryIdentifier, '\$lol823y4bcp3qo4');
res = 'matrix:group/them:example.org'.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '+them:example.org');
expect(res.secondaryIdentifier, null);
res = 'matrix:roomid/rid:example.org?via=fox.com&via=fox.org'
.parseIdentifierIntoParts();
expect(res.primaryIdentifier, '!rid:example.org');
expect(res.secondaryIdentifier, null);
expect(res.via, <String>{'fox.com', 'fox.org'});
res = 'matrix:beep/boop:example.org'.parseIdentifierIntoParts();
expect(res, null);
res = 'matrix:boop'.parseIdentifierIntoParts();
expect(res, null);
});
});
}