/* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ extension MatrixIdExtension on String { static const Set VALID_SIGILS = {'@', '!', '#', '\$', '+'}; static const int MAX_LENGTH = 255; List _getParts() { final s = substring(1); final ix = s.indexOf(':'); if (ix == -1) { return [substring(1)]; } return [s.substring(0, ix), s.substring(ix + 1)]; } bool get isValidMatrixId { if (isEmpty ?? true) return false; if (length > MAX_LENGTH) return false; if (!VALID_SIGILS.contains(substring(0, 1))) { return false; } // event IDs do not have to have a domain if (substring(0, 1) == '\$') { return true; } // all other matrix IDs have to have a domain final parts = _getParts(); // the localpart can be an empty string, e.g. for aliases if (parts.length != 2 || parts[1].isEmpty) { return false; } return true; } String get sigil => isValidMatrixId ? substring(0, 1) : null; String get localpart => isValidMatrixId ? _getParts().first : null; 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 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 = []; for (var i = 0; i < pathSegments.length - 1; i += 2) { final thisSigil = { 'u': '@', 'roomid': '!', 'r': '#', 'e': '\$', }[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 via; final String action; MatrixIdentifierStringExtensionResults( {this.primaryIdentifier, this.secondaryIdentifier, this.queryString, this.via, this.action}); }