1 //=============================================================================
2 //
3 //   File : KviIrcServerParser_ctcp.cpp
4 //   Creation date : Thu Aug 16 2000 13:34:42 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 // FIXME: #warning "CTCP BEEP == WAKEUP == AWAKE"
26 // FIXME: #warning "CTCP AVATARREQ or QUERYAVATAR"
27 
28 #include "KviControlCodes.h"
29 #include "KviRuntimeInfo.h"
30 #include "KviApplication.h"
31 #include "KviIrcServerParser.h"
32 #include "KviWindow.h"
33 #include "kvi_out.h"
34 #include "KviLocale.h"
35 #include "KviIrcSocket.h"
36 #include "KviChannelWindow.h"
37 #include "kvi_defaults.h"
38 #include "KviQueryWindow.h"
39 #include "KviIrcUserDataBase.h"
40 #include "KviIconManager.h"
41 #include "KviModuleManager.h"
42 #include "KviSharedFilesManager.h"
43 #include "KviTimeUtils.h"
44 #include "KviFileUtils.h"
45 #include "KviCtcpPageDialog.h"
46 #include "KviUserAction.h"
47 #include "KviOptions.h"
48 #include "KviIrcConnection.h"
49 #include "KviIrcConnectionUserInfo.h"
50 #include "KviIrcConnectionAntiCtcpFloodData.h"
51 #include "KviLagMeter.h"
52 #include "KviKvsEventTriggers.h"
53 #include "KviKvsScript.h"
54 #include "kvi_sourcesdate.h"
55 #include "KviRegisteredUserDataBase.h"
56 #include "KviBuildInfo.h"
57 #include "KviIrcConnectionServerInfo.h"
58 #include "KviIrcMessage.h"
59 
60 #ifdef COMPILE_CRYPT_SUPPORT
61 #include "KviCryptEngine.h"
62 #include "KviCryptController.h"
63 #endif //COMPILE_CRYPT_SUPPORT
64 
65 #include <cstdlib>
66 
67 #include <QDateTime>
68 #include <QLocale>
69 
70 extern KVIRC_API KviSharedFilesManager * g_pSharedFilesManager;
71 extern KVIRC_API KviCtcpPageDialog * g_pCtcpPageDialog;
72 
73 /*
74 	@doc: ctcp_handling
75 	@title:
76 		KVIrc and CTCP
77 	@short:
78 		For developers: Client-To-Client Protocol handling in KVIrc
79 	@body:
80 		[big]Introduction[/big]
81 		Personally, I think that the CTCP specification is to
82 		be symbolically printed & burned. It is really too complex
83 		(you can go mad with the quoting specifications)
84 		and NO IRC CLIENT supports it completely.
85 		Here is my personal point of view on the CTCP protocol.
86 		[big]What is CTCP?[/big]
87 		CTCP stands for Client-to-Client Protocol. It is designed
88 		for exchanging almost arbitrary data between IRC clients;
89 		the data is embedded into text messages of the underlying
90 		IRC protocol.
91 		[big]Basic concepts[/big]
92 		A CTCP message is sent as the <text> part of the PRIVMSG and
93 		NOTICE IRC commands.[br]
94 		To differentiate the CTCP message from a normal IRC message
95 		text we use a delimiter character (ASCII char 1); we will
96 		use the symbol <0x01> for this delimiter.
97 		You may receive a CTCP message from server in one of the
98 		following two ways:[br]
99 		[b]:<source_mask> PRIVMSG <target> :<0x01><ctcp message><0x01>[/b][br]
100 		[b]:<source_mask> NOTICE <target>:<0x01><ctcp message><0x01>[/b][br]
101 		The PRIVMSG is used for CTCP REQUESTS, the NOTICE for CTCP REPLIES.
102 		The NOTICE form should never generate an automatic reply.[br]
103 		The two delimiters were used to begin and terminate the
104 		CTCP message; The original protocol allowed more than one CTCP
105 		message inside a single IRC message. [b]Nobody sends more than
106 		one message at once, no client can recognize it (since it
107 		complicates the message parsing), it could be even dangerous (see below)[/b].
108 		It makes no real sense unless we wanted to use the CTCP protocol to embed escape sequences
109 		into IRC messages, which is not the case.[br]
110 		Furthermore, sending more CTCP messages in a single IRC message could
111 		be easily used to flood a client. Assuming 450 characters available for the IRC message
112 		text part, you could include 50 CTCP messages containing "<0x01>VERSION<0x01>".[br]
113 		Since the VERSION replies are usually long (there can be 3 or 4 replies per IRC message),
114 		a client that has no CTCP flood protection (or has it disabled) will surely
115 		be disconnected while sending the replies, after only
116 		receiving a single IRC message (no flood for the sender).
117 		From my personal point of view, only [b]one CTCP message per IRC message[/b]
118 		should be allowed and theoretically the trailing <0x01> delimiter can be optional.
119 		[big]How to extract the CTCP message[/big]
120 		The IRC messages do not allow the following characters to be sent:[br]
121 		<NUL> (ASCII character 0), <CR> (Carriage return), <LF> (Line feed).[br]
122 		So finally we have four characters that [b]cannot appear literally into a
123 		CTCP message[/b]: <NUL>,<CR>,<LF>,<0x01>.[br]
124 		To extract a <ctcp_message> from an IRC PRIVMSG or NOTICE command you
125 		have to perform the following actions:[br]
126 		Find the <trailing> part of the IRC message (the one just after the ':'
127 		delimiter, or the last message token).[br]
128 		Check if the first character of the <trailing> is a <0x01>, if it is
129 		we have a <ctcp_message> beginning just after <0x01>.
130 		The trailing (optional) <0x01> can be removed in this phase
131 		or later, assuming that it is not a valid char in the <ctcp message>.[br]
132 		In this document I will assume that you have stripped the trailing <0x01>
133 		and thus from now on we will deal only with the <ctcp message> part.
134 		[big]Parsing a CTCP message: The quoting dilemma[/big]
135 		Since there are characters that cannot appear in a <ctcp message>,
136 		theoretically we should have to use a quoting mechanism.
137 		Well, in fact, no actual CTCP message uses the quoting: there
138 		is no need to include a <NUL>, a <CR> or <LF> inside the actually
139 		defined messages (The only one could be CTCP SED, but I have never
140 		seen it in action... is there any client that implements it?).
141 		We could also leave the [i]quoting[/i] to the [i]single message type semantic[/i]:
142 		a message that needs to include [i]any character[/i] could have its own
143 		encoding method (Base64 for example). With the "one CTCP per IRC message"
144 		convention we could even allow <0x01> inside messages. Only the leading
145 		(and eventually trailing) <0x01> would be the delimiter, the other ones
146 		would be valid characters. Finally, is there any CTCP type that needs
147 		<0x01> inside a message? <0x01> is not printable (as well as <CR>,<LF> and <NUL>),
148 		so only encoded messages (and again we can stick to the single message semantic)
149 		messages or the ones including special parameters. Some machines might
150 		allow <0x01> in filenames... well, a file with <0x01> in its name has something
151 		broken inside, or the creator is a sort of [i]hacker[/i] (so he also
152 		knows how to rename a file...) :).[br]
153 		Anyway, let's be pedantic, and define this quoting method.
154 		Let's use the most intuitive method, adopted all around the world:[br]
155 		The backslash character ('\') as escape.[br]
156 		An escape sequence is formed by the backslash character and a number
157 		of following ASCII characters. We define the following two types of escape sequences:[br]
158 		[b]'\XXX'[/b] (where XXX is an [b]octal number[/b] formed by three digits)
159 		that indicates the ASCII character with code that corresponds to the number.[br]
160 		[b]'\C'[/b] (where C is a [b]CTCP valid ASCII non digit character[/b]) that corresponds
161 		literally to the character C discarding any other semantic that might be associated
162 		with it (This will become clear later).
163 		I've chosen the octal representation just to follow a bit the old specification:
164 		the authors seemed to like it. This point could be discussed in
165 		some mailing list or sth. The '\C' sequence is useful to include the backslash
166 		character (escape sequence '\\').
167 		[big]Let's mess a little more[/big]
168 		A CTCP message is made of [b]space separated parameters[/b].[br]
169 		The natural way of separating parameters is to use the space character.
170 		We define a [i]token[/i] as a sequence of valid CTCP characters not including literal space.
171 		A <ctcp parameter> is usually a token, but not always;
172 		filenames can contain spaces inside names (and it happens very often!).
173 		So one of the parameters of CTCP DCC is not a space separated token.
174 		How do we handle it? Again a standard is missing. Some clients simply change
175 		the filename placing underscores instead of spaces, this is a reasonable solution if used with care.
176 		Other clients attempt to [i]isolate[/i] the filename token by surrounding it with some kind
177 		of quotes, usually the [b]"[/b] or [b]'[/b] characters. This is also a good solution.
178 		Another one that naturally comes into my mind is to use the previously defined
179 		quoting to define a [i]non-breaking space[/i] character, because a space after a backslash
180 		could lose its original semantic. Better yet, use the backslash followed by
181 		the octal representation of the space character ('\040').
182 		Anyway, to maintain compatibility with other popular IRC clients (such as mIRC),
183 		let's include the [b]"[/b] quotes in our standard: literal (unescaped) [b]"[/b] quotes
184 		define a single token string. To include a literal [b]"[/b] character, escape it.
185 		Additionally, the last parameter of a <ctcp message> may be made of multiple tokens.
186 		[big]A CTCP parameter extracting example[/big]
187 		A trivial example of a C [i]CTCP parameter extracting routine[/i] follows.[br]
188 		An IRC message is made of up to 510 usable characters.
189 		When a CTCP is sent there is a PRIVMSG or NOTICE token that uses at least 6 characters,
190 		at least two spaces and a target token (that can not be empty, so it is at least one character)
191 		and finally one <0x01> escape character. This gives 500 characters as maximum size
192 		for a complete <ctcp message> and thus for a <ctcp token>.
193 		In fact, the <ctcp message> is always smaller than 500 characters; there are usually two
194 		<0x01> chars, there is a message source part at the beginning of the IRC message
195 		that is 10-15 characters long, and there is a [b]:[/b] character before the trailing parameter.
196 		Anyway, to really be on the [i]safe side[/i], we use a 512 character buffer for each
197 		<ctcp token>. Finally, I'll assume that you have already ensured that
198 		the <ctcp message> that we are extracting from is shorter than 511 characters in all,
199 		and have provided a buffer big enough to avoid this code segfaulting.
200 		I'm assuming that msg_ptr points somewhere in the <ctcp message> and is null-terminated.[br]
201 		(There are C++ style comments, you might want to remove them)
202 		[example]
203 		const char * decode_escape(const char * msg_ptr,char * buffer)
204 		{
205 			// This one decodes an escape sequence
206 			// and returns the pointer "just after it"
207 			// and should be called when *msg_ptr points
208 			// just after a backslash
209 			char c;
210 			if((*msg_ptr >= '0') && (*msg_ptr < '8'))
211 			{
212 				// a digit follows the backslash
213 				c = *msg_ptr - '0';
214 				msg_ptr++;
215 				if(*msg_ptr >= '0') && (*msg_ptr < '8'))
216 				{
217 					c = ((c << 3) + (*msg_ptr - '0'));
218 					msg_ptr++;
219 					if(*msg_ptr >= '0') && (*msg_ptr < '8'))
220 					{
221 						c = ((c << 3) + (*msg_ptr - '0'));
222 						msg_ptr++;
223 					} // else broken message, but let's be flexible
224 				} // else it is broken, but let's be flexible
225 				// append the character and return
226 				*buffer = c;
227 				return msg_ptr;
228 			} else {
229 				// simple escape: just append the following
230 				// character (thus discarding its semantic)
231 				*buffer = *msg_ptr;
232 				return ++msg_ptr;
233 			}
234 		}
235 
236 		const char * extract_ctcp_parameter(const char * msg_ptr,char * buffer,int spaceBreaks)
237 		{
238 			// this one extracts the "next" ctcp parameter in msg_ptr
239 			// it skips the leading and trailing spaces.
240 			// spaceBreaks should be set to 0 if (and only if) the
241 			// extracted parameter is the last in the CTCP message.
242 			int inString = 0;
243 			while(*msg_ptr == ' ')msg_ptr++;
244 			while(*msg_ptr)
245 			{
246 				switch(*msg_ptr)
247 				{
248 					case '\\':
249 						// backslash : escape sequence
250 						msg_ptr++;
251 						if(*msg_ptr)msg_ptr = decode_escape(msg_ptr,buffer);
252 						else return msg_ptr; // senseless backslash
253 					break;
254 					case ' ':
255 						// space : separate tokens?
256 						if(inString || (!spaceBreaks))*buffer++ = *msg_ptr++;
257 						else {
258 							// not in string and space breaks: end of token
259 							// skip trailing white space (this could be avoided)
260 							// and return
261 							while(*msg_ptr == ' ')msg_ptr++;
262 							return msg_ptr;
263 						}
264 					break;
265 					case '"':
266 						// a string begin or end
267 						inString = !inString;
268 						msg_ptr++;
269 					break;
270 					default:
271 						// any other char
272 						*buffer++ = *msg_ptr++;
273 					break;
274 				}
275 			}
276 			return msg_ptr;
277 		}
278 		[/example]
279 		[big]CTCP parameter semantics[/big]
280 		The first <ctcp parameter> of a <ctcp message> is the <ctcp tag>: it defines
281 		the semantic of the rest of the message.[br]
282 		Although it is a convention to specify the <ctcp tag> as uppercase letters,
283 		and the original specification says that the whole <ctcp message> is
284 		case sensitive, I'd prefer to follow the IRC message semantic (just to
285 		have less "special cases") and treat the whole message as [b]case insensitive[/b].[br]
286 		The remaining tokens depend on the <ctcp tag>. A description of known <ctcp tags>
287 		and thus <ctcp messages> follows.
288 		[big]PING[/big]
289 		[b]Syntax: <0x01>PING <data><0x01>[/b][br]
290 		The PING request is used to check the round trip time from one client to another.
291 		The receiving client should reply with exactly the same message but sent
292 		through a NOTICE instead of a PRIVMSG. The <data> usually contains an unsigned
293 		integer but not necessarily; it is not even mandatory for <data> to be a single token.
294 		The receiver should ignore the semantic of <data>.[br]
295 		The reply is intended to be processed by IRC clients.
296 		[big]VERSION[/big]
297 		[b]Syntax: <0x01>VERSION<0x01>[/b][br]
298 		The VERSION request asks for information about another user's IRC client program.
299 		The reply should be sent through a NOTICE with the following syntax:[br]
300 		<0x01>VERSION <client_version_data><0x01>[br]
301 		The preferred form for <client_version_data> is
302 		[i]<client_name>:<client_version>:<client_enviroinement>[/i], but historically
303 		clients (and users) send a generic reply describing the client name, version
304 		and eventually the used script name. This CTCP reply is intended to be human
305 		readable, so any form is accepted.
306 		[big]USERINFO[/big]
307 		[b]Syntax: <0x01>USERINFO<0x01>[/b][br]
308 		The USERINFO request asks for information about another user.
309 		The reply should be sent through a NOTICE with the following syntax:[br]
310 		<0x01>USERINFO <user_info_data><0x01>[br]
311 		The <user_info_data> should be a human readable [i]user defined[/i] string;
312 		[big]CLIENTINFO[/big]
313 		[b]Syntax: <0x01>CLIENTINFO<0x01>[/b][br]
314 		The CLIENTINFO request asks for information about another user's IRC client program.
315 		While VERSION requests the client program name and version, CLIENTINFO requests
316 		information about CTCP capabilities.[br]
317 		The reply should be sent through a NOTICE with the following syntax:[br]
318 		<0x01>CLIENTINFO <client_info_data><0x01>[br]
319 		The <client_info_data> should contain a list of supported CTCP request tags.
320 		The CLIENTINFO reply is intended to be human readable.
321 		[big]FINGER[/big]
322 		[b]Syntax: <0x01>FINGER<0x01>[/b][br]
323 		The FINGER request asks for information about another IRC user.
324 		The reply should be sent through a NOTICE with the following syntax:[br]
325 		<0x01>FINGER <user_info_data><0x01>[br]
326 		The <user_info_data> should be a human readable string containing
327 		the system username and possibly the system idle time;
328 		[big]SOURCE[/big]
329 		[b]Syntax: <0x01>SOURCE<0x01>[/b][br]
330 		The SOURCE request asks for the client homepage or ftp site information.
331 		The reply should be sent through a NOTICE with the following syntax:[br]
332 		<0x01>VERSION <homepage_url_data><0x01>[br]
333 		This CTCP reply is intended to be human readable, so any form is accepted.
334 		[big]TIME[/big]
335 		[b]Syntax: <0x01>TIME<0x01>[/b][br]
336 		The TIME request asks for the user local time.
337 		The reply should be sent through a NOTICE with the following syntax:[br]
338 		<0x01>TIME <time and date string><0x01>[br]
339 		This CTCP reply is intended to be human readable, so any form is accepted.
340 		[big]ACTION[/big]
341 		[b]Syntax: <0x01>ACTION<0x01>[/b][br]
342 		The ACTION tag is used to describe an action.[br]
343 		It should be sent through a NOTICE message and never generate a reply.
344 		[big]AVATAR (equivalent to ICON or FACE)[/big]
345 		[b]Syntax: <0x01>AVATAR<0x01>[/b][br]
346 		The AVATAR tag is used to query a user's avatar.
347 		[big]MULTIMEDIA (equivalent to MM or SOUND)[/big]
348 		[b]Syntax: <0x01>MULTIMEDIA <filename><0x01>[/b][br]
349 		The MULTIMEDIA tag is used to play a multimedia file on the receiver's side.[br]
350 		The receiving client should locate the file associated to <filename>,
351 		and play it. If the file can not be located
352 		by the receiving client, and the MULTIMEDIA tag was sent through a PRIVMSG format CTCP,
353 		the receiving client CAN request a [doc:dcc_connection]DCC GET[/doc] <filename> from the source user.
354 		If the MULTIMEDIA tag was sent through a NOTICE message, the receiving client
355 		should not generate any reply: the message should be notified to the receiving
356 		client's user and then be discarded. The <filename> should never contain a leading
357 		path. If any part of the <filename> appears to be a path component, it should be discarded.
358 		The client may decide to drop the entire message too. Older clients (including
359 		older releases of KVIrc) used to request the missing filenames by a particular
360 		non-standard private message syntax. This convention should be dropped.
361 		[big]DCC[/big]
362 		[b]Syntax: <0x01>DCC <type> <type dependent parameters><0x01>[/b][br]
363 		The DCC tag is used to initiate a Direct Client Connection.
364 		The known DCC types are:[br]
365 		[pre]
366 			CHAT
367 			SEND
368 			SSEND
369 			TSEND
370 			GET
371 			TGET
372 			ACCEPT
373 			RESUME
374 		[/pre]
375 */
376 
encodeCtcpParameter(const char * param,KviCString & buffer,bool bSpaceBreaks)377 void KviIrcServerParser::encodeCtcpParameter(const char * param, KviCString & buffer, bool bSpaceBreaks)
378 {
379 	//
380 	// This one encodes a single ctcp parameter with the simplest
381 	// subset of rules and places it in the supplied buffer
382 	//
383 	if(!(*param))
384 	{
385 		// empty parameter: the only reason we REALLY need the double quotes
386 		if(bSpaceBreaks)
387 			buffer.append("\"\"");
388 		return;
389 	}
390 
391 	const char * begin = param;
392 
393 	while(*param)
394 	{
395 		switch(*param)
396 		{
397 			case ' ':
398 				if(bSpaceBreaks)
399 				{
400 					if(param != begin)
401 						buffer.append(begin, param - begin);
402 					buffer.append("\\040");
403 					param++;
404 					begin = param;
405 				}
406 				else
407 				{
408 					// space is non breaking (last parameter)
409 					param++;
410 				}
411 				break;
412 			case '\r':
413 				if(param != begin)
414 					buffer.append(begin, param - begin);
415 				buffer.append("\\015");
416 				param++;
417 				begin = param;
418 				break;
419 			case '\n':
420 				if(param != begin)
421 					buffer.append(begin, param - begin);
422 				buffer.append("\\012");
423 				param++;
424 				begin = param;
425 				break;
426 			case '"':
427 				if(param != begin)
428 					buffer.append(begin, param - begin);
429 				buffer.append("\\042");
430 				param++;
431 				begin = param;
432 				break;
433 			case '\\':
434 				if(param != begin)
435 					buffer.append(begin, param - begin);
436 				buffer.append("\\143");
437 				param++;
438 				begin = param;
439 				break;
440 			case 0x01:
441 				if(param != begin)
442 					buffer.append(begin, param - begin);
443 				buffer.append("\\001");
444 				param++;
445 				begin = param;
446 				break;
447 			default:
448 				param++;
449 				break;
450 		}
451 	}
452 
453 	if(param != begin)
454 		buffer.append(begin, param - begin);
455 }
456 
encodeCtcpParameter(const char * parametr,QString & resultBuffer,bool bSpaceBreaks)457 void KviIrcServerParser::encodeCtcpParameter(const char * parametr, QString & resultBuffer, bool bSpaceBreaks)
458 {
459 	// This one encodes a single ctcp parameter with the simplest
460 	// subset of rules and places it in the supplied buffer
461 	QByteArray buffer;
462 	const char * param = parametr;
463 	if(!param)
464 	{
465 		if(bSpaceBreaks)
466 			buffer.append("\"\"");
467 		return;
468 	}
469 	if(!(*param))
470 	{
471 		// empty parameter: the only reason we REALLY need the double quotes
472 		if(bSpaceBreaks)
473 			buffer.append("\"\"");
474 		return;
475 	}
476 
477 	while(*param)
478 	{
479 		switch(*param)
480 		{
481 			case ' ':
482 				if(bSpaceBreaks)
483 				{
484 					buffer.append("\\040");
485 					param++;
486 				}
487 				else
488 				{
489 					buffer += *param;
490 					param++;
491 				}
492 				break;
493 			case '\r':
494 				buffer.append("\\015");
495 				param++;
496 				break;
497 			case '\n':
498 				buffer.append("\\012");
499 				param++;
500 				break;
501 			case '"':
502 				buffer.append("\\042");
503 				param++;
504 				break;
505 			case '\\':
506 				buffer.append("\\143");
507 				param++;
508 				break;
509 			case 0x01:
510 				buffer.append("\\001");
511 				param++;
512 				break;
513 			default:
514 				buffer += *param;
515 				param++;
516 				break;
517 		}
518 	}
519 	resultBuffer = buffer;
520 }
521 
decodeCtcpEscape(const char * msg_ptr,KviCString & buffer)522 const char * KviIrcServerParser::decodeCtcpEscape(const char * msg_ptr, KviCString & buffer)
523 {
524 	// This one decodes an octal sequence
525 	// and returns the pointer "just after it".
526 	// It should be called when *msg_ptr points
527 	// just after a backslash.
528 	// The decoded escape is appended to the buffer
529 	//
530 	// We're also assuming that *msg_ptr is not null here
531 	if((*msg_ptr >= '0') && (*msg_ptr < '8'))
532 	{
533 		// a digit follows the backslash */
534 		char c = *msg_ptr - '0';
535 		msg_ptr++;
536 		if((*msg_ptr >= '0') && (*msg_ptr < '8'))
537 		{
538 			c = ((c << 3) + (*msg_ptr - '0'));
539 			msg_ptr++;
540 			if((*msg_ptr >= '0') && (*msg_ptr < '8'))
541 			{
542 				c = ((c << 3) + (*msg_ptr - '0'));
543 				msg_ptr++;
544 			} // else broken message, but let's be flexible
545 		}         // else it is broken, but let's be flexible
546 		buffer.append(c);
547 		return msg_ptr;
548 	}
549 
550 	if(*msg_ptr == 'r')
551 	{
552 		buffer.append('\r');
553 		return ++msg_ptr;
554 	}
555 
556 	if(*msg_ptr == 'n')
557 	{
558 		buffer.append('\n');
559 		return ++msg_ptr;
560 	}
561 
562 	// null escape: just append the following
563 	// character (thus discarding its semantics)
564 
565 	buffer.append(msg_ptr);
566 	return ++msg_ptr;
567 }
568 
decodeCtcpEscape(const char * msg_ptr,QByteArray & buffer)569 const char * KviIrcServerParser::decodeCtcpEscape(const char * msg_ptr, QByteArray & buffer)
570 {
571 	// This one decodes an octal sequence
572 	// and returns the pointer "just after it".
573 	// It should be called when *msg_ptr points
574 	// just after a backslash.
575 	// The decoded escape is appended to the buffer
576 	//
577 	// We're also assuming that *msg_ptr is not null here
578 	if((*msg_ptr >= '0') && (*msg_ptr < '8'))
579 	{
580 		// a digit follows the backslash */
581 		char c = *msg_ptr - '0';
582 		msg_ptr++;
583 		if((*msg_ptr >= '0') && (*msg_ptr < '8'))
584 		{
585 			c = ((c << 3) + (*msg_ptr - '0'));
586 			msg_ptr++;
587 			if((*msg_ptr >= '0') && (*msg_ptr < '8'))
588 			{
589 				c = ((c << 3) + (*msg_ptr - '0'));
590 				msg_ptr++;
591 			} // else broken message, but let's be flexible
592 		}     // else it is broken, but let's be flexible
593 		buffer += (c ? c : ' ');
594 		return msg_ptr;
595 	}
596 
597 	if(*msg_ptr == 'r')
598 	{
599 		buffer += '\r';
600 		return ++msg_ptr;
601 	}
602 	if(*msg_ptr == 'n')
603 	{
604 		buffer += '\n';
605 		return ++msg_ptr;
606 	}
607 
608 	// null escape: just append the following
609 	// character (thus discarding its semantics)
610 
611 	buffer += *msg_ptr;
612 	return ++msg_ptr;
613 }
614 
extractCtcpParameter(const char * msg_ptr,KviCString & buffer,bool bSpaceBreaks,bool bSafeOnly)615 const char * KviIrcServerParser::extractCtcpParameter(const char * msg_ptr, KviCString & buffer, bool bSpaceBreaks, bool bSafeOnly)
616 {
617 	// This one extracts the "next" ctcp parameter in msg_ptr
618 	// and puts it in the supplied buffer.
619 	// It is assumed that the leading and trailing CTCP
620 	// tags have been already removed.
621 	// Skips the leading and trailing spaces.
622 	// bSpaceBreaks should be set to false if (and only if) the
623 	// extracted parameter is the last in a positional parameter
624 	// based CTCP message.
625 
626 	if(!msg_ptr)
627 		return nullptr;
628 	while(*msg_ptr == ' ')
629 		msg_ptr++; // skip leading spaces
630 
631 	int bInString = 0;
632 	if(*msg_ptr == '"')
633 	{
634 		// a quoted parameter
635 		bInString = 1;
636 		msg_ptr++;
637 	}
638 
639 	const char * begin = msg_ptr;
640 
641 	while(*msg_ptr)
642 	{
643 		switch(*msg_ptr)
644 		{
645 			case '\\':
646 				// backslash : escape sequence
647 				if(bSafeOnly)
648 					msg_ptr++;
649 				else
650 				{
651 					if(msg_ptr != begin)
652 						buffer.append(begin, msg_ptr - begin);
653 					msg_ptr++;
654 					if(*msg_ptr)
655 					{
656 						// decode the escape
657 						msg_ptr = decodeCtcpEscape(msg_ptr, buffer);
658 						begin = msg_ptr;
659 					}
660 					// else it is a senseless trailing backslash.
661 					// Just ignore and let the function
662 					// return spontaneously.
663 				}
664 				break;
665 			case ' ':
666 				// space : separate tokens if not in string
667 				if(bInString || (!bSpaceBreaks))
668 					msg_ptr++;
669 				else
670 				{
671 					// Not in string and space breaks: end of token
672 					// skip trailing white space (this could be avoided)
673 					// and return
674 					if(msg_ptr != begin)
675 						buffer.append(begin, msg_ptr - begin);
676 					while(*msg_ptr == ' ')
677 						msg_ptr++;
678 					return msg_ptr;
679 				}
680 				break;
681 			case '"':
682 				if(bInString && !bSafeOnly)
683 				{
684 					// A string terminator. We don't return
685 					// immediately since if !bSpaceBreaks
686 					// we must handle tokens until the end
687 					// and otherwise we just run up to the
688 					// next breaking space (but that's a bug anyway, heh).
689 					if(msg_ptr != begin)
690 						buffer.append(begin, msg_ptr - begin);
691 					bInString = 0;
692 					msg_ptr++;
693 					begin = msg_ptr;
694 				}
695 				else
696 				{
697 					// we don't begin a string here
698 					// since we're in the middle of the token
699 					// it is assumed to be simply a non encoded "
700 					msg_ptr++;
701 				}
702 				break;
703 			default:
704 				// any other char
705 				msg_ptr++;
706 				break;
707 		}
708 	}
709 	if(msg_ptr != begin)
710 		buffer.append(begin, msg_ptr - begin);
711 	return msg_ptr;
712 }
713 
extractCtcpParameter(const char * p_msg_ptr,QString & resultBuffer,bool bSpaceBreaks,bool bSafeOnly)714 const char * KviIrcServerParser::extractCtcpParameter(const char * p_msg_ptr, QString & resultBuffer, bool bSpaceBreaks, bool bSafeOnly)
715 {
716 	// This one extracts the "next" ctcp parameter in p_msg_ptr
717 	// and puts it in the supplied buffer.
718 	// It is assumed that the leading and trailing CTCP
719 	// tags have been already removed.
720 	// Skips the leading and trailing spaces.
721 	// bSpaceBreaks should be set to false if (and only if) the
722 	// extracted parameter is the last in a positional parameter
723 	// based CTCP message.
724 
725 	QByteArray buffer;
726 	const char * msg_ptr = p_msg_ptr;
727 	int bInString = 0;
728 	if(!msg_ptr)
729 		return nullptr;
730 	while(*msg_ptr == ' ')
731 		msg_ptr++; // skip leading spaces
732 
733 	if(*msg_ptr == '"')
734 	{
735 		// a quoted parameter
736 		bInString = 1;
737 		msg_ptr++;
738 	}
739 
740 	while(*msg_ptr)
741 	{
742 		switch(*msg_ptr)
743 		{
744 			case '\\':
745 				// backslash : escape sequence
746 				if(bSafeOnly)
747 					msg_ptr++;
748 				else
749 				{
750 					msg_ptr++;
751 					if(*msg_ptr)
752 					{
753 						// decode the escape
754 						msg_ptr = decodeCtcpEscape(msg_ptr, buffer);
755 					}
756 					// else it is a senseless trailing backslash.
757 					// Just ignore and let the function
758 					// return spontaneously.
759 				}
760 				break;
761 			case ' ':
762 				// space : separate tokens if not in string
763 				if(bInString || (!bSpaceBreaks))
764 				{
765 					buffer += *msg_ptr;
766 					msg_ptr++;
767 				}
768 				else
769 				{
770 					// Not in string and space breaks: end of token
771 					// skip trailing white space (this could be avoided)
772 					// and return
773 					while(*msg_ptr == ' ')
774 						msg_ptr++;
775 					resultBuffer = buffer;
776 					return msg_ptr;
777 				}
778 				break;
779 			case '"':
780 				if(bInString && !bSafeOnly)
781 				{
782 					// A string terminator. We don't return
783 					// immediately since if !bSpaceBreaks
784 					// we must handle tokens until the end
785 					// and otherwise we just run up to the
786 					// next breaking space (but that's a bug anyway, heh).
787 					buffer += *msg_ptr;
788 					bInString = 0;
789 					msg_ptr++;
790 				}
791 				else
792 				{
793 					// we don't begin a string here
794 					// since we're in the middle of the token
795 					// it is assumed to be simply a non encoded "
796 					buffer += *msg_ptr;
797 					msg_ptr++;
798 				}
799 				break;
800 			default:
801 				// any other char
802 				buffer += *msg_ptr;
803 				msg_ptr++;
804 				break;
805 		}
806 	}
807 	resultBuffer = buffer;
808 	return msg_ptr;
809 }
810 
parseCtcpRequest(KviCtcpMessage * msg)811 void KviIrcServerParser::parseCtcpRequest(KviCtcpMessage * msg)
812 {
813 	msg->pData = extractCtcpParameter(msg->pData, msg->szTag);
814 
815 	bool bAction = KviQString::equalCI(msg->szTag, "ACTION");
816 
817 	if (!bAction)
818 	{
819 		if (IS_ME(msg->msg, msg->pSource->nick()) && !IS_ME(msg->msg, msg->szTarget))
820 		{
821 			// "znc.in/self-message" capability: Handle a replayed message from ourselves to someone else.
822 			return;
823 		}
824 	}
825 
826 	if(msg->szTag == "DCC" || msg->szTag == "XDCC" || msg->szTag == "TDCC") // is dcc request
827 	{
828 		if(KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpDcc))
829 		{
830 			msg->msg->console()->output(KVI_OUT_IGNORE, __tr2qs("Ignoring DCC from \r!nc\r%s\r [%s@\r!h\r%s\r]"),
831 			    msg->pSource->nick().toUtf8().data(),
832 			    msg->pSource->user().toUtf8().data(),
833 			    msg->pSource->host().toUtf8().data());
834 			return;
835 		}
836 	}
837 	else
838 	{
839 
840 		KviRegisteredUser * u = msg->msg->connection()->userDataBase()->registeredUser(msg->pSource->nick(), msg->pSource->user(), msg->pSource->host());
841 
842 		//Ignore it?
843 		if(u)
844 		{
845 			if(
846 			    (!bAction && u->isIgnoreEnabledFor(KviRegisteredUser::Ctcp)) || (bAction && u->isIgnoreEnabledFor(IS_ME(msg->msg, msg->szTarget) ? KviRegisteredUser::Query : KviRegisteredUser::Channel)))
847 			{
848 				if(KVI_OPTION_BOOL(KviOption_boolVerboseIgnore))
849 				{
850 					msg->msg->console()->output(KVI_OUT_IGNORE, __tr2qs("Ignoring CTCP from \r!nc\r%s\r [%s@\r!h\r%s\r]"),
851 					    msg->pSource->nick().toUtf8().data(),
852 					    msg->pSource->user().toUtf8().data(),
853 					    msg->pSource->host().toUtf8().data());
854 				}
855 				return;
856 			}
857 		}
858 	}
859 
860 	for(int i = 0; m_ctcpParseProcTable[i].msgName; i++)
861 	{
862 		if(KviQString::equalCS(KviQString::upperISO88591(msg->szTag), m_ctcpParseProcTable[i].msgName)
863 		    && m_ctcpParseProcTable[i].req)
864 		{
865 			if(!(m_ctcpParseProcTable[i].iFlags & KVI_CTCP_MESSAGE_PARSE_TRIGGERNOEVENT))
866 			{
867 				QString szData = msg->msg->connection()->decodeText(msg->pData);
868 				if(
869 				    KVS_TRIGGER_EVENT_6_HALTED(
870 				        KviEvent_OnCTCPRequest,
871 				        msg->msg->console(),
872 				        msg->pSource->nick(),
873 				        msg->pSource->user(),
874 				        msg->pSource->host(),
875 				        msg->szTarget,
876 				        msg->szTag,
877 				        szData))
878 					return;
879 			}
880 			(this->*(m_ctcpParseProcTable[i].req))(msg);
881 			return;
882 		}
883 	}
884 
885 	QString szData = msg->msg->connection()->decodeText(msg->pData);
886 	// trigger the event on unrecognized requests too
887 	if(
888 	    KVS_TRIGGER_EVENT_6_HALTED(
889 	        KviEvent_OnCTCPRequest,
890 	        msg->msg->console(),
891 	        msg->pSource->nick(),
892 	        msg->pSource->user(),
893 	        msg->pSource->host(),
894 	        msg->szTarget,
895 	        msg->szTag,
896 	        szData))
897 		return;
898 
899 	// unknown
900 	msg->bUnknown = true;
901 	echoCtcpRequest(msg);
902 }
903 
parseCtcpReply(KviCtcpMessage * msg)904 void KviIrcServerParser::parseCtcpReply(KviCtcpMessage * msg)
905 {
906 	msg->pData = extractCtcpParameter(msg->pData, msg->szTag);
907 
908 	for(int i = 0; m_ctcpParseProcTable[i].msgName; i++)
909 	{
910 		if(KviQString::equalCS(KviQString::upperISO88591(msg->szTag), m_ctcpParseProcTable[i].msgName)
911 		    && m_ctcpParseProcTable[i].rpl)
912 		{
913 			if(!(m_ctcpParseProcTable[i].iFlags & KVI_CTCP_MESSAGE_PARSE_TRIGGERNOEVENT))
914 			{
915 				QString szData = msg->msg->connection()->decodeText(msg->pData);
916 				if(KVS_TRIGGER_EVENT_6_HALTED(KviEvent_OnCTCPReply,
917 				       msg->msg->console(), msg->pSource->nick(), msg->pSource->user(),
918 				       msg->pSource->host(), msg->szTarget, msg->szTag, szData))
919 					return;
920 			}
921 			(this->*(m_ctcpParseProcTable[i].rpl))(msg);
922 			return;
923 		}
924 	}
925 
926 	QString szData = msg->msg->connection()->decodeText(msg->pData);
927 	// trigger the event on unrecognized replies too
928 	if(KVS_TRIGGER_EVENT_6_HALTED(KviEvent_OnCTCPReply,
929 	       msg->msg->console(), msg->pSource->nick(), msg->pSource->user(),
930 	       msg->pSource->host(), msg->szTarget, msg->szTag, szData))
931 		return;
932 
933 	// unknown
934 	msg->bUnknown = true;
935 	echoCtcpReply(msg);
936 }
937 
938 // Ctcp message handlers
939 
checkCtcpFlood(KviCtcpMessage * msg)940 bool KviIrcServerParser::checkCtcpFlood(KviCtcpMessage * msg)
941 {
942 	if(!KVI_OPTION_BOOL(KviOption_boolUseCtcpFloodProtection))
943 		return false;
944 
945 	kvi_time_t tNow = kvi_unixTime();
946 
947 	KviIrcConnectionAntiCtcpFloodData * d = msg->msg->connection()->antiCtcpFloodData();
948 
949 	unsigned int interval = (unsigned int)(((unsigned int)tNow) - ((unsigned int)d->lastCtcpTime()));
950 
951 	if(interval < KVI_OPTION_UINT(KviOption_uintCtcpFloodCheckInterval))
952 	{
953 		d->increaseCtcpCount();
954 		if(d->ctcpCount() > KVI_OPTION_UINT(KviOption_uintMaxCtcpRequests))
955 		{
956 			// This is flood
957 			msg->bIsFlood = true;
958 			return true;
959 		}
960 	}
961 	else
962 	{
963 		d->setLastCtcpTime(tNow);
964 		d->setCtcpCount(1);
965 	}
966 	return false;
967 }
968 
replyCtcp(KviCtcpMessage * msg,const QString & data)969 void KviIrcServerParser::replyCtcp(KviCtcpMessage * msg, const QString & data)
970 {
971 	QByteArray szNick = msg->msg->connection()->encodeText(msg->pSource->nick());
972 	msg->msg->connection()->sendFmtData(
973 	    "NOTICE %s :%c%s %s%c",
974 	    szNick.data(),
975 	    0x01,
976 	    msg->msg->connection()->encodeText(msg->szTag).data(),
977 	    msg->msg->connection()->encodeText(data).data(),
978 	    0x01);
979 }
980 
echoCtcpReply(KviCtcpMessage * msg)981 void KviIrcServerParser::echoCtcpReply(KviCtcpMessage * msg)
982 {
983 	if(!msg->msg->haltOutput())
984 	{
985 		KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolCtcpRepliesToActiveWindow) ? msg->msg->console()->activeWindow() : msg->msg->console();
986 
987 		bool bIsChannel = false;
988 
989 		if(!IS_ME(msg->msg, msg->szTarget))
990 		{
991 			// Channel ctcp request!
992 			pOut = msg->msg->connection()->findChannel(msg->szTarget);
993 			if(!pOut)
994 			{
995 				pOut = msg->msg->console();
996 				pOut->output(KVI_OUT_SYSTEMWARNING,
997 				    __tr2qs("The following CTCP reply has unrecognized target %Q"), &(msg->szTarget));
998 			}
999 			else
1000 				bIsChannel = true;
1001 		}
1002 
1003 		QString szData = msg->msg->connection()->decodeText(msg->pData);
1004 
1005 		QString szWhat = bIsChannel ? __tr2qs("Channel CTCP") : QString("CTCP");
1006 
1007 		pOut->output(
1008 		    msg->bUnknown ? KVI_OUT_CTCPREPLYUNKNOWN : KVI_OUT_CTCPREPLY,
1009 		    __tr2qs("%Q %Q reply from \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
1010 		    &szWhat, &(msg->szTag), &(msg->pSource->nick()), &(msg->pSource->user()),
1011 		    &(msg->pSource->host()), &szData);
1012 	}
1013 }
1014 
echoCtcpRequest(KviCtcpMessage * msg)1015 void KviIrcServerParser::echoCtcpRequest(KviCtcpMessage * msg)
1016 {
1017 	// FIXME: #warning "DEDICATED CTCP WINDOW...MINIMIZED ?"
1018 	if(!msg->msg->haltOutput())
1019 	{
1020 		QString req = msg->szTag;
1021 		if(*(msg->pData))
1022 		{
1023 			req.append(" ");
1024 			req.append(msg->pData);
1025 		}
1026 
1027 		KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolCtcpRequestsToActiveWindow) ? msg->msg->console()->activeWindow() : msg->msg->console();
1028 
1029 		bool bIsChannel = false;
1030 
1031 		if(!IS_ME(msg->msg, msg->szTarget))
1032 		{
1033 			// Channel ctcp request!
1034 			pOut = msg->msg->connection()->findChannel(msg->szTarget);
1035 			if(!pOut)
1036 			{
1037 				pOut = msg->msg->console();
1038 				pOut->output(KVI_OUT_SYSTEMWARNING,
1039 				    __tr2qs("The following CTCP request has unrecognized target %Q"),
1040 				    &(msg->szTarget));
1041 			}
1042 			else
1043 				bIsChannel = true;
1044 		}
1045 
1046 		QString szRequest = req;
1047 		QString szTag = msg->szTag;
1048 		QString szWhat = bIsChannel ? __tr2qs("Channel CTCP") : QString("CTCP");
1049 
1050 		if(msg->bIsFlood)
1051 		{
1052 			QString szData = msg->msg->connection()->decodeText(msg->pData);
1053 			if(!KVS_TRIGGER_EVENT_6_HALTED(KviEvent_OnCTCPFlood, pOut, msg->pSource->nick(), msg->pSource->user(), msg->pSource->host(), msg->szTarget, msg->szTag, szData))
1054 				pOut->output(KVI_OUT_CTCPREQUESTFLOOD,
1055 				    __tr2qs("%Q %Q%c request from \r!n\r%Q\r [%Q@\r!h\r%Q\r] (%Q), ignored (flood limit exceeded)"),
1056 				    &szWhat, &szTag, KviControlCodes::Reset, &(msg->pSource->nick()),
1057 				    &(msg->pSource->user()), &(msg->pSource->host()), &szRequest);
1058 		}
1059 		else
1060 		{
1061 			QString szAction = msg->bUnknown ? __tr2qs("ignored (unrecognized)") : (msg->bIgnored ? __tr2qs("ignored") : __tr2qs("replied"));
1062 			pOut->output(
1063 			    msg->bUnknown ? KVI_OUT_CTCPREQUESTUNKNOWN : (msg->bIgnored ? KVI_OUT_CTCPREQUESTIGNORED : KVI_OUT_CTCPREQUESTREPLIED),
1064 			    __tr2qs("%Q %Q%c request from \r!n\r%Q\r [%Q@\r!h\r%Q\r] (%Q), %Q"),
1065 			    &szWhat, &szTag, KviControlCodes::Reset, &(msg->pSource->nick()),
1066 			    &(msg->pSource->user()), &(msg->pSource->host()), &szRequest, &szAction);
1067 		}
1068 	}
1069 }
1070 
parseCtcpRequestPing(KviCtcpMessage * msg)1071 void KviIrcServerParser::parseCtcpRequestPing(KviCtcpMessage * msg)
1072 {
1073 	if(!checkCtcpFlood(msg))
1074 	{
1075 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpPing))
1076 		{
1077 			replyCtcp(msg, msg->msg->connection()->encodeText(msg->pData));
1078 		}
1079 		else
1080 			msg->bIgnored = true;
1081 	}
1082 
1083 	echoCtcpRequest(msg);
1084 }
1085 
parseCtcpReplyPing(KviCtcpMessage * msg)1086 void KviIrcServerParser::parseCtcpReplyPing(KviCtcpMessage * msg)
1087 {
1088 	if(!msg->msg->haltOutput())
1089 	{
1090 		KviWindow * pOut = KVI_OPTION_BOOL(KviOption_boolCtcpRepliesToActiveWindow) ? msg->msg->console()->activeWindow() : msg->msg->console();
1091 
1092 		bool bIsChannel = false;
1093 
1094 		if(!IS_ME(msg->msg, msg->szTarget))
1095 		{
1096 			// Channel ctcp request!
1097 			pOut = msg->msg->connection()->findChannel(msg->szTarget);
1098 			if(!pOut)
1099 			{
1100 				pOut = msg->msg->console();
1101 				pOut->output(KVI_OUT_SYSTEMWARNING,
1102 				    __tr2qs("The following CTCP PING reply has unrecognized target \"%Q\""),
1103 				    &(msg->szTarget));
1104 			}
1105 			else
1106 				bIsChannel = true;
1107 		}
1108 
1109 		unsigned int uSecs;
1110 		unsigned int uMSecs = 0;
1111 
1112 		KviCString szTime;
1113 
1114 		struct timeval tv;
1115 		kvi_gettimeofday(&tv);
1116 
1117 		msg->pData = extractCtcpParameter(msg->pData, szTime, true);
1118 
1119 		bool bOk;
1120 
1121 		if(szTime.contains('.'))
1122 		{
1123 			KviCString szUSecs = szTime;
1124 			szUSecs.cutToFirst('.');
1125 			szTime.cutFromFirst('.');
1126 
1127 			uMSecs = szUSecs.toUInt(&bOk);
1128 			if(!bOk)
1129 			{
1130 				uMSecs = 0;
1131 				tv.tv_usec = 0;
1132 			}
1133 		}
1134 		else
1135 			tv.tv_usec = 0;
1136 
1137 		uSecs = szTime.toUInt(&bOk);
1138 		if(!bOk)
1139 			pOut->output(KVI_OUT_SYSTEMWARNING,
1140 			    __tr2qs("The following CTCP PING reply has a broken time identifier \"%S\", don't trust the displayed time"), &szTime);
1141 
1142 		unsigned int uDiffSecs = tv.tv_sec - uSecs;
1143 
1144 		while(uMSecs > 1000000)
1145 			uMSecs /= 10; // precision too high?
1146 		if(((unsigned int)tv.tv_usec) < uMSecs)
1147 		{
1148 			tv.tv_usec += 1000000;
1149 			if(uDiffSecs > 0)
1150 				uDiffSecs--;
1151 		}
1152 		unsigned int uDiffMSecs = (tv.tv_usec - uMSecs) / 1000;
1153 
1154 		QString szWhat = bIsChannel ? __tr2qs("Channel CTCP") : QString("CTCP");
1155 
1156 		pOut->output(
1157 		    msg->bUnknown ? KVI_OUT_CTCPREPLYUNKNOWN : KVI_OUT_CTCPREPLY,
1158 		    __tr2qs("%Q PING reply from \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %u sec %u msec"),
1159 		    &szWhat, &(msg->pSource->nick()),
1160 		    &(msg->pSource->user()), &(msg->pSource->host()), uDiffSecs, uDiffMSecs);
1161 	}
1162 }
1163 
parseCtcpRequestVersion(KviCtcpMessage * msg)1164 void KviIrcServerParser::parseCtcpRequestVersion(KviCtcpMessage * msg)
1165 {
1166 	if(!checkCtcpFlood(msg))
1167 	{
1168 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpVersion))
1169 		{
1170 			QString szVersion;
1171 
1172 			szVersion = "KVIrc " KVI_VERSION;
1173 			if(KviBuildInfo::buildRevision() != QString())
1174 			{
1175 				szVersion += " git:";
1176 				szVersion += KviBuildInfo::buildRevision();
1177 			}
1178 			szVersion += " '" KVI_RELEASE_NAME "' " KVI_SOURCES_DATE " - build ";
1179 			szVersion += KviBuildInfo::buildDate();
1180 #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
1181 			szVersion.append(QString(" - %1").arg(KviRuntimeInfo::version()));
1182 #else
1183 			szVersion.append(QString(" - %1 (%2)").arg(KviRuntimeInfo::name(), KviRuntimeInfo::release()));
1184 #endif
1185 			if(!KVI_OPTION_STRING(KviOption_stringCtcpVersionPostfix).isEmpty())
1186 			{
1187 				QString sz = KVI_OPTION_STRING(KviOption_stringCtcpVersionPostfix);
1188 				if(!sz.isEmpty())
1189 				{
1190 					szVersion.append(" :");
1191 					szVersion.append(sz);
1192 				}
1193 			}
1194 			replyCtcp(msg, szVersion);
1195 		}
1196 		else
1197 			msg->bIgnored = true;
1198 	}
1199 
1200 	echoCtcpRequest(msg);
1201 }
1202 
parseCtcpRequestUserinfo(KviCtcpMessage * msg)1203 void KviIrcServerParser::parseCtcpRequestUserinfo(KviCtcpMessage * msg)
1204 {
1205 	if(!checkCtcpFlood(msg))
1206 	{
1207 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpUserinfo))
1208 		{
1209 			QString szReply;
1210 			if(!KVI_OPTION_STRING(KviOption_stringCtcpUserInfoAge).isEmpty())
1211 			{
1212 				szReply = "Age=";
1213 				szReply += KVI_OPTION_STRING(KviOption_stringCtcpUserInfoAge);
1214 			}
1215 			if(!KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).isEmpty())
1216 			{
1217 				if(!szReply.isEmpty())
1218 					szReply += "; ";
1219 				szReply += "Gender=";
1220 				szReply += KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender);
1221 			}
1222 			if(!KVI_OPTION_STRING(KviOption_stringCtcpUserInfoLocation).isEmpty())
1223 			{
1224 				if(!szReply.isEmpty())
1225 					szReply += "; ";
1226 				szReply += "Location=";
1227 				szReply += KVI_OPTION_STRING(KviOption_stringCtcpUserInfoLocation);
1228 			}
1229 			if(!KVI_OPTION_STRING(KviOption_stringCtcpUserInfoLanguages).isEmpty())
1230 			{
1231 				if(!szReply.isEmpty())
1232 					szReply += "; ";
1233 				szReply += "Languages=";
1234 				szReply += KVI_OPTION_STRING(KviOption_stringCtcpUserInfoLanguages);
1235 			}
1236 			if(!KVI_OPTION_STRING(KviOption_stringCtcpUserInfoOther).isEmpty())
1237 			{
1238 				if(!szReply.isEmpty())
1239 					szReply += "; ";
1240 				szReply += KVI_OPTION_STRING(KviOption_stringCtcpUserInfoOther);
1241 			}
1242 			if(szReply.isEmpty())
1243 				szReply = KVI_DEFAULT_CTCP_USERINFO_REPLY;
1244 			replyCtcp(msg, szReply);
1245 		}
1246 		else
1247 			msg->bIgnored = true;
1248 	}
1249 
1250 	echoCtcpRequest(msg);
1251 }
1252 
1253 // FIXME: KEEP THIS TABLE UP TO DATE
1254 
1255 static const char * ctcpTagTable[][2] = {
1256 	{ "PING", "Returns given parameters without parsing them" },
1257 	{ "VERSION", "Returns the version of this client" },
1258 	{ "CLIENTINFO", "With no parameters, lists supported CTCP tags,"
1259 	                " 'CLIENTINFO <tag>' describes <tag>" },
1260 	{ "USERINFO", "Returns personal information about the current user" },
1261 	{ "FINGER", "Returns information about the current user" },
1262 	{ "SOURCE", "Returns the client homepage URL" },
1263 	{ "TIME", "Returns the current local time" },
1264 	{ "ACTION", "Used to describe actions, generates no reply" },
1265 	{ "AVATAR", "Returns the current avatar (may trigger a DCC GET) or"
1266 	            " sets your own on this side if sent through a NOTICE" },
1267 	{ "DCC", "Initiates a DCC connection (XDCC,TDCC)" },
1268 	{ "PAGE", "Leaves a message for this user" },
1269 	{ nullptr, nullptr }
1270 };
1271 
parseCtcpRequestClientinfo(KviCtcpMessage * msg)1272 void KviIrcServerParser::parseCtcpRequestClientinfo(KviCtcpMessage * msg)
1273 {
1274 	// this is completely latin1
1275 	if(!checkCtcpFlood(msg))
1276 	{
1277 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpClientinfo))
1278 		{
1279 			KviCString szTag;
1280 			msg->pData = extractCtcpParameter(msg->pData, szTag, false);
1281 			szTag.trim();
1282 			szTag.toUpperISO88591();
1283 			if(szTag.isEmpty())
1284 			{
1285 				QString reply("KVIrc " KVI_VERSION " '" KVI_RELEASE_NAME "' " KVI_SOURCES_DATE " - http://www.kvirc.net - Supported tags: ");
1286 				for(int i = 0; ctcpTagTable[i][0]; i++)
1287 				{
1288 					reply += ctcpTagTable[i][0];
1289 					if(ctcpTagTable[i + 1][0])
1290 						reply += ",";
1291 				}
1292 				reply += " - Use 'CLIENTINFO <tag>' for a description of each tag";
1293 				replyCtcp(msg, reply);
1294 			}
1295 			else
1296 			{
1297 				bool bFound = false;
1298 				for(int i = 0; ctcpTagTable[i][0] && !bFound; i++)
1299 				{
1300 					if(kvi_strEqualCS(ctcpTagTable[i][0], szTag.ptr()))
1301 					{
1302 						KviCString reply(KviCString::Format, "%s: %s", ctcpTagTable[i][0], ctcpTagTable[i][1]);
1303 						replyCtcp(msg, reply.ptr());
1304 						bFound = true;
1305 					}
1306 				}
1307 				if(!bFound)
1308 				{
1309 					msg->szTag = "ERRMSG";
1310 					KviCString reply(KviCString::Format, "Unsupported tag %s", szTag.ptr());
1311 					replyCtcp(msg, reply.ptr());
1312 				}
1313 			}
1314 		}
1315 		else
1316 			msg->bIgnored = true;
1317 	}
1318 
1319 	echoCtcpRequest(msg);
1320 }
1321 
parseCtcpRequestFinger(KviCtcpMessage * msg)1322 void KviIrcServerParser::parseCtcpRequestFinger(KviCtcpMessage * msg)
1323 {
1324 	// completely latin1 atm
1325 	if(!checkCtcpFlood(msg))
1326 	{
1327 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpFinger))
1328 		{
1329 			KviCString username = getenv("USER");
1330 			if(username.isEmpty())
1331 				username = getenv("LOGNAME");
1332 			if(username.isEmpty())
1333 				username = msg->msg->connection()->userInfo()->userName();
1334 			// FIXME: #warning "UTSNAME ?...AND OTHER INFO ?...SYSTEM IDLE TIME ?...KVIRC IDLE TIME ?"
1335 			KviCString reply(KviCString::Format, "%s", username.ptr());
1336 			replyCtcp(msg, reply.ptr());
1337 		}
1338 		else
1339 			msg->bIgnored = true;
1340 	}
1341 
1342 	echoCtcpRequest(msg);
1343 }
1344 
parseCtcpRequestSource(KviCtcpMessage * msg)1345 void KviIrcServerParser::parseCtcpRequestSource(KviCtcpMessage * msg)
1346 {
1347 	if(!checkCtcpFlood(msg))
1348 	{
1349 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpSource))
1350 		{
1351 			QString version = "KVIrc " KVI_VERSION " '" KVI_RELEASE_NAME "' - http://www.kvirc.net/";
1352 			if(!KVI_OPTION_STRING(KviOption_stringCtcpSourcePostfix).isEmpty())
1353 			{
1354 				version += " :";
1355 				version += KVI_OPTION_STRING(KviOption_stringCtcpSourcePostfix);
1356 			}
1357 			replyCtcp(msg, version);
1358 		}
1359 		else
1360 			msg->bIgnored = true;
1361 	}
1362 
1363 	echoCtcpRequest(msg);
1364 }
1365 
parseCtcpRequestTime(KviCtcpMessage * msg)1366 void KviIrcServerParser::parseCtcpRequestTime(KviCtcpMessage * msg)
1367 {
1368 	if(!checkCtcpFlood(msg))
1369 	{
1370 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpTime))
1371 		{
1372 			QString szTmp;
1373 			QDateTime date = QDateTime::currentDateTime();
1374 			switch(KVI_OPTION_UINT(KviOption_uintOutputDatetimeFormat))
1375 			{
1376 				case 0:
1377 					// this is the equivalent to an empty date.toString() call, but it's needed
1378 					// to ensure qt4 will use the default() locale and not the system() one
1379 					szTmp = QLocale().toString(date, "ddd MMM d hh:mm:ss yyyy");
1380 					break;
1381 				case 1:
1382 					szTmp = date.toString(Qt::ISODate);
1383 					break;
1384 				case 2:
1385 					szTmp = date.toString(Qt::SystemLocaleShortDate);
1386 					break;
1387 			}
1388 			replyCtcp(msg, szTmp);
1389 		}
1390 		else
1391 			msg->bIgnored = true;
1392 	}
1393 
1394 	echoCtcpRequest(msg);
1395 }
1396 
parseCtcpRequestPage(KviCtcpMessage * msg)1397 void KviIrcServerParser::parseCtcpRequestPage(KviCtcpMessage * msg)
1398 {
1399 	if(!checkCtcpFlood(msg))
1400 	{
1401 		if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpPage))
1402 		{
1403 			KVI_OPTION_STRING(KviOption_stringCtcpPageReply) = KVI_OPTION_STRING(KviOption_stringCtcpPageReply).trimmed();
1404 			if(KVI_OPTION_STRING(KviOption_stringCtcpPageReply).isEmpty())
1405 				KVI_OPTION_STRING(KviOption_stringCtcpPageReply) = KVI_DEFAULT_CTCP_PAGE_REPLY;
1406 
1407 			replyCtcp(msg, KVI_OPTION_STRING(KviOption_stringCtcpPageReply));
1408 
1409 			bool bIsChannel = !IS_ME(msg->msg, msg->szTarget);
1410 
1411 			if((KVI_OPTION_BOOL(KviOption_boolShowDialogOnCtcpPage) && !bIsChannel) || (KVI_OPTION_BOOL(KviOption_boolShowDialogOnChannelCtcpPage) && bIsChannel))
1412 			{
1413 				if(!g_pCtcpPageDialog)
1414 					g_pCtcpPageDialog = new KviCtcpPageDialog();
1415 				KviCString szData8;
1416 				szData8 = msg->pData;
1417 				QString szData = KviQString::toHtmlEscaped(msg->msg->connection()->decodeText(szData8.ptr()));
1418 				g_pCtcpPageDialog->addPage(msg->pSource->nick(), msg->pSource->user(), msg->pSource->host(), szData);
1419 				g_pCtcpPageDialog->popup();
1420 			}
1421 		}
1422 		else
1423 			msg->bIgnored = true;
1424 	}
1425 
1426 	echoCtcpRequest(msg);
1427 }
1428 
1429 #ifdef COMPILE_CRYPT_SUPPORT
1430 #define DECRYPT_IF_NEEDED(target, txt, type, type2, buffer, retptr, retmsgtype)        \
1431 	if(KviCryptSessionInfo * cinf = target->cryptSessionInfo())                    \
1432 	{                                                                              \
1433 		if(cinf->m_bDoDecrypt)                                                 \
1434 		{                                                                      \
1435 			switch(cinf->m_pEngine->decrypt(txt, buffer))                  \
1436 			{                                                              \
1437 				case KviCryptEngine::DecryptOkWasEncrypted:            \
1438 					retptr = buffer.ptr();                         \
1439 					retmsgtype = type2;                            \
1440 					break;                                         \
1441 				case KviCryptEngine::DecryptOkWasPlainText:            \
1442 				case KviCryptEngine::DecryptOkWasEncoded:              \
1443 					retptr = buffer.ptr();                         \
1444 					retmsgtype = type;                             \
1445 					break;                                         \
1446 				default: /* also case KviCryptEngine::DecryptError: */ \
1447 				{                                                      \
1448 					QString szEngineError = cinf->m_pEngine->lastError(); \
1449 					target->output(KVI_OUT_SYSTEMERROR,            \
1450 					    __tr2qs("The following message appears to be encrypted but the encryption engine failed to decode it: %Q"), \
1451 					    &szEngineError);                           \
1452 					retptr = txt + 1;                              \
1453 					retmsgtype = type;                             \
1454 				}                                                      \
1455 				break;                                                 \
1456 			}                                                              \
1457 		}                                                                      \
1458 		else                                                                   \
1459 			retptr = txt, retmsgtype = type;                               \
1460 	}                                                                              \
1461 	else                                                                           \
1462 		retptr = txt, retmsgtype = type;
1463 #else //COMPILE_CRYPT_SUPPORT
1464 #define DECRYPT_IF_NEEDED(target, txt, type, type2, buffer, retptr, retmsgtype) \
1465 	retptr = txt;                                                           \
1466 	retmsgtype = type;
1467 #endif //COMPILE_CRYPT_SUPPORT
1468 
parseCtcpRequestAction(KviCtcpMessage * msg)1469 void KviIrcServerParser::parseCtcpRequestAction(KviCtcpMessage * msg)
1470 {
1471 	KviCString szData8(msg->pData);
1472 	// CTCP ACTION is a special exception... most clients do not encode/decode it.
1473 	//msg->pData = extractCtcpParameter(msg->pData,szData8,false);
1474 
1475 	KviWindow * pOut = nullptr;
1476 	bool bIsChannel = msg->msg->connection()->serverInfo()->supportedChannelTypes().indexOf(msg->szTarget[0]) != -1;
1477 	int msgtype = KVI_OUT_ACTION;
1478 
1479 	// "znc.in/self-message" capability: Handle a replayed message from ourselves to someone else.
1480 	bool bSelfMessage = IS_ME(msg->msg, msg->pSource->nick());
1481 	QString szTargetNick, szTargetUser, szTargetHost;
1482 	msg->msg->decodeAndSplitMask(msg->szTarget.toLatin1().data(), szTargetNick, szTargetUser, szTargetHost);
1483 	QString szWindow = bIsChannel || bSelfMessage ? szTargetNick : msg->pSource->nick();
1484 	const QString &szOtherNick = bSelfMessage ? szTargetNick : msg->pSource->nick();
1485 	const QString &szOtherUser = bSelfMessage ? szTargetUser : msg->pSource->user();
1486 	const QString &szOtherHost = bSelfMessage ? szTargetHost : msg->pSource->host();
1487 
1488 	QString szData;
1489 
1490 	if(bIsChannel)
1491 	{
1492 		KviChannelWindow * chan = msg->msg->connection()->findChannel(msg->szTarget);
1493 		if(chan)
1494 		{
1495 			KviCString szBuffer;
1496 			const char * txtptr;
1497 
1498 			DECRYPT_IF_NEEDED(chan, szData8.ptr(), KVI_OUT_ACTION, KVI_OUT_ACTIONCRYPTED, szBuffer, txtptr, msgtype)
1499 
1500 			szData = chan->decodeText(txtptr);
1501 
1502 			pOut = static_cast<KviWindow *>(chan);
1503 		}
1504 		else
1505 			szData = msg->msg->connection()->decodeText(szData8.ptr());
1506 	}
1507 	else
1508 	{
1509 		KviQueryWindow * query = msg->msg->connection()->findQuery(szWindow);
1510 		if(!query)
1511 		{
1512 			// New query requested ?
1513 			// FIXME: 			#warning "CHECK FOR SPAM!"
1514 			if(KVI_OPTION_BOOL(KviOption_boolCreateQueryOnPrivmsg))
1515 			{
1516 				// We still want to create it
1517 				// Give the scripter a chance to filter it out again
1518 				if(KVS_TRIGGER_EVENT_4_HALTED(KviEvent_OnQueryWindowRequest, msg->msg->console(),
1519 				       msg->pSource->nick(),
1520 				       msg->pSource->user(),
1521 				       msg->pSource->host(),
1522 				       szData))
1523 				{
1524 					// check if the scripter hasn't created it
1525 					query = msg->msg->connection()->findQuery(szWindow);
1526 				}
1527 				else
1528 				{
1529 					// no query yet, create it!
1530 					// this will trigger OnQueryWindowCreated
1531 					query = msg->msg->console()->connection()->createQuery(szWindow);
1532 					query->setTarget(szOtherNick, szOtherUser, szOtherHost);
1533 				}
1534 				if(!KVI_OPTION_STRING(KviOption_stringOnNewQueryOpenedSound).isEmpty())
1535 				{
1536 					KviKvsVariantList soundParams{new KviKvsVariant{KVI_OPTION_STRING(KviOption_stringOnNewQueryOpenedSound)}};
1537 					KviKvsScript::run("snd.play $0", nullptr, &soundParams);
1538 				}
1539 			}
1540 		}
1541 		else
1542 		{
1543 			if(!KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound).isEmpty() && !query->hasAttention())
1544 			{
1545 				KviKvsVariantList soundParams(new KviKvsVariant(KVI_OPTION_STRING(KviOption_stringOnQueryMessageSound)));
1546 				// coverity is wrong here.
1547 				KviKvsScript::run("snd.play $0", query, &soundParams);
1548 			}
1549 		}
1550 
1551 		if(query)
1552 		{
1553 			KviCString szBuffer;
1554 			const char * txtptr;
1555 
1556 			DECRYPT_IF_NEEDED(query, szData8.ptr(), KVI_OUT_ACTION, KVI_OUT_ACTIONCRYPTED, szBuffer, txtptr, msgtype)
1557 
1558 			szData = query->decodeText(txtptr);
1559 		}
1560 		else
1561 			szData = msg->msg->connection()->decodeText(szData8.ptr());
1562 
1563 		pOut = static_cast<KviWindow *>(query);
1564 	}
1565 
1566 	bool bTargetFound = pOut;
1567 	if(!pOut)
1568 	{
1569 		pOut = KVI_OPTION_BOOL(KviOption_boolExternalMessagesToActiveWindow) ? msg->msg->console()->activeWindow() : msg->msg->console();
1570 	}
1571 
1572 	//see bug ticket #220
1573 	if(KVI_OPTION_BOOL(KviOption_boolStripMircColorsInUserMessages))
1574 		szData = KviControlCodes::stripControlBytes(szData);
1575 
1576 	if(KVS_TRIGGER_EVENT_7_HALTED(KviEvent_OnAction, pOut,
1577 	       msg->pSource->nick(),
1578 	       msg->pSource->user(),
1579 	       msg->pSource->host(),
1580 	       msg->szTarget,
1581 	       szData,
1582 	       msg->msg->messageTagsKvsHash(),
1583 	       (kvs_int_t)(msgtype == KVI_OUT_ACTIONCRYPTED)))
1584 	{
1585 		msg->msg->setHaltOutput();
1586 		return;
1587 	}
1588 
1589 	int type = msg->msg->console()->applyHighlighting(pOut, msgtype, msg->pSource->nick(), msg->pSource->user(), msg->pSource->host(), szData);
1590 
1591 	if(type < 0)
1592 		return; // event stopped the message!
1593 
1594 	if(type == KVI_OUT_HIGHLIGHT || !bIsChannel)
1595 	{
1596 		if(!pOut->hasAttention(KviWindow::MainWindowIsVisible))
1597 		{
1598 			if(KVI_OPTION_BOOL(KviOption_boolFlashWindowOnHighlightedMessages))
1599 				pOut->demandAttention();
1600 			if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnHighlightedMessages))
1601 			{
1602 				QString szMsg = "<b>";
1603 				szMsg += msg->pSource->nick();
1604 				szMsg += "</b> ";
1605 				szMsg += KviQString::toHtmlEscaped(szData);
1606 				//qDebug("KviIrcServerParser_ctcp.cpp:975 debug: %s",szMsg.data());
1607 				g_pApp->notifierMessage(pOut, KVI_OPTION_MSGTYPE(msgtype).pixId(), szMsg, KVI_OPTION_UINT(KviOption_uintNotifierAutoHideTime));
1608 			}
1609 		}
1610 	}
1611 
1612 	if(bTargetFound)
1613 	{
1614 		QString szMsg = QString("\r!n\r%1\r ").arg(msg->pSource->nick());
1615 		szMsg += szData;
1616 		if(bIsChannel)
1617 		{
1618 			((KviChannelWindow *)pOut)->outputMessage(type, szMsg, msg->msg->serverTime());
1619 		}
1620 		else
1621 		{
1622 			pOut->outputNoFmt(type, szMsg, 0, msg->msg->serverTime());
1623 		}
1624 	}
1625 	else
1626 	{
1627 		if(bIsChannel)
1628 		{
1629 			pOut->output(KVI_OUT_SYSTEMWARNING,
1630 			    __tr2qs("The following CTCP ACTION has unrecognized target %Q"),
1631 			    &(msg->szTarget));
1632 		}
1633 		KviCString buffer1, buffer2;
1634 		pOut->output(type,
1635 		    __tr2qs("CTCP ACTION from \r!n\r%Q\r [%Q@\r!h\r%Q\r]: %Q"),
1636 		    &(msg->pSource->nick()), &(msg->pSource->user()),
1637 		    &(msg->pSource->host()), &szData);
1638 	}
1639 }
1640 
1641 // FIXME: #warning "UTSNAME ?...AND OTHER INFO ?...SYSTEM IDLE TIME ?...KVIRC IDLE TIME ?"
parseCtcpRequestAvatar(KviCtcpMessage * msg)1642 void KviIrcServerParser::parseCtcpRequestAvatar(KviCtcpMessage * msg)
1643 {
1644 	// AVATAR
1645 	if(!KVI_OPTION_BOOL(KviOption_boolIgnoreCtcpAvatar))
1646 	{
1647 		QString szGenderTag = " ";
1648 		if(KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).startsWith("m", Qt::CaseInsensitive))
1649 		{
1650 			szGenderTag.append("M");
1651 		}
1652 		else if(KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).startsWith("f", Qt::CaseInsensitive))
1653 		{
1654 			szGenderTag.append("F");
1655 		}
1656 		else
1657 		{
1658 			szGenderTag.append("?");
1659 		}
1660 
1661 		KviAvatar * a = msg->msg->console()->currentAvatar();
1662 		if(a)
1663 		{
1664 			if(!checkCtcpFlood(msg))
1665 			{
1666 				// FIXME: #warning "OPTION FOR SETTING A FIXED BIND ADDRESS FOR OUTGOING DCC OFFERS"
1667 				QString szUserMask;
1668 				msg->pSource->mask(szUserMask);
1669 
1670 				QString szReply, szFileName;
1671 				szFileName = a->name();
1672 				if(KVI_OPTION_BOOL(KviOption_boolDCCFileTransferReplaceOutgoingSpacesWithUnderscores))
1673 					szFileName.replace(" ", "_");
1674 
1675 				// escape the spaces with the right octal code
1676 				encodeCtcpParameter(szFileName.toUtf8().data(), szReply);
1677 
1678 				if(!a->isRemote())
1679 				{
1680 					if(!g_pSharedFilesManager->addSharedFile(szFileName, a->localPath(), szUserMask, KVI_OPTION_UINT(KviOption_uintAvatarOfferTimeoutInSecs)))
1681 					{
1682 						msg->msg->console()->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Unable to add file offer for file %Q (File not readable?)"), &(a->localPath()));
1683 					}
1684 					else
1685 					{
1686 						if(_OUTPUT_VERBOSE)
1687 						{
1688 							msg->msg->console()->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Added %d sec file offer for file %Q (%Q) to recipient %Q"),
1689 							    KVI_OPTION_UINT(KviOption_uintAvatarOfferTimeoutInSecs), &(a->name()), &(a->localPath()), &szUserMask);
1690 						}
1691 					}
1692 				}
1693 
1694 				szReply.append(szGenderTag);
1695 				replyCtcp(msg, szReply);
1696 			}
1697 		}
1698 		else
1699 		{
1700 			// no avatar set.. ignore channel requests if the user wishes
1701 			if(!IS_ME(msg->msg, msg->szTarget))
1702 			{
1703 				// channel target
1704 				if(KVI_OPTION_BOOL(KviOption_boolIgnoreChannelAvatarRequestsWhenNoAvatarSet))
1705 					msg->bIgnored = true;
1706 			}
1707 			if(!msg->bIgnored)
1708 				replyCtcp(msg, "");
1709 		}
1710 	}
1711 	else
1712 		msg->bIgnored = true;
1713 
1714 	echoCtcpRequest(msg);
1715 }
1716 
parseCtcpReplyAvatar(KviCtcpMessage * msg)1717 void KviIrcServerParser::parseCtcpReplyAvatar(KviCtcpMessage * msg)
1718 {
1719 	QString szRemoteFile;
1720 	QString szGender;
1721 	QString decoded = msg->msg->console()->decodeText(msg->pData);
1722 
1723 	decoded = extractCtcpParameter(decoded.toUtf8().data(), szRemoteFile, true);
1724 	decoded = extractCtcpParameter(decoded.toUtf8().data(), szGender, true);
1725 	szRemoteFile = szRemoteFile.trimmed();
1726 
1727 	bool bPrivate = IS_ME(msg->msg, msg->szTarget);
1728 
1729 	QString textLine;
1730 	KviAvatar * avatar = nullptr;
1731 
1732 	bool bResetAvatar = true;
1733 
1734 	QString nickLink = QString("\r!n\r%1\r").arg(msg->pSource->nick());
1735 
1736 	KviIrcUserEntry * e = msg->msg->connection()->userDataBase()->find(msg->pSource->nick());
1737 	if(e)
1738 	{
1739 		if((szGender == "m") || (szGender == "M"))
1740 		{
1741 			e->setGender(KviIrcUserEntry::Male);
1742 		}
1743 		else if((szGender == "f") || (szGender == "F"))
1744 		{
1745 			e->setGender(KviIrcUserEntry::Female);
1746 		}
1747 		else
1748 		{
1749 			e->setGender(KviIrcUserEntry::Unknown);
1750 		}
1751 	}
1752 
1753 	QString szWhere = bPrivate ? __tr2qs("private") : __tr2qs("channel notification:");
1754 	QString szWhat = bPrivate ? __tr2qs("notification") : msg->szTarget;
1755 
1756 	if(szRemoteFile.isEmpty())
1757 	{
1758 		// avatar unset
1759 		textLine = QString(__tr2qs("%1 unsets avatar")).arg(nickLink);
1760 		if(_OUTPUT_VERBOSE)
1761 			KviQString::appendFormatted(textLine, " (%Q %Q)", &szWhere, &szWhat);
1762 	}
1763 	else
1764 	{
1765 
1766 		// FIXME: #warning "The avatar should be the one with the requested size!!"
1767 		textLine = QString(__tr2qs("%1 changes avatar to %2")).arg(nickLink, szRemoteFile);
1768 		if(_OUTPUT_VERBOSE)
1769 			KviQString::appendFormatted(textLine, " (%Q %Q)", &szWhere, &szWhat);
1770 
1771 		bool bIsUrl = ((KviQString::equalCIN("http://", szRemoteFile, 7) && (szRemoteFile.length() > 7)) || (KviQString::equalCIN("https://", szRemoteFile, 8) && (szRemoteFile.length() > 8)));
1772 		if(!bIsUrl)
1773 		{
1774 			// no hacks
1775 			KviQString::cutToLast(szRemoteFile, '/');
1776 			KviQString::cutToLast(szRemoteFile, '\\');
1777 		}
1778 
1779 		avatar = g_pIconManager->getAvatar(QString(), szRemoteFile);
1780 
1781 		if((avatar == nullptr) && e)
1782 		{
1783 			// we have no such file on our HD....
1784 			bResetAvatar = false;
1785 			// request DCC GET ?
1786 			if(KVI_OPTION_BOOL(KviOption_boolRequestMissingAvatars))
1787 			{
1788 				// FIXME: #warning "Request avatars only from registered users ?"
1789 				// FIXME: #warning "Ask before making the request ?"
1790 				if(bIsUrl)
1791 				{
1792 					QString szLocalFilePath;
1793 					QString szLocalFile = szRemoteFile;
1794 					g_pIconManager->urlToCachedFileName(szLocalFile);
1795 					g_pApp->getLocalKvircDirectory(szLocalFilePath, KviApplication::Avatars, szLocalFile);
1796 					KviQString::escapeKvs(&szLocalFilePath, KviQString::EscapeSpace);
1797 					QString szCommand = "http.get -w=nm ";
1798 					unsigned int uMaxSize = KVI_OPTION_UINT(KviOption_uintMaximumRequestedAvatarSize);
1799 					if(uMaxSize > 0)
1800 						KviQString::appendFormatted(szCommand, "-m=%u ", uMaxSize);
1801 					szRemoteFile = szRemoteFile.replace(";", "%3B");
1802 					szRemoteFile = szRemoteFile.replace("\"", "%22");
1803 					szRemoteFile = szRemoteFile.replace("\\", "\\\\");
1804 					szRemoteFile = szRemoteFile.replace("$", "\\$");
1805 					szRemoteFile = szRemoteFile.replace("%", "\\%");
1806 					szCommand += "\"" + szRemoteFile + "\"";
1807 					szCommand += " ";
1808 					szCommand += szLocalFilePath;
1809 
1810 					if(KviKvsScript::run(szCommand, msg->msg->console()))
1811 					{
1812 						if(_OUTPUT_VERBOSE)
1813 						{
1814 							KviQString::appendFormatted(textLine,
1815 							    __tr2qs(": No valid local copy of avatar available, requesting one (HTTP GET %s)"),
1816 							    szRemoteFile.toUtf8().data());
1817 						}
1818 						g_pApp->setAvatarOnFileReceived(msg->msg->console(),
1819 						    szRemoteFile, msg->pSource->nick(), msg->pSource->user(), msg->pSource->host());
1820 					}
1821 					else
1822 					{
1823 						if(_OUTPUT_VERBOSE)
1824 							KviQString::appendFormatted(textLine, __tr2qs(": No valid local copy of avatar available; failed to start an HTTP transfer, ignoring"));
1825 					}
1826 				}
1827 				else
1828 				{
1829 					if(!checkCtcpFlood(msg))
1830 					{
1831 						if(_OUTPUT_VERBOSE)
1832 						{
1833 							KviQString::appendFormatted(textLine,
1834 							    __tr2qs(": No valid local copy of avatar available, requesting one (DCC GET %s)"),
1835 							    szRemoteFile.toUtf8().data());
1836 						}
1837 
1838 						QString szFName;
1839 						encodeCtcpParameter(szRemoteFile.toUtf8().data(), szFName);
1840 						msg->msg->connection()->sendFmtData("PRIVMSG %s :%cDCC GET %s%c",
1841 						    msg->msg->connection()->encodeText(msg->pSource->nick()).data(), 0x01, msg->msg->connection()->encodeText(szFName.toUtf8().data()).data(), 0x01);
1842 						g_pApp->setAvatarOnFileReceived(msg->msg->console(),
1843 						    szRemoteFile, msg->pSource->nick(), msg->pSource->user(), msg->pSource->host());
1844 					}
1845 					else
1846 					{
1847 						if(_OUTPUT_VERBOSE)
1848 							KviQString::appendFormatted(textLine, __tr2qs(": No valid local copy of avatar available; flood limit exceeded, ignoring"));
1849 					}
1850 				}
1851 			}
1852 			else
1853 			{
1854 				if(_OUTPUT_VERBOSE)
1855 					KviQString::appendFormatted(textLine, __tr2qs(": No valid local copy of avatar available, ignoring"));
1856 			}
1857 		}
1858 	}
1859 
1860 	if(!e)
1861 	{
1862 		if(_OUTPUT_VERBOSE)
1863 			KviQString::appendFormatted(textLine, __tr2qs(": No such nickname in the user database, ignoring the change"));
1864 		msg->msg->console()->outputNoFmt(KVI_OUT_AVATAR, textLine);
1865 		return;
1866 	}
1867 
1868 	if(bResetAvatar)
1869 		e->setAvatar(avatar);
1870 
1871 	msg->msg->console()->avatarChanged(avatar, msg->pSource->nick(), msg->pSource->user(), msg->pSource->host(),
1872 	    msg->msg->haltOutput() ? QString() : textLine);
1873 }
1874 
1875 using dccModuleCtcpDccParseRoutine = void (*)(KviDccRequest *);
1876 
parseCtcpRequestDcc(KviCtcpMessage * msg)1877 void KviIrcServerParser::parseCtcpRequestDcc(KviCtcpMessage * msg)
1878 {
1879 	KviDccRequest p;
1880 	KviCString aux = msg->pData;
1881 	msg->pData = extractCtcpParameter(msg->pData, p.szType, true, true);
1882 	msg->pData = extractCtcpParameter(msg->pData, p.szParam1);
1883 	msg->pData = extractCtcpParameter(msg->pData, p.szParam2);
1884 	msg->pData = extractCtcpParameter(msg->pData, p.szParam3);
1885 	msg->pData = extractCtcpParameter(msg->pData, p.szParam4);
1886 	msg->pData = extractCtcpParameter(msg->pData, p.szParam5);
1887 	p.ctcpMsg = msg;
1888 	p.bIPv6 = msg->msg->console()->isIPv6Connection();
1889 	p.pConsole = msg->msg->console();
1890 
1891 	KviRegisteredUser * u = msg->msg->connection()->userDataBase()->registeredUser(msg->pSource->nick(), msg->pSource->user(), msg->pSource->host());
1892 
1893 	if(u)
1894 	{
1895 		if(u->isIgnoreEnabledFor(KviRegisteredUser::Dcc))
1896 		{
1897 			if(KVI_OPTION_BOOL(KviOption_boolVerboseIgnore))
1898 			{
1899 				msg->msg->console()->output(KVI_OUT_DCCREQUEST,
1900 				    __tr2qs("Ignoring DCC %S request from \r!n\r%Q\r [%Q@\r!h\r%Q\r] (%Q %S)"),
1901 				    &p.szType, &(msg->pSource->nick()),
1902 				    &(msg->pSource->user()), &(msg->pSource->host()),
1903 				    &msg->szTag, &aux);
1904 			}
1905 			return;
1906 		}
1907 	}
1908 
1909 	bool bIsFlood = checkCtcpFlood(msg);
1910 
1911 	if(bIsFlood && ((kvi_strEqualCI(p.szType.ptr(), "SEND")) || (kvi_strEqualCI(p.szType.ptr(), "RSEND")) || (kvi_strEqualCI(p.szType.ptr(), "TSEND")) || (kvi_strEqualCI(p.szType.ptr(), "TRSEND"))))
1912 	{
1913 		// don't consider as flood the avatars we have requested
1914 		if(g_pApp->findPendingAvatarChange(msg->msg->console(), msg->pSource->nick(), p.szParam1.ptr()))
1915 			bIsFlood = false;
1916 	}
1917 
1918 	if(!bIsFlood)
1919 	{
1920 		if(!msg->msg->haltOutput())
1921 		{
1922 			QString decoded = msg->msg->console()->decodeText(p.szType.ptr());
1923 			msg->msg->console()->output(KVI_OUT_DCCREQUEST,
1924 			    __tr2qs("Processing DCC %Q request from \r!n\r%Q\r [%Q@\r!h\r%Q\r] (%s %s)"),
1925 			    &decoded, &(msg->pSource->nick()),
1926 			    &(msg->pSource->user()), &(msg->pSource->host()),
1927 			    msg->szTag.toUtf8().data(),
1928 			    msg->msg->console()->decodeText(aux).toUtf8().data());
1929 		}
1930 
1931 		KviModule * m = g_pModuleManager->getModule("dcc");
1932 		if(!m)
1933 		{
1934 			msg->msg->console()->output(KVI_OUT_DCCERROR,
1935 			    __tr2qs("Unable to process the above request: can't load DCC module (%Q)"), &(g_pModuleManager->lastError()));
1936 		}
1937 		else
1938 		{
1939 			dccModuleCtcpDccParseRoutine proc = (dccModuleCtcpDccParseRoutine)m->getSymbol("dccModuleCtcpDccParseRoutine");
1940 			if(!proc)
1941 			{
1942 				msg->msg->console()->outputNoFmt(KVI_OUT_DCCERROR,
1943 				    __tr2qs("Unable to process the above request: DCC module may be broken"));
1944 			}
1945 			else
1946 			{
1947 				proc(&p);
1948 			}
1949 		}
1950 	}
1951 	else
1952 	{
1953 		// That's flood
1954 		echoCtcpRequest(msg);
1955 	}
1956 }
1957 
parseCtcpReplyUserinfo(KviCtcpMessage * msg)1958 void KviIrcServerParser::parseCtcpReplyUserinfo(KviCtcpMessage * msg)
1959 {
1960 	QString szRemoteFile;
1961 	QString szGender;
1962 	QString decoded = msg->msg->console()->decodeText(msg->pData);
1963 
1964 	bool bNeedToUpdateUserlist = false;
1965 	KviIrcUserEntry * e = msg->msg->connection()->userDataBase()->find(msg->pSource->nick());
1966 	if(e)
1967 	{
1968 		int pos = decoded.indexOf("Gender=", 0, Qt::CaseInsensitive);
1969 
1970 		if(pos >= 0)
1971 		{
1972 			QChar c = decoded[pos + 7];
1973 			switch(c.unicode())
1974 			{
1975 				case 'F':
1976 				case 'f':
1977 					bNeedToUpdateUserlist = true;
1978 					e->setGender(KviIrcUserEntry::Female);
1979 					break;
1980 				case 'M':
1981 				case 'm':
1982 					bNeedToUpdateUserlist = true;
1983 					e->setGender(KviIrcUserEntry::Male);
1984 					break;
1985 			}
1986 		}
1987 	}
1988 
1989 	if(bNeedToUpdateUserlist)
1990 	{
1991 		if(KviQString::equalCS(g_pActiveWindow->metaObject()->className(), QString("KviChannelWindow")))
1992 		{
1993 			((KviChannelWindow *)g_pActiveWindow)->userListView()->updateArea();
1994 		}
1995 	}
1996 
1997 	echoCtcpReply(msg);
1998 }
1999 
parseCtcpReplyGeneric(KviCtcpMessage * msg)2000 void KviIrcServerParser::parseCtcpReplyGeneric(KviCtcpMessage * msg)
2001 {
2002 	echoCtcpReply(msg);
2003 }
2004 
parseCtcpReplyLagcheck(KviCtcpMessage * msg)2005 void KviIrcServerParser::parseCtcpReplyLagcheck(KviCtcpMessage * msg)
2006 {
2007 	// this is an internal CTCP used for checking lag
2008 	KviCString szTag;
2009 	msg->pData = extractCtcpParameter(msg->pData, szTag, true);
2010 	if(msg->msg->console()->connection()->lagMeter())
2011 		msg->msg->console()->connection()->lagMeter()->lagCheckComplete(szTag.ptr());
2012 }
2013 
2014 //ERRORMSG,ECHO,ERRMSG
2015 //SED,DCC,SOUND/MULTIMEDIA/MM,SCRIPT
2016