Coverage Report

Created: 2023-09-25 06:17

/src/znc/src/Message.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2004-2023 ZNC, see the NOTICE file for details.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <znc/Message.h>
18
#include <znc/Utils.h>
19
#include "bpstd/string_view.hpp"
20
21
1.90k
CMessage::CMessage(const CString& sMessage) {
22
1.90k
    Parse(sMessage);
23
1.90k
    InitTime();
24
1.90k
}
25
26
CMessage::CMessage(const CNick& Nick, const CString& sCommand,
27
                   const VCString& vsParams, const MCString& mssTags)
28
    : m_Nick(Nick),
29
      m_sCommand(sCommand),
30
      m_vsParams(vsParams),
31
0
      m_mssTags(mssTags) {
32
0
    InitTime();
33
0
}
34
35
0
bool CMessage::Equals(const CMessage& Other) const {
36
0
    return m_Nick.NickEquals(Other.GetNick().GetNick()) &&
37
0
           m_sCommand.Equals(Other.GetCommand()) &&
38
0
           m_vsParams == Other.GetParams();
39
0
}
40
41
0
void CMessage::Clone(const CMessage& Message) {
42
0
    if (&Message != this) {
43
0
        *this = Message;
44
0
    }
45
0
}
46
47
0
void CMessage::SetCommand(const CString& sCommand) {
48
0
    m_sCommand = sCommand;
49
0
    InitType();
50
0
}
51
52
0
CString CMessage::GetParamsColon(unsigned int uIdx, unsigned int uLen) const {
53
0
    if (m_vsParams.empty() || uLen == 0) {
54
0
        return "";
55
0
    }
56
0
    if (uLen > m_vsParams.size() - uIdx - 1) {
57
0
        uLen = m_vsParams.size() - uIdx;
58
0
    }
59
0
    VCString vsParams;
60
0
    unsigned uParams = m_vsParams.size();
61
0
    for (unsigned int i = uIdx; i < uIdx + uLen; ++i) {
62
0
        CString sParam = m_vsParams[i];
63
0
        if (i == uParams - 1 &&
64
0
            (m_bColon || sParam.empty() || sParam.StartsWith(":") ||
65
0
             sParam.Contains(" "))) {
66
0
            sParam = ":" + sParam;
67
0
        }
68
0
        vsParams.push_back(sParam);
69
0
    }
70
0
    return CString(" ").Join(vsParams.begin(), vsParams.end());
71
0
}
72
73
0
void CMessage::SetParams(const VCString& vsParams) {
74
0
    m_vsParams = vsParams;
75
0
    m_bColon = false;
76
77
0
    if (m_eType == Type::Text || m_eType == Type::Notice ||
78
0
        m_eType == Type::Action || m_eType == Type::CTCP) {
79
0
        InitType();
80
0
    }
81
0
}
82
83
402
CString CMessage::GetParam(unsigned int uIdx) const {
84
402
    if (uIdx >= m_vsParams.size()) {
85
8
        return "";
86
8
    }
87
394
    return m_vsParams[uIdx];
88
402
}
89
90
1.90k
void CMessage::SetParam(unsigned int uIdx, const CString& sParam) {
91
1.90k
    if (uIdx >= m_vsParams.size()) {
92
1.64k
        m_vsParams.resize(uIdx + 1);
93
1.64k
    }
94
1.90k
    m_vsParams[uIdx] = sParam;
95
96
1.90k
    if (uIdx == 1 && (m_eType == Type::Text || m_eType == Type::Notice ||
97
1.90k
                      m_eType == Type::Action || m_eType == Type::CTCP)) {
98
201
        InitType();
99
201
    }
100
1.90k
}
101
102
0
CString CMessage::GetTag(const CString& sKey) const {
103
0
    MCString::const_iterator it = m_mssTags.find(sKey);
104
0
    if (it != m_mssTags.end()) {
105
0
        return it->second;
106
0
    }
107
0
    return "";
108
0
}
109
110
0
void CMessage::SetTag(const CString& sKey, const CString& sValue) {
111
0
    m_mssTags[sKey] = sValue;
112
0
}
113
114
0
CString CMessage::ToString(unsigned int uFlags) const {
115
0
    CString sMessage;
116
117
    // <tags>
118
0
    if (!(uFlags & ExcludeTags) && !m_mssTags.empty()) {
119
0
        CString sTags;
120
0
        for (const auto& it : m_mssTags) {
121
0
            if (!sTags.empty()) {
122
0
                sTags += ";";
123
0
            }
124
0
            sTags += it.first;
125
0
            if (!it.second.empty())
126
0
                sTags += "=" + it.second.Escape_n(CString::EMSGTAG);
127
0
        }
128
0
        sMessage = "@" + sTags;
129
0
    }
130
131
    // <prefix>
132
0
    if (!(uFlags & ExcludePrefix)) {
133
0
        CString sPrefix = m_Nick.GetHostMask();
134
0
        if (!sPrefix.empty()) {
135
0
            if (!sMessage.empty()) {
136
0
                sMessage += " ";
137
0
            }
138
0
            sMessage += ":" + sPrefix;
139
0
        }
140
0
    }
141
142
    // <command>
143
0
    if (!m_sCommand.empty()) {
144
0
        if (!sMessage.empty()) {
145
0
            sMessage += " ";
146
0
        }
147
0
        sMessage += m_sCommand;
148
0
    }
149
150
    // <params>
151
0
    if (!m_vsParams.empty()) {
152
0
        if (!sMessage.empty()) {
153
0
            sMessage += " ";
154
0
        }
155
0
        sMessage += GetParamsColon(0);
156
0
    }
157
158
0
    return sMessage;
159
0
}
160
161
3.81k
void CMessage::Parse(const CString& sMessage) {
162
3.81k
    const char* begin = sMessage.c_str();
163
3.81k
    const char* const end = begin + sMessage.size();
164
2.66M
    auto next_word = [&]() {
165
        // Find the end of the first word
166
2.66M
        const char* p = begin;
167
74.8M
        while (p < end && *p != ' ') ++p;
168
2.66M
        bpstd::string_view result(begin, p - begin);
169
2.66M
        begin = p;
170
        // Prepare for the following word
171
5.37M
        while (begin < end && *begin == ' ') ++begin;
172
2.66M
        return result;
173
2.66M
    };
174
175
    // <tags>
176
3.81k
    m_mssTags.clear();
177
3.81k
    if (begin < end && *begin == '@') {
178
945
        bpstd::string_view svTags = next_word().substr(1);
179
945
        std::vector<bpstd::string_view> vsTags;
180
        // Split by ';'
181
2.13M
        while (true) {
182
2.13M
            auto delim = svTags.find_first_of(';');
183
2.13M
            if (delim == bpstd::string_view::npos) {
184
945
                vsTags.push_back(svTags);
185
945
                break;
186
945
            }
187
2.13M
            vsTags.push_back(svTags.substr(0, delim));
188
2.13M
            svTags = svTags.substr(delim + 1);
189
2.13M
        }
190
        // Save key and value
191
2.13M
        for (bpstd::string_view svTag : vsTags) {
192
2.13M
            auto delim = svTag.find_first_of('=');
193
2.13M
            CString sKey = std::string(delim == bpstd::string_view::npos ? svTag : svTag.substr(0, delim));
194
2.13M
            CString sValue = delim == bpstd::string_view::npos ? std::string() : std::string(svTag.substr(delim + 1));
195
2.13M
            m_mssTags[sKey] =
196
2.13M
                sValue.Escape(CString::EMSGTAG, CString::CString::EASCII);
197
2.13M
        }
198
945
    }
199
200
    //  <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
201
    //  <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
202
    //  <command>  ::= <letter> { <letter> } | <number> <number> <number>
203
    //  <SPACE>    ::= ' ' { ' ' }
204
    //  <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
205
    //  <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
206
    //                 or NUL or CR or LF, the first of which may not be ':'>
207
    //  <trailing> ::= <Any, possibly *empty*, sequence of octets not including
208
    //                   NUL or CR or LF>
209
210
    // <prefix>
211
3.81k
    if (begin < end && *begin == ':') {
212
177
        m_Nick.Parse(std::string(next_word().substr(1)));
213
177
    }
214
215
    // <command>
216
3.81k
    m_sCommand = std::string(next_word());
217
218
    // <params>
219
3.81k
    m_bColon = false;
220
3.81k
    m_vsParams.clear();
221
2.66M
    while (begin < end) {
222
2.65M
        m_bColon = *begin == ':';
223
2.65M
        if (m_bColon) {
224
65
            ++begin;
225
65
            m_vsParams.push_back(std::string(begin, end - begin));
226
65
      begin = end;
227
2.65M
        } else {
228
2.65M
            m_vsParams.push_back(std::string(next_word()));
229
2.65M
        }
230
2.65M
    }
231
232
3.81k
    InitType();
233
3.81k
}
234
235
1.90k
void CMessage::InitTime() {
236
1.90k
    auto it = m_mssTags.find("time");
237
1.90k
    if (it != m_mssTags.end()) {
238
0
        m_time = CUtils::ParseServerTime(it->second);
239
0
        return;
240
0
    }
241
242
1.90k
    m_time = CUtils::GetTime();
243
1.90k
}
244
245
4.01k
void CMessage::InitType() {
246
4.01k
    if (m_sCommand.length() == 3 && isdigit(m_sCommand[0]) &&
247
4.01k
        isdigit(m_sCommand[1]) && isdigit(m_sCommand[2])) {
248
6
        m_eType = Type::Numeric;
249
4.01k
    } else if (m_sCommand.Equals("PRIVMSG")) {
250
246
        CString sParam = GetParam(1);
251
246
        if (sParam.TrimPrefix("\001") && sParam.EndsWith("\001")) {
252
72
            if (sParam.StartsWith("ACTION ")) {
253
1
                m_eType = Type::Action;
254
71
            } else {
255
71
                m_eType = Type::CTCP;
256
71
            }
257
174
        } else {
258
174
            m_eType = Type::Text;
259
174
        }
260
3.76k
    } else if (m_sCommand.Equals("NOTICE")) {
261
156
        CString sParam = GetParam(1);
262
156
        if (sParam.StartsWith("\001") && sParam.EndsWith("\001")) {
263
1
            m_eType = Type::CTCP;
264
155
        } else {
265
155
            m_eType = Type::Notice;
266
155
        }
267
3.60k
    } else {
268
3.60k
        std::map<CString, Type> mTypes = {
269
3.60k
            {"ACCOUNT", Type::Account},
270
3.60k
            {"AWAY", Type::Away},
271
3.60k
            {"CAP", Type::Capability},
272
3.60k
            {"ERROR", Type::Error},
273
3.60k
            {"INVITE", Type::Invite},
274
3.60k
            {"JOIN", Type::Join},
275
3.60k
            {"KICK", Type::Kick},
276
3.60k
            {"MODE", Type::Mode},
277
3.60k
            {"NICK", Type::Nick},
278
3.60k
            {"PART", Type::Part},
279
3.60k
            {"PING", Type::Ping},
280
3.60k
            {"PONG", Type::Pong},
281
3.60k
            {"QUIT", Type::Quit},
282
3.60k
            {"TOPIC", Type::Topic},
283
3.60k
            {"WALLOPS", Type::Wallops},
284
3.60k
        };
285
3.60k
        auto it = mTypes.find(m_sCommand.AsUpper());
286
3.60k
        if (it != mTypes.end()) {
287
9
            m_eType = it->second;
288
3.60k
        } else {
289
3.60k
            m_eType = Type::Unknown;
290
3.60k
        }
291
3.60k
    }
292
4.01k
}
293
294
0
VCString CMessage::GetParamsSplit(unsigned int uIdx, unsigned int uLen) const {
295
0
    VCString splitParams;
296
0
    const VCString &params = GetParams();
297
298
0
    if (params.empty() || uLen == 0 || uIdx >= params.size()) {
299
0
        return splitParams;
300
0
    }
301
302
0
    if (uLen > params.size() - uIdx - 1) {
303
0
        uLen = params.size() - uIdx;
304
0
    }
305
306
0
    VCString::const_iterator startIt = params.begin() + uIdx;
307
0
    VCString::const_iterator endIt = startIt + uLen;
308
309
0
    splitParams.assign(startIt, endIt);
310
311
0
    return splitParams;
312
0
}