Implement support for the HAProxy proxy protocol
[quassel.git] / src / common / ircdecoder.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2020 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 QByteArray IrcDecoder::extractFragment(const QByteArray& raw, int& start, int end, char prefix)
67 {
68     // Try to set find the end of the space-delimited fragment
69     if (end == -1) {
70         end = raw.indexOf(' ', start);
71     }
72     // If no space comes after this point, use the remainder of the string
73     if (end == -1) {
74         end = raw.length();
75     }
76     QByteArray fragment;
77     // If a prefix is set
78     if (prefix != 0) {
79         // And the fragment starts with the prefix
80         if (start < raw.length() && raw[start] == prefix) {
81             // return the fragment without the prefix, advancing the string
82             fragment = raw.mid(start + 1, end - start - 1);
83             start = end;
84         }
85     }
86     else {
87         // otherwise return the entire fragment
88         fragment = raw.mid(start, end - start);
89         start = end;
90     }
91     return fragment;
92 }
93
94 void IrcDecoder::skipEmptyParts(const QByteArray& raw, int& start)
95 {
96     while (start < raw.length() && raw[start] == ' ') {
97         start++;
98     }
99 }
100
101 QHash<IrcTagKey, QString> IrcDecoder::parseTags(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, int& start)
102 {
103     QHash<IrcTagKey, QString> tags = {};
104     QString rawTagStr = decode(extractFragment(raw, start, -1, '@'));
105     // Tags are delimited with ; according to spec
106     QList<QString> rawTags = rawTagStr.split(';');
107     for (const QString& rawTag : rawTags) {
108         if (rawTag.isEmpty()) {
109             continue;
110         }
111
112         QString rawKey;
113         QString rawValue;
114         int index = rawTag.indexOf('=');
115         if (index == -1 || index == rawTag.length()) {
116             rawKey = rawTag;
117         }
118         else {
119             rawKey = rawTag.left(index);
120             rawValue = rawTag.mid(index + 1);
121         }
122
123         IrcTagKey key{};
124         key.clientTag = rawKey.startsWith('+');
125         if (key.clientTag) {
126             rawKey.remove(0, 1);
127         }
128         QList<QString> splitByVendorAndKey = rawKey.split('/');
129         if (!splitByVendorAndKey.isEmpty()) key.key = splitByVendorAndKey.takeLast();
130         if (!splitByVendorAndKey.isEmpty()) key.vendor = splitByVendorAndKey.takeLast();
131         tags[key] = parseTagValue(rawValue);
132     }
133     return tags;
134 }
135
136 QString IrcDecoder::parsePrefix(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, int& start)
137 {
138     return decode(extractFragment(raw, start, -1, ':'));
139 }
140
141 QString IrcDecoder::parseCommand(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, int& start)
142 {
143     return decode(extractFragment(raw, start, -1));
144 }
145
146 QByteArray IrcDecoder::parseParameter(const QByteArray& raw, int& start)
147 {
148     if (start < raw.length() && raw[start] == ':') {
149         // Skip the prefix
150         start++;
151         return extractFragment(raw, start, raw.size());
152     }
153     else {
154         return extractFragment(raw, start);
155     }
156 }
157
158 void IrcDecoder::parseMessage(const std::function<QString(const QByteArray&)>& decode, const QByteArray& rawMsg, QHash<IrcTagKey, QString>& tags, QString& prefix, QString& command, QList<QByteArray>& parameters)
159 {
160     int start = 0;
161     skipEmptyParts(rawMsg, start);
162     tags = parseTags(decode, rawMsg, start);
163     skipEmptyParts(rawMsg, start);
164     prefix = parsePrefix(decode, rawMsg, start);
165     skipEmptyParts(rawMsg, start);
166     command = parseCommand(decode, rawMsg, start);
167     skipEmptyParts(rawMsg, start);
168     QList<QByteArray> params;
169     while (start != rawMsg.length()) {
170         QByteArray param = parseParameter(rawMsg, start);
171         skipEmptyParts(rawMsg, start);
172         params.append(param);
173
174     }
175     parameters = params;
176 }