File indexing completed on 2024-04-28 05:50:05

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 #include "uri/uri.h"
0006 
0007 #include <QTest>
0008 #include <QtDebug>
0009 
0010 Q_DECLARE_METATYPE(uri::QrParts::Type);
0011 
0012 class UriParsingTest: public QObject // clazy:exclude=ctor-missing-parent-argument
0013 {
0014     Q_OBJECT
0015 private Q_SLOTS:
0016     void testValid(void);
0017     void testValid_data(void);
0018     void testInvalid(void);
0019     void testInvalid_data(void);
0020 };
0021 
0022 static void define_valid_test_data(void)
0023 {
0024     QTest::addColumn<QString>("input");
0025     QTest::addColumn<uri::QrParts::Type>("type");
0026     QTest::addColumn<QString>("name");
0027     QTest::addColumn<QString>("issuer");
0028     QTest::addColumn<QString>("secret");
0029     QTest::addColumn<QString>("timeStep");
0030     QTest::addColumn<QString>("tokenLength");
0031     QTest::addColumn<QString>("algorithm");
0032     QTest::addColumn<QString>("counter");
0033 }
0034 
0035 static void define_valid_test_case(const char *testCase, const QString &input, uri::QrParts::Type type,
0036                                    const QString &name, const QString &issuer, const QString &secret,
0037                                    const QString &timeStep, const QString &tokenLength, const QString &algorithm,
0038                                    const QString &counter)
0039 {
0040     QTest::newRow(testCase) << input
0041         << type << name << issuer << secret << timeStep << tokenLength << algorithm << counter;
0042 }
0043 
0044 void UriParsingTest::testValid(void)
0045 {
0046     QFETCH(QString, input);
0047 
0048     const std::optional<uri::QrParts> result = uri::QrParts::parse(input);
0049     QVERIFY2(result, "should parse valid input successfully");
0050     QTEST(result->type(), "type");
0051     QTEST(result->name(), "name");
0052     QTEST(result->issuer(), "issuer");
0053     QTEST(result->secret(), "secret");
0054     QTEST(result->tokenLength(), "tokenLength");
0055     QTEST(result->algorithm(), "algorithm");
0056     QTEST(result->counter(), "counter");
0057 }
0058 
0059 void UriParsingTest::testValid_data(void)
0060 {
0061     define_valid_test_data();
0062     define_valid_test_case("hotp (all fields)",
0063                            QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42"),
0064                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QStringLiteral("issuer"),
0065                            QStringLiteral("VALUE"), QStringLiteral("30"), QStringLiteral("6"), QStringLiteral("sha1"),
0066                            QStringLiteral("42"));
0067     define_valid_test_case("hotp (all fields, URI encoded label separator)",
0068                            QStringLiteral("otpauth://hotp/issuer%3Ana:me?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42"),
0069                            uri::QrParts::Type::Hotp, QStringLiteral("na:me"), QStringLiteral("issuer"),
0070                            QStringLiteral("VALUE"), QStringLiteral("30"), QStringLiteral("6"), QStringLiteral("sha1"),
0071                            QStringLiteral("42"));
0072     define_valid_test_case("hotp (minimal)",
0073                            QStringLiteral("otpauth://hotp/name?secret=VALUE&counter=42"),
0074                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QString(), QStringLiteral("VALUE"),
0075                            QString(), QString(), QString(), QStringLiteral("42"));
0076     define_valid_test_case("hotp (minimal, without name and missing params)",
0077                            QStringLiteral("otpauth://hotp?secret=VALUE"),
0078                            uri::QrParts::Type::Hotp, QString(), QString(), QStringLiteral("VALUE"), QString(),
0079                            QString(), QString(), QString());
0080     define_valid_test_case("hotp (issuer only in label)",
0081                            QStringLiteral("otpauth://hotp/issuer:name?secret=VALUE&counter=42"),
0082                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QStringLiteral("issuer"),
0083                            QStringLiteral("VALUE"), QString(), QString(), QString(), QStringLiteral("42"));
0084     define_valid_test_case("hotp (issuer only as param)",
0085                            QStringLiteral("otpauth://hotp/name?secret=VALUE&counter=42&issuer=issuer"),
0086                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QStringLiteral("issuer"),
0087                            QStringLiteral("VALUE"), QString(), QString(), QString(), QStringLiteral("42"));
0088     define_valid_test_case("hotp (inconsistent issuer)",
0089                            QStringLiteral("otpauth://hotp/issuer:name?secret=VALUE&counter=42&issuer=other"),
0090                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QStringLiteral("issuer"),
0091                            QStringLiteral("VALUE"), QString(), QString(), QString(), QStringLiteral("42"));
0092     define_valid_test_case("hotp (empty counter)",
0093                            QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter="),
0094                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QStringLiteral("issuer"),
0095                            QStringLiteral("VALUE"), QStringLiteral("30"), QStringLiteral("6"), QStringLiteral("sha1"),
0096                            QString());
0097     define_valid_test_case("hotp (missing counter)",
0098                            QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1"),
0099                            uri::QrParts::Type::Hotp, QStringLiteral("name"), QStringLiteral("issuer"),
0100                            QStringLiteral("VALUE"), QStringLiteral("30"), QStringLiteral("6"), QStringLiteral("sha1"),
0101                            QString());
0102     define_valid_test_case("totp (all fields, including redundant counter)",
0103                            QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42"),
0104                            uri::QrParts::Type::Totp, QStringLiteral("name"), QStringLiteral("issuer"),
0105                            QStringLiteral("VALUE"), QStringLiteral("30"), QStringLiteral("6"), QStringLiteral("sha1"),
0106                            QStringLiteral("42"));
0107     define_valid_test_case("totp (all fields, except redundant counter)",
0108                            QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1"),
0109                            uri::QrParts::Type::Totp, QStringLiteral("name"), QStringLiteral("issuer"),
0110                            QStringLiteral("VALUE"), QStringLiteral("30"), QStringLiteral("6"), QStringLiteral("sha1"),
0111                            QString());
0112     define_valid_test_case("totp (minimal)",
0113                            QStringLiteral("otpauth://totp/name?secret=VALUE"),
0114                            uri::QrParts::Type::Totp, QStringLiteral("name"), QString(), QStringLiteral("VALUE"), QString(),
0115                            QString(), QString(), QString());
0116     define_valid_test_case("totp (minimal, without name)",
0117                            QStringLiteral("otpauth://totp?secret=VALUE"),
0118                            uri::QrParts::Type::Totp, QString(), QString(), QStringLiteral("VALUE"), QString(),
0119                            QString(), QString(), QString());
0120     define_valid_test_case("hotp (with padding in secret)",
0121                            QStringLiteral("otpauth://hotp?secret=VALUE==="),
0122                            uri::QrParts::Type::Hotp, QString(), QString(), QStringLiteral("VALUE==="), QString(),
0123                            QString(), QString(), QString());
0124     define_valid_test_case("totp (with padding in secret)",
0125                            QStringLiteral("otpauth://totp?secret=VALUE===&period=30"),
0126                            uri::QrParts::Type::Totp, QString(), QString(), QStringLiteral("VALUE==="),
0127                            QStringLiteral("30"), QString(), QString(), QString());
0128     define_valid_test_case("totp (Microsoft organization account)",
0129                            QStringLiteral("otpauth://totp/Mega%20Corporation%20A.B.%20%26%20Co.%20Kg%3Auser%40domain?secret=abcdef&issuer=Microsoft"),
0130                            uri::QrParts::Type::Totp, QStringLiteral("user@domain"), QStringLiteral("Mega Corporation A.B. & Co. Kg"), QStringLiteral("abcdef"),
0131                            QString(), QString(), QString(), QString());
0132 }
0133 
0134 void UriParsingTest::testInvalid(void)
0135 {
0136     QFETCH(QString, input);
0137     QVERIFY2(!uri::QrParts::parse(input), "should reject invalid input");
0138 }
0139 
0140 void UriParsingTest::testInvalid_data(void)
0141 {
0142     QTest::addColumn<QString>("input");
0143 
0144     QTest::newRow("wrong scheme") << QStringLiteral("http://localhost");
0145     QTest::newRow("missing type") << QStringLiteral("otpauth:///issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0146     QTest::newRow("unsupported type") << QStringLiteral("otpauth://wrong/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0147     QTest::newRow("invalid param") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42&foo=bar");
0148     QTest::newRow("missing secret") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&period=30&digits=6&algorithm=sha1&counter=42");
0149     QTest::newRow("empty secret") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=&period=30&digits=6&algorithm=sha1&counter=42");
0150     QTest::newRow("secret = bad utf8") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=%c0%7fALUE&period=30&digits=6&algorithm=sha1&counter=42");
0151     QTest::newRow("secret = invalid percent encoding") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=%VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0152     QTest::newRow("counter = bad utf8") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=%fc%80%80%80%80%80%80");
0153     QTest::newRow("counter = invalid percent encoding") << QStringLiteral("otpauth://hotp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42%");
0154     QTest::newRow("name label = bad utf8") << QStringLiteral("otpauth://hotp/issuer:name%c1%00?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0155     QTest::newRow("name label = invalid percent encoding") << QStringLiteral("otpauth://hotp/issuer:n%#ame?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0156     QTest::newRow("issuer label = bad utf8") << QStringLiteral("otpauth://hotp/issuer%90:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0157     QTest::newRow("issuer label = invalid percent encoding") << QStringLiteral("otpauth://hotp/is%suer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha1&counter=42");
0158     QTest::newRow("issuer param = bad utf8") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer%cf&secret=VALUE&period=30&digits=%80&algorithm=sha1");
0159     QTest::newRow("issuer param = invalid percent encoding") << QStringLiteral("otpauth://totp/issuer:name?issuer=issu%er&secret=VALUE&period=30&digits=%S&algorithm=sha1");
0160     QTest::newRow("digits = bad utf8") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=%80&algorithm=sha1");
0161     QTest::newRow("digits = invalid percent encoding") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=%S&algorithm=sha1");
0162     QTest::newRow("period = bad utf8") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=%c0&digits=6&algorithm=sha1");
0163     QTest::newRow("period = invalid percent encoding") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=3%&digits=6&algorithm=sha1");
0164     QTest::newRow("algorithm = bad utf8") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha%ff");
0165     QTest::newRow("algorithm = invalid percent encoding") << QStringLiteral("otpauth://totp/issuer:name?issuer=issuer&secret=VALUE&period=30&digits=6&algorithm=sha%1");
0166     QTest::newRow("hotp without params") << QStringLiteral("otpauth://hotp/issuer:name");
0167     QTest::newRow("totp without params") << QStringLiteral("otpauth://hotp/name");
0168 }
0169 
0170 QTEST_APPLESS_MAIN(UriParsingTest)
0171 
0172 #include "qr-parsing.moc"