qa: Replace deprecated qVariantFromValue() by QVariant::fromValue()
[quassel.git] / src / common / ircdecoder.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "ircdecoder.h"
22
23 #include <QDebug>
24
25 #include "irctag.h"
26
27 QString IrcDecoder::parseTagValue(const QString& value)
28 {
29     QString result;
30     bool escaped = false;
31     for (auto it = value.begin(); it < value.end(); it++) {
32         // Check if it's on the list of special wildcard characters, converting to Unicode for use
33         // in the switch statement
34         //
35         // See https://doc.qt.io/qt-5/qchar.html#unicode
36         if (escaped) {
37             switch (it->unicode()) {
38             case '\\':
39                 result.append('\\');
40                 break;
41             case 's':
42                 result.append(' ');
43                 break;
44             case ':':
45                 result.append(';');
46                 break;
47             case 'r':
48                 result.append('\r');
49                 break;
50             case 'n':
51                 result.append('\n');
52                 break;
53             default:
54                 result.append(*it);
55             }
56             escaped = false;
57         } else if (it->unicode() == '\\') {
58             escaped = true;
59         } else {
60             result.append(*it);
61         }
62     }
63     return result;
64 }
65
66 /**
67  * Extracts a space-delimited fragment from an IRC message
68  * @param raw Raw Message
69  * @param start Current index into the message, will be advanced automatically
70  * @param end End of fragment, if already known. Default is -1, in which case it will be set to the next whitespace
71  * character or the end of the string
72  * @param prefix Required prefix. Default is 0. If set, this only parses a fragment if it starts with the given prefix.
73  * @return Fragment
74  */
75 QByteArray extractFragment(const QByteArray& raw, int& start, int end = -1, char prefix = 0)
76 {
77     // Try to set find the end of the space-delimited fragment
78     if (end == -1) {
79         end = raw.indexOf(' ', start);
80     }
81     // If no space comes after this point, use the remainder of the string
82     if (end == -1) {
83         end = raw.length();
84     }
85     QByteArray fragment;
86     // If a prefix is set
87     if (prefix != 0) {
88         // And the fragment starts with the prefix
89         if (start < raw.length() && raw[start] == prefix) {
90             // return the fragment without the prefix, advancing the string
91             fragment = raw.mid(start + 1, end - start - 1);
92             start = end;
93         }
94     }
95     else {
96         // otherwise return the entire fragment
97         fragment = raw.mid(start, end - start);
98         start = end;
99     }
100     return fragment;
101 }
102
103 /**
104  * Skips empty parts in the message
105  * @param raw Raw Message
106  * @param start Current index into the message, will be advanced  automatically
107  */
108 void skipEmptyParts(const QByteArray& raw, int& start)
109 {
110     while (start < raw.length() && raw[start] == ' ') {
111         start++;
112     }
113 }
114
115 QHash<IrcTagKey, QString> IrcDecoder::parseTags(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, int& start)
116 {
117     QHash<IrcTagKey, QString> tags = {};
118     QString rawTagStr = decode(extractFragment(raw, start, -1, '@'));
119     // Tags are delimited with ; according to spec
120     QList<QString> rawTags = rawTagStr.split(';');
121     for (const QString& rawTag : rawTags) {
122         if (rawTag.isEmpty()) {
123             continue;
124         }
125
126         QString rawKey;
127         QString rawValue;
128         int index = rawTag.indexOf('=');
129         if (index == -1 || index == rawTag.length()) {
130             rawKey = rawTag;
131         }
132         else {
133             rawKey = rawTag.left(index);
134             rawValue = rawTag.mid(index + 1);
135         }
136
137         IrcTagKey key{};
138         key.clientTag = rawKey.startsWith('+');
139         if (key.clientTag) {
140             rawKey.remove(0, 1);
141         }
142         QList<QString> splitByVendorAndKey = rawKey.split('/');
143         if (!splitByVendorAndKey.isEmpty()) key.key = splitByVendorAndKey.takeLast();
144         if (!splitByVendorAndKey.isEmpty()) key.vendor = splitByVendorAndKey.takeLast();
145         tags[key] = parseTagValue(rawValue);
146     }
147     return tags;
148 }
149
150 QString IrcDecoder::parsePrefix(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, int& start)
151 {
152     return decode(extractFragment(raw, start, -1, ':'));
153 }
154
155 QString IrcDecoder::parseCommand(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, int& start)
156 {
157     return decode(extractFragment(raw, start, -1));
158 }
159
160 QByteArray IrcDecoder::parseParameter(const QByteArray& raw, int& start)
161 {
162     if (start < raw.length() && raw[start] == ':') {
163         // Skip the prefix
164         start++;
165         return extractFragment(raw, start, raw.size());
166     }
167     else {
168         return extractFragment(raw, start);
169     }
170 }
171
172 void IrcDecoder::parseMessage(const std::function<QString(const QByteArray&)>& decode, const QByteArray& rawMsg, QHash<IrcTagKey, QString>& tags, QString& prefix, QString& command, QList<QByteArray>& parameters)
173 {
174     int start = 0;
175     skipEmptyParts(rawMsg, start);
176     tags = parseTags(decode, rawMsg, start);
177     skipEmptyParts(rawMsg, start);
178     prefix = parsePrefix(decode, rawMsg, start);
179     skipEmptyParts(rawMsg, start);
180     command = parseCommand(decode, rawMsg, start);
181     skipEmptyParts(rawMsg, start);
182     QList<QByteArray> params;
183     while (start != rawMsg.length()) {
184         QByteArray param = parseParameter(rawMsg, start);
185         skipEmptyParts(rawMsg, start);
186         params.append(param);
187
188     }
189     parameters = params;
190 }