/* kircmessage.cpp - IRC Client Copyright (c) 2003 by Michel Hermier Kopete (c) 2003 by the Kopete engineelopers ************************************************************************* * * * 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 #include #include #include 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::fromLatin1("\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::fromLatin1("(%1 bytes) >> %2").arg(wrote).arg(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::fromLatin1(" :") + 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::fromLatin1(" :") + 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.at(length-2) = '\0'; } if (length > 2 && raw.at(length-3) == '\r') { raw.at(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::fromLatin1("r")); tmp.replace(TQChar('\n'), q+TQString::fromLatin1("n")); tmp.replace(TQChar('\0'), q+TQString::fromLatin1("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::fromLatin1("\\\\")); tmp.replace( (char)1, TQString::fromLatin1("\\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::fromLatin1(" :") + 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(); } }