X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=core%2Fserver.cpp;h=01cf281916a0e2653b09661270608895fad9bc26;hp=eeb46934e7755e96e2e5f3319cf69cb8ac62363c;hb=9d46ee920ab0687940b3d937189eff7e82025d7b;hpb=d6a96a47a3964b9c0b1ea2413601d70d0d327413 diff --git a/core/server.cpp b/core/server.cpp index eeb46934..01cf2819 100644 --- a/core/server.cpp +++ b/core/server.cpp @@ -27,7 +27,16 @@ #include Server::Server(QString net) : network(net) { + QString MQUOTE = QString('\020'); + ctcpMDequoteHash[MQUOTE + '0'] = QString('\000'); + ctcpMDequoteHash[MQUOTE + 'n'] = QString('\n'); + ctcpMDequoteHash[MQUOTE + 'r'] = QString('\r'); + ctcpMDequoteHash[MQUOTE + MQUOTE] = MQUOTE; + XDELIM = QString('\001'); + QString XQUOTE = QString('\134'); + ctcpXDelimDequoteHash[XQUOTE + XQUOTE] = XQUOTE; + ctcpXDelimDequoteHash[XQUOTE + QString('a')] = XDELIM; } Server::~Server() { @@ -75,7 +84,7 @@ void Server::disconnectFromIrc(QString net) { void Server::socketHasData() { while(socket.canReadLine()) { QString s = socket.readLine().trimmed(); - qDebug() << "Read" << s; + //qDebug() << "Read" << s; emit recvRawServerMsg(s); //Message *msg = Message::createFromServerString(this, s); handleServerMsg(s); @@ -89,7 +98,7 @@ void Server::socketError( QAbstractSocket::SocketError err ) { void Server::socketConnected( ) { emit connected(network); - putRawLine(QString("NICK :%1").arg(identity["NickList"].toStringList()[0])); + putRawLine(QString("NICK :%1").arg(identity["NickList"].toStringList()[0])); // FIXME: try more nicks if error occurs putRawLine(QString("USER %1 8 * :%2").arg(identity["Ident"].toString()).arg(identity["RealName"].toString())); } @@ -132,7 +141,7 @@ void Server::userInput(QString net, QString buf, QString msg) { } void Server::putRawLine(QString s) { - qDebug() << "SentRaw: " << s; +// qDebug() << "SentRaw: " << s; s += "\r\n"; socket.write(s.toAscii()); } @@ -145,7 +154,7 @@ void Server::putCmd(QString cmd, QStringList params, QString prefix) { m += " " + params[i]; } if(!params.isEmpty()) m += " :" + params.last(); - qDebug() << "Sent: " << m; +// qDebug() << "Sent: " << m; m += "\r\n"; socket.write(m.toAscii()); } @@ -161,34 +170,38 @@ void Server::handleServerMsg(QString msg) { QString prefix; QString cmd; QStringList params; + + // check for prefix by checking for a colon as the first char if(msg[0] == ':') { msg.remove(0,1); prefix = msg.section(' ', 0, 0); msg = msg.section(' ', 1); } + + // next string without a whitespace is the command cmd = msg.section(' ', 0, 0).toUpper(); - msg = msg.section(' ', 1); - QString left, trailing; - // RPL_ISUPPORT (005) can contain colons, so don't treat it like the rest of the commands - if(cmd.toUInt() == 5) { - left = msg.remove(QString(":are supported by this server")); - } else { - left = msg.section(':', 0, 0); - trailing = msg.section(':', 1); + msg = msg.mid(cmd.length()); + + // get the parameters + QString trailing = ""; + if(msg.contains(" :")) { + trailing = msg.section(" :", 1); + msg = msg.section(" :", 0, 0); } - if(!left.isEmpty()) { - params << left.split(' ', QString::SkipEmptyParts); + if(!msg.isEmpty()) { + params << msg.split(' ', QString::SkipEmptyParts); } if(!trailing.isEmpty()) { params << trailing; } - // numeric replies usually have our own nick as first param. Remove this! - // BTW, this behavior is not in the RFC. + + // numeric replies have the target as first param (RFC 2812 - 2.4). this is usually our own nick. Remove this! uint num = cmd.toUInt(); - if(num > 1 && params.count() > 0) { // 001 sets our nick, so we shouldn't remove anything - if(params[0] == ownNick) params.removeFirst(); - else qWarning((QString("First param NOT nick: %1:%2 %3").arg(prefix).arg(cmd).arg(params.join(" "))).toAscii()); + if(num > 0) { + Q_ASSERT(params.count() > 0); // Violation to RFC + params.removeFirst(); } + // Now we try to find a handler for this message. BTW, I do love the Trolltech guys ;-) QString hname = cmd.toLower(); hname[0] = hname[0].toUpper(); @@ -198,7 +211,7 @@ void Server::handleServerMsg(QString msg) { defaultServerHandler(cmd, prefix, params); } } catch(Exception e) { - emit displayMsg(Message::error("", e.msg())); + emit displayMsg(Message::Error, "", e.msg()); } } @@ -210,24 +223,26 @@ void Server::defaultServerHandler(QString cmd, QString prefix, QStringList param switch(num) { // Welcome, status, info messages. Just display these. case 2: case 3: case 4: case 5: case 251: case 252: case 253: case 254: case 255: case 372: case 375: - emit displayMsg(Message::server("", params.join(" "), prefix)); + emit displayMsg(Message::Server, "", params.join(" "), prefix); break; // Server error messages without param, just display them - case 409: case 411: case 412: case 422: case 424: case 431: case 445: case 446: case 451: case 462: + case 409: case 411: case 412: case 422: case 424: case 445: case 446: case 451: case 462: case 463: case 464: case 465: case 466: case 472: case 481: case 483: case 485: case 491: case 501: case 502: - emit displayMsg(Message::error("", params.join(" "), prefix)); + case 431: // ERR_NONICKNAMEGIVEN + emit displayMsg(Message::Error, "", params.join(" "), prefix); break; // Server error messages, display them in red. First param will be appended. - case 401: case 402: case 403: case 404: case 406: case 408: case 415: case 421: case 432: case 442: + case 401: case 402: case 403: case 404: case 406: case 408: case 415: case 421: case 442: { QString p = params.takeFirst(); - emit displayMsg(Message::error("", params.join(" ") + " " + p, prefix)); + emit displayMsg(Message::Error, "", params.join(" ") + " " + p, prefix); break; } // Server error messages which will be displayed with a colon between the first param and the rest - case 413: case 414: case 423: case 433: case 436: case 441: case 444: case 461: + case 413: case 414: case 423: case 441: case 444: case 461: case 467: case 471: case 473: case 474: case 475: case 476: case 477: case 478: case 482: + case 436: // ERR_NICKCOLLISION { QString p = params.takeFirst(); - emit displayMsg(Message::error("", p + ": " + params.join(" "))); + emit displayMsg(Message::Error, "", p + ": " + params.join(" ")); break; } // Ignore these commands. @@ -236,11 +251,11 @@ void Server::defaultServerHandler(QString cmd, QString prefix, QStringList param // Everything else will be marked in red, so we can add them somewhere. default: - emit displayMsg(Message::error("", cmd + " " + params.join(" "), prefix)); + emit displayMsg(Message::Error, "", cmd + " " + params.join(" "), prefix); } //qDebug() << prefix <<":"<methodCount(); i++) { + QString methodSignature(metaObject()->method(i).signature()); + if (methodSignature.startsWith("handleUser")) { + methodSignature = methodSignature.section('(',0,0); // chop the attribute list + methodSignature = methodSignature.mid(10); // strip "handleUser" + userHandlers << methodSignature; + } + } + return userHandlers; +} + +QString Server::ctcpDequote(QString message) { + QString dequotedMessage; + QString messagepart; + QHash::iterator ctcpquote; + + // copy dequote Message + for(int i = 0; i < message.size(); i++) { + messagepart = message[i]; + if(i+1 < message.size()) { + for(ctcpquote = ctcpMDequoteHash.begin(); ctcpquote != ctcpMDequoteHash.end(); ++ctcpquote) { + if(message.mid(i,2) == ctcpquote.key()) { + dequotedMessage += ctcpquote.value(); + i++; + break; + } + } + } + dequotedMessage += messagepart; + } + return dequotedMessage; +} + + +QString Server::ctcpXdelimDequote(QString message) { + QString dequotedMessage; + QString messagepart; + QHash::iterator xdelimquote; + + for(int i = 0; i < message.size(); i++) { + messagepart = message[i]; + if(i+1 < message.size()) { + for(xdelimquote = ctcpXDelimDequoteHash.begin(); xdelimquote != ctcpXDelimDequoteHash.end(); ++xdelimquote) { + if(message.mid(i,2) == xdelimquote.key()) { + messagepart = xdelimquote.value(); + i++; + break; + } + } + } + dequotedMessage += messagepart; + } + return dequotedMessage; +} + +QStringList Server::parseCtcp(CtcpType ctcptype, QString prefix, QString target, QString message) { + QStringList messages; + QString ctcp; + + //lowlevel message dequote + QString dequotedMessage = ctcpDequote(message); + + // extract tagged / extended data + while(dequotedMessage.contains(XDELIM)) { + messages << dequotedMessage.section(XDELIM,0,0); + ctcp = ctcpXdelimDequote(dequotedMessage.section(XDELIM,1,1)); + dequotedMessage = dequotedMessage.section(XDELIM,2,2); + + //dispatch the ctcp command + QString ctcpcmd = ctcp.section(' ', 0, 0); + QString ctcpparam = ctcp.section(' ', 1); + + QString hname = ctcpcmd.toLower(); + hname[0] = hname[0].toUpper(); + hname = "handleCtcp" + hname; + if(!QMetaObject::invokeMethod(this, hname.toAscii(), Q_ARG(CtcpType, ctcptype), Q_ARG(QString, prefix), Q_ARG(QString, target), Q_ARG(QString, ctcpparam))) { + // Ok. Default handler it is. + defaultCtcpHandler(ctcptype, prefix, ctcpcmd, target, ctcpparam); + } + } + if(!dequotedMessage.isEmpty()) { + messages << dequotedMessage; + } + return messages; +} + +QString Server::ctcpPack(QString ctcpTag, QString message) { + return XDELIM + ctcpTag + ' ' + message + XDELIM; +} +void Server::ctcpQuery(QString bufname, QString ctcpTag, QString message) { + QStringList params; + params << bufname << ctcpPack(ctcpTag, message); + putCmd("PRIVMSG", params); +} + +void Server::ctcpReply(QString bufname, QString ctcpTag, QString message) { + QStringList params; + params << bufname << ctcpPack(ctcpTag, message); + putCmd("NOTICE", params); } /**********************************************************************************/ @@ -326,6 +445,7 @@ void Server::handleUserMode(QString bufname, QString msg) { putCmd("MODE", msg.split(' ', QString::SkipEmptyParts)); } +// TODO: show privmsgs void Server::handleUserMsg(QString bufname, QString msg) { QString nick = msg.section(" ", 0, 0); msg = msg.section(" ", 1); @@ -354,6 +474,12 @@ void Server::handleUserPart(QString bufname, QString msg) { putCmd("PART", params); } +// TODO: implement queries +void Server::handleUserQuery(QString bufname, QString msg) { + QString nick = msg.section(' ', 0, 0); + if(!nick.isEmpty()) emit queryRequested(network, nick); +} + void Server::handleUserQuit(QString bufname, QString msg) { putCmd("QUIT", QStringList(msg)); } @@ -367,7 +493,17 @@ void Server::handleUserSay(QString bufname, QString msg) { QStringList params; params << bufname << msg; putCmd("PRIVMSG", params); - emit displayMsg(Message::plain(params[0], msg, ownNick, Message::Self)); + if(isChannelName(bufname)) { + emit displayMsg(Message::Plain, params[0], msg, ownNick, Message::Self); + } else { + emit displayMsg(Message::Plain, params[0], msg, ownNick, Message::Self|Message::PrivMsg); + } +} + +void Server::handleUserMe(QString bufname, QString msg) { + if(bufname.isEmpty()) return; // server buffer + ctcpQuery(bufname, "ACTION", msg); + emit displayMsg(Message::Action, bufname, msg, ownNick); } void Server::handleUserTopic(QString bufname, QString msg) { @@ -390,6 +526,7 @@ void Server::handleUserVoice(QString bufname, QString msg) { void Server::handleServerJoin(QString prefix, QStringList params) { Q_ASSERT(params.count() == 1); QString nick = updateNickFromMask(prefix); + emit displayMsg(Message::Join, params[0], params[0], prefix); if(nick == ownNick) { // Q_ASSERT(!buffers.contains(params[0])); // cannot join a buffer twice! // Buffer *buf = new Buffer(params[0]); @@ -415,7 +552,7 @@ void Server::handleServerJoin(QString prefix, QStringList params) { nicks[nick] = n; emit nickAdded(network, nick, n); } - emit displayMsg(Message::join(params[0], params[0], prefix)); + //emit displayMsg(Message::Join, params[0], params[0], prefix); //} } @@ -429,7 +566,7 @@ void Server::handleServerKick(QString prefix, QStringList params) { chans.remove(params[0]); QString msg = nick; if(params.count() > 2) msg = QString("%1 %2").arg(msg).arg(params[2]); - emit displayMsg(Message::kick(params[0], msg, prefix)); + emit displayMsg(Message::Kick, params[0], msg, prefix); if(chans.count() > 0) { n["Channels"] = chans; nicks[nick] = n; @@ -470,12 +607,12 @@ void Server::handleServerMode(QString prefix, QStringList params) { m++; } } - emit displayMsg(Message::mode(params[0], params.join(" "), prefix)); + emit displayMsg(Message::Mode, params[0], params.join(" "), prefix); } else { //Q_ASSERT(nicks.contains(params[0])); //VarMap n = nicks[params[0]].toMap(); //QString mode = n["Mode"].toString(); - emit displayMsg(Message::mode("", params.join(" "))); + emit displayMsg(Message::Mode, "", params.join(" ")); } } @@ -486,8 +623,8 @@ void Server::handleServerNick(QString prefix, QStringList params) { nicks[newnick] = v; VarMap chans = v["Channels"].toMap(); foreach(QString c, chans.keys()) { - if(oldnick != ownNick) { emit displayMsg(Message::nick(c, newnick, prefix)); } - else { emit displayMsg(Message::nick(c, newnick, newnick)); } + if(oldnick != ownNick) { emit displayMsg(Message::Nick, c, newnick, prefix); } + else { emit displayMsg(Message::Nick, c, newnick, newnick); } } emit nickRenamed(network, oldnick, newnick); if(oldnick == ownNick) { @@ -498,8 +635,8 @@ void Server::handleServerNick(QString prefix, QStringList params) { void Server::handleServerNotice(QString prefix, QStringList params) { //Message msg(Message::Notice, params[1], prefix); - if(prefix == currentServer) emit displayMsg(Message::server("", params[1], prefix)); - else emit displayMsg(Message::notice("", params[1], prefix)); + if(currentServer.isEmpty() || prefix == currentServer) emit displayMsg(Message::Server, "", params[1], prefix); + else emit displayMsg(Message::Notice, "", params[1], prefix); } void Server::handleServerPart(QString prefix, QStringList params) { @@ -511,7 +648,7 @@ void Server::handleServerPart(QString prefix, QStringList params) { chans.remove(params[0]); QString msg; if(params.count() > 1) msg = params[1]; - emit displayMsg(Message::part(params[0], msg, prefix)); + emit displayMsg(Message::Part, params[0], msg, prefix); if(chans.count() > 0) { n["Channels"] = chans; nicks[nick] = n; @@ -532,8 +669,28 @@ void Server::handleServerPing(QString prefix, QStringList params) { void Server::handleServerPrivmsg(QString prefix, QStringList params) { updateNickFromMask(prefix); - if(params.count()<2) emit displayMsg(Message::plain(params[0], "", prefix)); - else emit displayMsg(Message::plain(params[0], params[1], prefix)); + Q_ASSERT(params.count() >= 2); + if(params.count()<2) emit displayMsg(Message::Plain, params[0], "", prefix); + else { + // it's possible to pack multiple privmsgs into one param using ctcp + QStringList messages = parseCtcp(Server::CtcpQuery, prefix, params[0], params[1]); + if(params[0].toLower() == ownNick.toLower()) { // Freenode sends nickname in lower case! + foreach(QString message, messages) { + if(!message.isEmpty()) { + emit displayMsg(Message::Plain, "", message, prefix, Message::PrivMsg); + } + } + + } else { + //qDebug() << prefix << params; + Q_ASSERT(isChannelName(params[0])); // should be channel! + foreach(QString message, messages) { + if(!message.isEmpty()) { + emit displayMsg(Message::Plain, params[0], message, prefix); + } + } + } + } } void Server::handleServerQuit(QString prefix, QStringList params) { @@ -541,7 +698,7 @@ void Server::handleServerQuit(QString prefix, QStringList params) { Q_ASSERT(nicks.contains(nick)); VarMap chans = nicks[nick]["Channels"].toMap(); foreach(QString c, chans.keys()) { - emit displayMsg(Message::quit(c, params[0], prefix)); + emit displayMsg(Message::Quit, c, params[0], prefix); } nicks.remove(nick); emit nickRemoved(network, nick); @@ -552,23 +709,34 @@ void Server::handleServerTopic(QString prefix, QStringList params) { Q_ASSERT(nicks.contains(nick)); topics[params[0]] = params[1]; emit topicSet(network, params[0], params[1]); - emit displayMsg(Message::server(params[0], tr("%1 has changed topic for %2 to: \"%3\"").arg(nick).arg(params[0]).arg(params[1]))); + emit displayMsg(Message::Server, params[0], tr("%1 has changed topic for %2 to: \"%3\"").arg(nick).arg(params[0]).arg(params[1])); } /* RPL_WELCOME */ void Server::handleServer001(QString prefix, QStringList params) { + // there should be only one param: "Welcome to the Internet Relay Network !@" currentServer = prefix; - ownNick = params[0]; + ownNick = params[0].section(' ', -1, -1).section('!', 0, 0); VarMap n; n["Channels"] = VarMap(); nicks[ownNick] = n; emit ownNickSet(network, ownNick); emit nickAdded(network, ownNick, VarMap()); - emit displayMsg(Message::server("", params[1], prefix)); + emit displayMsg(Message::Server, "", params[0], prefix); + // send performlist + QStringList performList = networkSettings["Perform"].toString().split( "\n" ); + int count = performList.count(); + for(int a = 0; a < count; a++) { + if(!performList[a].isEmpty() ) { + userInput(network, "", performList[a]); + } + } } /* RPL_ISUPPORT */ +// TODO Complete 005 handling, also use sensible defaults for non-sent stuff void Server::handleServer005(QString prefix, QStringList params) { + params.removeLast(); foreach(QString p, params) { QString key = p.section("=", 0, 0); QString val = p.section("=", 1); @@ -596,20 +764,20 @@ void Server::handleServer005(QString prefix, QStringList params) { void Server::handleServer331(QString prefix, QStringList params) { topics[params[0]] = ""; emit topicSet(network, params[0], ""); - emit displayMsg(Message::server(params[0], tr("No topic is set for %1.").arg(params[0]))); + emit displayMsg(Message::Server, params[0], tr("No topic is set for %1.").arg(params[0])); } /* RPL_TOPIC */ void Server::handleServer332(QString prefix, QStringList params) { topics[params[0]] = params[1]; emit topicSet(network, params[0], params[1]); - emit displayMsg(Message::server(params[0], tr("Topic for %1 is \"%2\"").arg(params[0]).arg(params[1]))); + emit displayMsg(Message::Server, params[0], tr("Topic for %1 is \"%2\"").arg(params[0]).arg(params[1])); } /* Topic set by... */ void Server::handleServer333(QString prefix, QStringList params) { - emit displayMsg(Message::server(params[0], - tr("Topic set by %1 on %2").arg(params[1]).arg(QDateTime::fromTime_t(params[2].toUInt()).toString()))); + emit displayMsg(Message::Server, params[0], + tr("Topic set by %1 on %2").arg(params[1]).arg(QDateTime::fromTime_t(params[2].toUInt()).toString())); } /* RPL_NAMREPLY */ @@ -643,6 +811,83 @@ void Server::handleServer353(QString prefix, QStringList params) { } } } + +/* ERR_ERRONEUSNICKNAME */ +void Server::handleServer432(QString prefix, QStringList params) { + if(params.size() < 2) { + // handle unreal-ircd bug, where unreal ircd doesnt supply a TARGET in ERR_ERRONEUSNICKNAME during registration phase: + // nick @@@ + // :irc.scortum.moep.net 432 @@@ :Erroneous Nickname: Illegal characters + // correct server reply: + // :irc.scortum.moep.net 432 * @@@ :Erroneous Nickname: Illegal characters + emit displayMsg(Message::Error, "", tr("There is a nickname in your identity's nicklist which contains illegal characters")); + emit displayMsg(Message::Error, "", tr("Due to a bug in Unreal IRCd (and maybe other irc-servers too) we're unable to determine the erroneous nick")); + emit displayMsg(Message::Error, "", tr("Please use: /nick to continue or clean up your nicklist")); + } else { + QString errnick = params[0]; + emit displayMsg(Message::Error, "", tr("Nick %1 contains illegal characters").arg(errnick)); + // if there is a problem while connecting to the server -> we handle it + // TODO rely on another source... + if(currentServer.isEmpty()) { + QStringList desiredNicks = identity["NickList"].toStringList(); + int nextNick = desiredNicks.indexOf(errnick) + 1; + if (desiredNicks.size() > nextNick) { + putCmd("NICK", QStringList(desiredNicks[nextNick])); + } else { + emit displayMsg(Message::Error, "", tr("No free and valid nicks in nicklist found. use: /nick to continue")); + } + } + } +} + +/* ERR_NICKNAMEINUSE */ +void Server::handleServer433(QString prefix, QStringList params) { + QString errnick = params[0]; + emit displayMsg(Message::Error, "", tr("Nick %1 is already taken").arg(errnick)); + // if there is a problem while connecting to the server -> we handle it + // TODO rely on another source... + if(currentServer.isEmpty()) { + QStringList desiredNicks = identity["NickList"].toStringList(); + int nextNick = desiredNicks.indexOf(errnick) + 1; + if (desiredNicks.size() > nextNick) { + putCmd("NICK", QStringList(desiredNicks[nextNick])); + } else { + emit displayMsg(Message::Error, "", tr("No free and valid nicks in nicklist found. use: /nick to continue")); + } + } +} + +/***********************************************************************************/ +// CTCP HANDLER + +void Server::handleCtcpAction(CtcpType ctcptype, QString prefix, QString target, QString param) { + emit displayMsg(Message::Action, target, param, prefix); +} + +void Server::handleCtcpPing(CtcpType ctcptype, QString prefix, QString target, QString param) { + if(ctcptype == CtcpQuery) { + ctcpReply(userFromMask(prefix), "PING", param); + emit displayMsg(Message::Plain, "", tr("Received CTCP PING request by %1").arg(prefix)); + } else { + // display ping answer + } +} + +void Server::handleCtcpVersion(CtcpType ctcptype, QString prefix, QString target, QString param) { + if(ctcptype == CtcpQuery) { + // FIXME use real Info about quasel :) + ctcpReply(userFromMask(prefix), "VERSION", QString("Quassel:pre Release:*nix")); + emit displayMsg(Message::Plain, "", tr("Received CTCP VERSION request by %1").arg(prefix)); + } else { + // TODO display Version answer + } +} + +void Server::defaultCtcpHandler(CtcpType ctcptype, QString prefix, QString cmd, QString target, QString param) { + emit displayMsg(Message::Error, "", tr("Received unknown CTCP %1 by %2").arg(cmd).arg(prefix)); +} + + /***********************************************************************************/ /* Exception classes for message handling */