|
|
|
/*
|
|
|
|
kircmessage.cpp - IRC Client
|
|
|
|
|
|
|
|
Copyright (c) 2003 by Michel Hermier <michel.hermier@wanadoo.fr>
|
|
|
|
|
|
|
|
Kopete (c) 2003 by the Kopete engineelopers <kopete-engineel@kde.org>
|
|
|
|
|
|
|
|
*************************************************************************
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
*************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "kircengine.h"
|
|
|
|
#include "kircmessage.h"
|
|
|
|
|
|
|
|
// FIXME: Remove the following dependencies.
|
|
|
|
#include "kopetemessage.h"
|
|
|
|
#include "ksparser.h"
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kextsock.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kmessagebox.h>
|
|
|
|
|
|
|
|
using namespace KIRC;
|
|
|
|
|
|
|
|
#ifndef _IRC_STRICTNESS_
|
|
|
|
TQRegExp Message::m_IRCNumericCommand("^\\d{1,3}$");
|
|
|
|
|
|
|
|
// TODO: This regexp parsing is no good. It's slower than it needs to be, and
|
|
|
|
// is not codec-safe since TQString requires a codec. NEed to parse this with
|
|
|
|
// our own parsing class that operates on the raw TQCStrings
|
|
|
|
TQRegExp Message::m_IRCCommandType1(
|
|
|
|
"^(?::([^ ]+) )?([A-Za-z]+|\\d{1,3})((?: [^ :][^ ]*)*) ?(?: :(.*))?$");
|
|
|
|
// Extra end arg space check -------------------------^
|
|
|
|
#else // _IRC_STRICTNESS_
|
|
|
|
TQRegExp Message::m_IRCNumericCommand("^\\d{3,3}$");
|
|
|
|
|
|
|
|
TQRegExp Message::m_IRCCommandType1(
|
|
|
|
"^(?::([^ ]+) )?([A-Za-z]+|\\d{3,3})((?: [^ :][^ ]*){0,13})(?: :(.*))?$");
|
|
|
|
TQRegExp Message::m_IRCCommandType2(
|
|
|
|
"^(?::[[^ ]+) )?([A-Za-z]+|\\d{3,3})((?: [^ :][^ ]*){14,14})(?: (.*))?$");
|
|
|
|
#endif // _IRC_STRICTNESS_
|
|
|
|
|
|
|
|
Message::Message()
|
|
|
|
: m_ctcpMessage(0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Message::Message(const Message &obj)
|
|
|
|
: m_ctcpMessage(0)
|
|
|
|
{
|
|
|
|
m_raw = obj.m_raw;
|
|
|
|
|
|
|
|
m_prefix = obj.m_prefix;
|
|
|
|
m_command = obj.m_command;
|
|
|
|
m_args = obj.m_args;
|
|
|
|
m_suffix = obj.m_suffix;
|
|
|
|
|
|
|
|
m_ctcpRaw = obj.m_ctcpRaw;
|
|
|
|
|
|
|
|
if (obj.m_ctcpMessage)
|
|
|
|
m_ctcpMessage = new Message(obj.m_ctcpMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
Message::Message(const Message *obj)
|
|
|
|
: m_ctcpMessage(0)
|
|
|
|
{
|
|
|
|
m_raw = obj->m_raw;
|
|
|
|
|
|
|
|
m_prefix = obj->m_prefix;
|
|
|
|
m_command = obj->m_command;
|
|
|
|
m_args = obj->m_args;
|
|
|
|
m_suffix = obj->m_suffix;
|
|
|
|
|
|
|
|
m_ctcpRaw = obj->m_ctcpRaw;
|
|
|
|
|
|
|
|
if (obj->m_ctcpMessage)
|
|
|
|
m_ctcpMessage = new Message(obj->m_ctcpMessage);
|
|
|
|
}
|
|
|
|
|
|
|
|
Message::~Message()
|
|
|
|
{
|
|
|
|
if (m_ctcpMessage)
|
|
|
|
delete m_ctcpMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::writeRawMessage(Engine *engine, const TQTextCodec *codec, const TQString &str)
|
|
|
|
{
|
|
|
|
// FIXME: Really handle this
|
|
|
|
if (!engine->socket())
|
|
|
|
{
|
|
|
|
kdDebug(14121) << k_funcinfo << "Not connected while attempting to write:" << str << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString txt = str + TQString::tqfromLatin1("\r\n");
|
|
|
|
|
|
|
|
TQCString s(codec->fromUnicode(txt));
|
|
|
|
kdDebug(14120) << "Message is " << s.length() << " chars" << endl;
|
|
|
|
// FIXME: Should check the amount of data really writen.
|
|
|
|
int wrote = engine->socket()->writeBlock(s.data(), s.length());
|
|
|
|
|
|
|
|
kdDebug(14121) << TQString::tqfromLatin1("(%1 bytes) >> %2").tqarg(wrote).tqarg(str) << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::writeMessage(Engine *engine, const TQTextCodec *codec, const TQString &message)
|
|
|
|
{
|
|
|
|
writeRawMessage(engine, codec, quote(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::writeMessage(Engine *engine, const TQTextCodec *codec,
|
|
|
|
const TQString &command, const TQStringList &args, const TQString &suffix)
|
|
|
|
{
|
|
|
|
TQString msg = command;
|
|
|
|
|
|
|
|
if (!args.isEmpty())
|
|
|
|
msg += TQChar(' ') + args.join(TQChar(' ')).stripWhiteSpace(); // some extra check should be done here
|
|
|
|
|
|
|
|
if (!suffix.isNull())
|
|
|
|
msg = msg.stripWhiteSpace() + TQString::tqfromLatin1(" :") + suffix;
|
|
|
|
|
|
|
|
writeMessage(engine, codec, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::writeCtcpMessage(Engine *engine, const TQTextCodec *codec,
|
|
|
|
const TQString &command, const TQString&to,
|
|
|
|
const TQString &ctcpMessage)
|
|
|
|
{
|
|
|
|
writeMessage(engine, codec, command, to, TQChar(0x01) + ctcpQuote(ctcpMessage) + TQChar(0x01));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::writeCtcpMessage(Engine *engine, const TQTextCodec *codec,
|
|
|
|
const TQString &command, const TQString &to, const TQString &suffix,
|
|
|
|
const TQString &ctcpCommand, const TQStringList &ctcpArgs, const TQString &ctcpSuffix )
|
|
|
|
{
|
|
|
|
TQString ctcpMsg = ctcpCommand;
|
|
|
|
|
|
|
|
if (!ctcpArgs.isEmpty())
|
|
|
|
ctcpMsg += TQChar(' ') + ctcpArgs.join(TQChar(' ')).stripWhiteSpace(); // some extra check should be done here
|
|
|
|
|
|
|
|
if (!ctcpSuffix.isNull())
|
|
|
|
ctcpMsg += TQString::tqfromLatin1(" :") + ctcpSuffix;
|
|
|
|
|
|
|
|
writeMessage(engine, codec, command, to, suffix + TQChar(0x01) + ctcpQuote(ctcpMsg) + TQChar(0x01));
|
|
|
|
}
|
|
|
|
|
|
|
|
Message Message::parse(Engine *engine, const TQTextCodec *codec, bool *parseSuccess)
|
|
|
|
{
|
|
|
|
if (parseSuccess)
|
|
|
|
*parseSuccess=false;
|
|
|
|
|
|
|
|
if (engine->socket()->canReadLine())
|
|
|
|
{
|
|
|
|
TQCString raw(engine->socket()->bytesAvailable()+1);
|
|
|
|
TQ_LONG length = engine->socket()->readLine(raw.data(), raw.count());
|
|
|
|
|
|
|
|
if( length > -1 )
|
|
|
|
{
|
|
|
|
raw.resize( length );
|
|
|
|
|
|
|
|
// Remove trailing '\r\n' or '\n'.
|
|
|
|
//
|
|
|
|
// Some servers send '\n' instead of '\r\n' that the RFCs say they should be sending.
|
|
|
|
|
|
|
|
if (length > 1 && raw.at(length-2) == '\n') {
|
|
|
|
raw.tqat(length-2) = '\0';
|
|
|
|
}
|
|
|
|
if (length > 2 && raw.at(length-3) == '\r') {
|
|
|
|
raw.tqat(length-3) = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
kdDebug(14121) << "<< " << raw << endl;
|
|
|
|
|
|
|
|
Message msg;
|
|
|
|
if(matchForIRCRegExp(raw, codec, msg))
|
|
|
|
{
|
|
|
|
if(parseSuccess)
|
|
|
|
*parseSuccess = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kdDebug(14120) << k_funcinfo << "Unmatched line: \"" << raw << "\"" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
kdWarning(14121) << k_funcinfo << "Failed to read a line while canReadLine returned true!" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Message();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Message::quote(const TQString &str)
|
|
|
|
{
|
|
|
|
TQString tmp = str;
|
|
|
|
TQChar q('\020');
|
|
|
|
tmp.replace(q, q+TQString(q));
|
|
|
|
tmp.replace(TQChar('\r'), q+TQString::tqfromLatin1("r"));
|
|
|
|
tmp.replace(TQChar('\n'), q+TQString::tqfromLatin1("n"));
|
|
|
|
tmp.replace(TQChar('\0'), q+TQString::tqfromLatin1("0"));
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: The unquote system is buggy.
|
|
|
|
TQString Message::unquote(const TQString &str)
|
|
|
|
{
|
|
|
|
TQString tmp = str;
|
|
|
|
|
|
|
|
char b[3] = { 020, 020, '\0' };
|
|
|
|
const char b2[2] = { 020, '\0' };
|
|
|
|
|
|
|
|
tmp.replace( b, b2 );
|
|
|
|
b[1] = 'r';
|
|
|
|
tmp.replace( b, "\r");
|
|
|
|
b[1] = 'n';
|
|
|
|
tmp.replace( b, "\n");
|
|
|
|
b[1] = '0';
|
|
|
|
tmp.replace( b, "\0");
|
|
|
|
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Message::ctcpQuote(const TQString &str)
|
|
|
|
{
|
|
|
|
TQString tmp = str;
|
|
|
|
tmp.replace( TQChar('\\'), TQString::tqfromLatin1("\\\\"));
|
|
|
|
tmp.replace( (char)1, TQString::tqfromLatin1("\\1"));
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Message::ctcpUnquote(const TQString &str)
|
|
|
|
{
|
|
|
|
TQString tmp = str;
|
|
|
|
tmp.replace("\\\\", "\\");
|
|
|
|
tmp.replace("\\1", "\1" );
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Message::matchForIRCRegExp(const TQCString &line, const TQTextCodec *codec, Message &message)
|
|
|
|
{
|
|
|
|
if(matchForIRCRegExp(m_IRCCommandType1, codec, line, message))
|
|
|
|
return true;
|
|
|
|
#ifdef _IRC_STRICTNESS_
|
|
|
|
if(!matchForIRCRegExp(m_IRCCommandType2, codec, line, message))
|
|
|
|
return true;
|
|
|
|
#endif // _IRC_STRICTNESS_
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: remove the decodeStrings calls or update them.
|
|
|
|
// FIXME: avoid the recursive call, it make the ctcp command unquoted twice (wich is wrong, but valid in most of the cases)
|
|
|
|
bool Message::matchForIRCRegExp(TQRegExp ®exp, const TQTextCodec *codec, const TQCString &line, Message &msg )
|
|
|
|
{
|
|
|
|
if( regexp.exactMatch( codec->toUnicode(line) ) )
|
|
|
|
{
|
|
|
|
msg.m_raw = line;
|
|
|
|
msg.m_prefix = unquote(regexp.cap(1));
|
|
|
|
msg.m_command = unquote(regexp.cap(2));
|
|
|
|
msg.m_args = TQStringList::split(' ', regexp.cap(3));
|
|
|
|
|
|
|
|
TQCString suffix = codec->fromUnicode(unquote(regexp.cap(4)));
|
|
|
|
if (!suffix.isNull() && suffix.length() > 0)
|
|
|
|
{
|
|
|
|
TQCString ctcpRaw;
|
|
|
|
if (extractCtcpCommand(suffix, ctcpRaw))
|
|
|
|
{
|
|
|
|
msg.m_ctcpRaw = codec->toUnicode(ctcpRaw);
|
|
|
|
|
|
|
|
msg.m_ctcpMessage = new Message();
|
|
|
|
msg.m_ctcpMessage->m_raw = codec->fromUnicode(ctcpUnquote(msg.m_ctcpRaw));
|
|
|
|
|
|
|
|
int space = ctcpRaw.find(' ');
|
|
|
|
if (!matchForIRCRegExp(msg.m_ctcpMessage->m_raw, codec, *msg.m_ctcpMessage))
|
|
|
|
{
|
|
|
|
TQCString command;
|
|
|
|
if (space > 0)
|
|
|
|
command = ctcpRaw.mid(0, space).upper();
|
|
|
|
else
|
|
|
|
command = ctcpRaw.upper();
|
|
|
|
msg.m_ctcpMessage->m_command =
|
|
|
|
Kopete::Message::decodeString( KSParser::parse(command), codec );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (space > 0)
|
|
|
|
msg.m_ctcpMessage->m_ctcpRaw =
|
|
|
|
Kopete::Message::decodeString( KSParser::parse(ctcpRaw.mid(space)), codec );
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.m_suffix = Kopete::Message::decodeString( KSParser::parse(suffix), codec );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
msg.m_suffix = TQString();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::decodeAgain( const TQTextCodec *codec )
|
|
|
|
{
|
|
|
|
matchForIRCRegExp(m_raw, codec, *this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: there are missing parts
|
|
|
|
TQString Message::toString() const
|
|
|
|
{
|
|
|
|
if( !isValid() )
|
|
|
|
return TQString();
|
|
|
|
|
|
|
|
TQString msg = m_command;
|
|
|
|
for (TQStringList::ConstIterator it = m_args.begin(); it != m_args.end(); ++it)
|
|
|
|
msg += TQChar(' ') + *it;
|
|
|
|
if (!m_suffix.isNull())
|
|
|
|
msg += TQString::tqfromLatin1(" :") + m_suffix;
|
|
|
|
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Message::isNumeric() const
|
|
|
|
{
|
|
|
|
return m_IRCNumericCommand.exactMatch(m_command);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Message::isValid() const
|
|
|
|
{
|
|
|
|
// This could/should be more complex but the message validity is tested durring the parsing
|
|
|
|
// So this is enougth as we don't allow the editing the content.
|
|
|
|
return !m_command.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return true if the given string is a special command string
|
|
|
|
* (i.e start and finish with the ascii code \001), and the given
|
|
|
|
* string is splited to get the first part of the message and fill the ctcp command.
|
|
|
|
* FIXME: The code currently only match for a textual message or a ctcp message not both mixed as it can be (even if very rare).
|
|
|
|
*/
|
|
|
|
bool Message::extractCtcpCommand(TQCString &message, TQCString &ctcpline)
|
|
|
|
{
|
|
|
|
uint len = message.length();
|
|
|
|
|
|
|
|
if( message[0] == 1 && message[len-1] == 1 )
|
|
|
|
{
|
|
|
|
ctcpline = message.mid(1,len-2);
|
|
|
|
message.truncate(0);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Message::dump() const
|
|
|
|
{
|
|
|
|
kdDebug(14120) << "Raw:" << m_raw << endl
|
|
|
|
<< "Prefix:" << m_prefix << endl
|
|
|
|
<< "Command:" << m_command << endl
|
|
|
|
<< "Args:" << m_args << endl
|
|
|
|
<< "Suffix:" << m_suffix << endl
|
|
|
|
<< "CtcpRaw:" << m_ctcpRaw << endl;
|
|
|
|
if(m_ctcpMessage)
|
|
|
|
{
|
|
|
|
kdDebug(14120) << "Contains CTCP Message:" << endl;
|
|
|
|
m_ctcpMessage->dump();
|
|
|
|
}
|
|
|
|
}
|