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 ¶ms = 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 | } |