feat: Add identifier string parsing
This commit is contained in:
parent
4d2c4df4d4
commit
19d96595cc
|
|
@ -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});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue