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