diff options
Diffstat (limited to 'knode/knnntpclient.cpp')
-rw-r--r-- | knode/knnntpclient.cpp | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/knode/knnntpclient.cpp b/knode/knnntpclient.cpp new file mode 100644 index 000000000..2c594be34 --- /dev/null +++ b/knode/knnntpclient.cpp @@ -0,0 +1,748 @@ +/* + knnntpclient.cpp + + KNode, the KDE newsreader + Copyright (c) 1999-2001 the KNode authors. + See file AUTHORS for details + + 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. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US +*/ + +#include <stdlib.h> +#include <klocale.h> +#include <qtextcodec.h> +#include <qmutex.h> + +#include "kngroupmanager.h" +#include "knnntpclient.h" +#include "utilities.h" + + +KNNntpClient::KNNntpClient(int NfdPipeIn, int NfdPipeOut, QMutex& nntpMutex) +: KNProtocolClient(NfdPipeIn,NfdPipeOut), mutex(nntpMutex) +{} + + +KNNntpClient::~KNNntpClient() +{} + + +// examines the job and calls the suitable handling method +void KNNntpClient::processJob() +{ + switch (job->type()) { + case KNJobData::JTLoadGroups : + doLoadGroups(); + break; + case KNJobData::JTFetchGroups : + doFetchGroups(); + break; + case KNJobData::JTCheckNewGroups : + doCheckNewGroups(); + break; + case KNJobData::JTfetchNewHeaders : + case KNJobData::JTsilentFetchNewHeaders : + doFetchNewHeaders(); + break; + case KNJobData::JTfetchArticle : + doFetchArticle(); + break; + case KNJobData::JTpostArticle : + doPostArticle(); + break; + case KNJobData::JTfetchSource : + doFetchSource(); + break; + default: +#ifndef NDEBUG + qDebug("knode: KNNntpClient::processJob(): mismatched job"); +#endif + break; + } +} + + +void KNNntpClient::doLoadGroups() +{ + KNGroupListData *target = static_cast<KNGroupListData *>(job->data()); + sendSignal(TSloadGrouplist); + + if (!target->readIn(this)) + job->setErrorString(i18n("Unable to read the group list file")); +} + + +void KNNntpClient::doFetchGroups() +{ + KNGroupListData *target = static_cast<KNGroupListData *>(job->data()); + + sendSignal(TSdownloadGrouplist); + errorPrefix = i18n("The group list could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = 30000; // rule of thumb ;-) + + if (!sendCommandWCheck("LIST",215)) // 215 list of newsgroups follows + return; + + char *s, *line; + QString name; + KNGroup::Status status; + bool subscribed; + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + break; // message complete + } + s = strchr(line,' '); + if(!s) { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-line - ignoring"); +#endif + } else { + s[0] = 0; // cut string + + name = QString::fromUtf8(line); + + if (target->subscribed.contains(name)) { + target->subscribed.remove(name); // group names are unique, we wont find it again anyway... + subscribed = true; + } else + subscribed = false; + + while (s[1]!=0) s++; // the last character determines the moderation status + switch (s[0]) { + case 'n' : status = KNGroup::readOnly; + break; + case 'y' : status = KNGroup::postingAllowed; + break; + case 'm' : status = KNGroup::moderated; + break; + default : status = KNGroup::unknown; + } + + target->groups->append(new KNGroupInfo(name,QString::null,false,subscribed,status)); + } + doneLines++; + } + + if (!job->success() || job->canceled()) + return; // stopped... + + QSortedVector<KNGroupInfo> tempVector; + target->groups->toVector(&tempVector); + tempVector.sort(); + + if (target->getDescriptions) { + errorPrefix = i18n("The group descriptions could not be retrieved.\nThe following error occurred:\n"); + progressValue = 100; + doneLines = 0; + predictedLines = target->groups->count(); + + sendSignal(TSdownloadDesc); + sendSignal(TSprogressUpdate); + + int rep; + if (!sendCommand("LIST NEWSGROUPS",rep)) + return; + + if (rep == 215) { // 215 informations follows + QString description; + KNGroupInfo info; + int pos; + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + break; // message complete + } + s = line; + while (*s != '\0' && *s != '\t' && *s != ' ') s++; + if (*s == '\0') { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-description - ignoring"); +#endif + } else { + s[0] = 0; // terminate groupname + s++; + while (*s == ' ' || *s == '\t') s++; // go on to the description + + name = QString::fromUtf8(line); + if (target->codecForDescriptions) // some countries use local 8 bit characters in the tag line + description = target->codecForDescriptions->toUnicode(s); + else + description = QString::fromLocal8Bit(s); + info.name = name; + + if ((pos=tempVector.bsearch(&info))!=-1) + tempVector[pos]->description = description; + } + doneLines++; + } + } + + if (!job->success() || job->canceled()) + return; // stopped... + } + + target->groups->setAutoDelete(false); + tempVector.toList(target->groups); + target->groups->setAutoDelete(true); + + sendSignal(TSwriteGrouplist); + if (!target->writeOut()) + job->setErrorString(i18n("Unable to write the group list file")); + +} + + +void KNNntpClient::doCheckNewGroups() +{ + KNGroupListData *target = static_cast<KNGroupListData *>(job->data()); + + sendSignal(TSdownloadNewGroups); + errorPrefix = i18n("New groups could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = 30; // rule of thumb ;-) + + QCString cmd; + cmd.sprintf("NEWGROUPS %.2d%.2d%.2d 000000",target->fetchSince.year()%100,target->fetchSince.month(),target->fetchSince.day()); + if (!sendCommandWCheck(cmd,231)) // 231 list of new newsgroups follows + return; + + char *s, *line; + QString name; + KNGroup::Status status; + QSortedList<KNGroupInfo> tmpList; + tmpList.setAutoDelete(true); + + while (getNextLine()) { + line = getCurrentLine(); + if (line[0]=='.') { + if (line[1]=='.') + line++; // collapse double period into one + else + if (line[1]==0) + break; // message complete + } + s = strchr(line,' '); + if(!s) { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-line - ignoring"); +#endif + } else { + s[0] = 0; // cut string + name = QString::fromUtf8(line); + + while (s[1]!=0) s++; // the last character determines the moderation status + switch (s[0]) { + case 'n' : status = KNGroup::readOnly; + break; + case 'y' : status = KNGroup::postingAllowed; + break; + case 'm' : status = KNGroup::moderated; + break; + default : status = KNGroup::unknown; + } + + tmpList.append(new KNGroupInfo(name,QString::null,true,false,status)); + } + doneLines++; + } + + if (!job->success() || job->canceled()) + return; // stopped... + + if (target->getDescriptions) { + errorPrefix = i18n("The group descriptions could not be retrieved.\nThe following error occurred:\n"); + progressValue = 100; + doneLines = 0; + predictedLines = tmpList.count()*3; + + sendSignal(TSdownloadDesc); + sendSignal(TSprogressUpdate); + + cmd = "LIST NEWSGROUPS "; + QStrList desList; + char *s; + int rep; + + for (KNGroupInfo *group=tmpList.first(); group; group=tmpList.next()) { + if (!sendCommand(cmd+group->name.utf8(),rep)) + return; + if (rep != 215) // 215 informations follows + break; + desList.clear(); + if (!getMsg(desList)) + return; + + if (desList.count()>0) { // group has a description + s = desList.first(); + while (*s !=- '\0' && *s != '\t' && *s != ' ') s++; + if (*s == '\0') { +#ifndef NDEBUG + qDebug("knode: retrieved broken group-description - ignoring"); +#endif + } else { + while (*s == ' ' || *s == '\t') s++; // go on to the description + if (target->codecForDescriptions) // some countries use local 8 bit characters in the tag line + group->description = target->codecForDescriptions->toUnicode(s); + else + group->description = QString::fromLocal8Bit(s); + } + } + } + } + + sendSignal(TSloadGrouplist); + + if (!target->readIn()) { + job->setErrorString(i18n("Unable to read the group list file")); + return; + } + target->merge(&tmpList); + sendSignal(TSwriteGrouplist); + if (!target->writeOut()) { + job->setErrorString(i18n("Unable to write the group list file")); + return; + } +} + + +void KNNntpClient::doFetchNewHeaders() +{ + KNGroup* target=static_cast<KNGroup*>(job->data()); + char* s; + int first=0, last=0, oldlast=0, toFetch=0, rep=0; + QCString cmd; + + target->setLastFetchCount(0); + + sendSignal(TSdownloadNew); + errorPrefix=i18n("No new articles could be retrieved for\n%1/%2.\nThe following error occurred:\n") + .arg(account.server()).arg(target->groupname()); + + cmd="GROUP "; + cmd+=target->groupname().utf8(); + if (!sendCommandWCheck(cmd,211)) { // 211 n f l s group selected + return; + } + + currentGroup = target->groupname(); + + progressValue = 90; + + s = strchr(getCurrentLine(),' '); + if (s) { + s++; + s = strchr(s,' '); + } + if (s) { + s++; + first=atoi(s); + target->setFirstNr(first); + s = strchr(s,' '); + } + if (s) { + last=atoi(s); + } else { + QString tmp=i18n("No new articles could be retrieved.\nThe server sent a malformatted response:\n"); + tmp+=getCurrentLine(); + job->setErrorString(tmp); + closeConnection(); + return; + } + + if(target->lastNr()==0) { //first fetch + if(first>0) + oldlast=first-1; + else + oldlast=first; + } else + oldlast=target->lastNr(); + + toFetch=last-oldlast; + //qDebug("knode: last %d oldlast %d toFetch %d\n",last,oldlast,toFetch); + + if(toFetch<=0) { + //qDebug("knode: No new Articles in group\n"); + target->setLastNr(last); // don't get stuck when the article numbers wrap + return; + } + + if(toFetch>target->maxFetch()) { + toFetch=target->maxFetch(); + //qDebug("knode: Fetching only %d articles\n",toFetch); + } + + progressValue = 100; + predictedLines = toFetch; + + // get list of additional headers provided by the XOVER command + // see RFC 2980 section 2.1.7 + QStrList headerformat; + cmd = "LIST OVERVIEW.FMT"; + if ( sendCommand( cmd, rep ) && rep == 215 ) { + QStrList tmp; + if (getMsg(tmp)) { + for(QCString s = tmp.first(); s; s = tmp.next()) { + s = s.stripWhiteSpace(); + // remove the mandatory xover header + if (s == "Subject:" || s == "From:" || s == "Date:" || s == "Message-ID:" + || s == "References:" || s == "Bytes:" || s == "Lines:") + continue; + else + headerformat.append(s); + } + } + } + + //qDebug("knode: KNNntpClient::doFetchNewHeaders() : xover %d-%d", last-toFetch+1, last); + cmd.sprintf("xover %d-%d",last-toFetch+1,last); + if (!sendCommand(cmd,rep)) + return; + + // no articles in selected range... + if (rep==420) { // 420 No article(s) selected + target->setLastNr(last); + return; + } else if (rep!=224) { // 224 success + handleErrors(); + return; + } + + QStrList headers; + if (!getMsg(headers)) { + return; + } + + progressValue = 1000; + sendSignal(TSprogressUpdate); + + sendSignal(TSsortNew); + + mutex.lock(); + target->insortNewHeaders(&headers, &headerformat, this); + target->setLastNr(last); + mutex.unlock(); +} + + +void KNNntpClient::doFetchArticle() +{ + KNRemoteArticle *target = static_cast<KNRemoteArticle*>(job->data()); + QCString cmd; + + sendSignal(TSdownloadArticle); + errorPrefix = i18n("Article could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = target->lines()->numberOfLines()+10; + + if (target->collection()) { + QString groupName = static_cast<KNGroup*>(target->collection())->groupname(); + if (currentGroup != groupName) { + cmd="GROUP "; + cmd+=groupName.utf8(); + if (!sendCommandWCheck(cmd,211)) // 211 n f l s group selected + return; + currentGroup = groupName; + } + } + + if (target->articleNumber() != -1) { + cmd.setNum(target->articleNumber()); + cmd.prepend("ARTICLE "); + } else { + cmd = "ARTICLE " + target->messageID()->as7BitString(false); + } + + if (!sendCommandWCheck(cmd,220)) { // 220 n <a> article retrieved - head and body follow + int code = atoi(getCurrentLine()); + if ( code == 430 || code == 423 ) { // 430 no such article found || 423 no such article number in this group + QString msgId = target->messageID()->as7BitString( false ); + // strip of '<' and '>' + msgId = msgId.mid( 1, msgId.length() - 2 ); + job->setErrorString( errorPrefix + getCurrentLine() + + i18n("<br><br>The article you requested is not available on your news server." + "<br>You could try to get it from <a href=\"http://groups.google.com/groups?selm=%1\">groups.google.com</a>.") + .arg( msgId ) ); + } + return; + } + + QStrList msg; + if (!getMsg(msg)) + return; + + progressValue = 1000; + sendSignal(TSprogressUpdate); + + target->setContent(&msg); + target->parse(); +} + + +void KNNntpClient::doPostArticle() +{ + KNLocalArticle *art=static_cast<KNLocalArticle*>(job->data()); + + sendSignal(TSsendArticle); + + if (art->messageID(false)!=0) { + int rep; + if (!sendCommand(QCString("STAT ")+art->messageID(false)->as7BitString(false),rep)) + return; + + if (rep==223) { // 223 n <a> article retrieved - request text separately + #ifndef NDEBUG + qDebug("knode: STAT successful, we have probably already sent this article."); + #endif + return; // the article is already on the server, lets put it silently into the send folder + } + } + + if(!sendCommandWCheck("POST", 340)) // 340 send article to be posted. End with <CR-LF>.<CR-LF> + return; + + if (art->messageID(false)==0) { // article has no message ID => search for a ID in the response + QCString s = getCurrentLine(); + int start = s.findRev(QRegExp("<[^\\s]*@[^\\s]*>")); + if (start != -1) { // post response includes a recommended id + int end = s.find('>',start); + art->messageID()->from7BitString(s.mid(start,end-start+1)); + art->assemble(); + #ifndef NDEBUG + qDebug("knode: using the message-id recommended by the server: %s",s.mid(start,end-start+1).data()); + #endif + } + } + + if (!sendMsg(art->encodedContent(true))) + return; + + if (!checkNextResponse(240)) // 240 article posted ok + return; +} + + +void KNNntpClient::doFetchSource() +{ + KNRemoteArticle *target = static_cast<KNRemoteArticle*>(job->data()); + + sendSignal(TSdownloadArticle); + errorPrefix = i18n("Article could not be retrieved.\nThe following error occurred:\n"); + + progressValue = 100; + predictedLines = target->lines()->numberOfLines()+10; + + QCString cmd = "ARTICLE " + target->messageID()->as7BitString(false); + if (!sendCommandWCheck(cmd,220)) // 220 n <a> article retrieved - head and body follow + return; + + QStrList msg; + if (!getMsg(msg)) + return; + + progressValue = 1000; + sendSignal(TSprogressUpdate); + + target->setContent(&msg); +} + + +bool KNNntpClient::openConnection() +{ + currentGroup = QString::null; + + QString oldPrefix = errorPrefix; + errorPrefix=i18n("Unable to connect.\nThe following error occurred:\n"); + + if (!KNProtocolClient::openConnection()) + return false; + + progressValue = 30; + + int rep; + if (!getNextResponse(rep)) + return false; + + if ( ( rep < 200 ) || ( rep > 299 ) ) { // RFC977: 2xx - Command ok + handleErrors(); + return false; + } + + progressValue = 50; + + if (!sendCommand("MODE READER",rep)) + return false; + + if (rep==500) { +#ifndef NDEBUG + qDebug("knode: \"MODE READER\" command not recognized."); +#endif + } else + if ( ( rep < 200 ) || ( rep > 299 ) ) { // RFC977: 2xx - Command ok + handleErrors(); + return false; + } + + progressValue = 60; + + // logon now, some newsserver send a incomplete group list otherwise + if (account.needsLogon() && !account.user().isEmpty()) { + //qDebug("knode: user: %s",account.user().latin1()); + + QCString command = "AUTHINFO USER "; + command += account.user().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + + if (rep==381) { // 381 PASS required + //qDebug("knode: Password required"); + + if (!account.pass().length()) { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.")); + job->setAuthError(true); + return false; + } + + //qDebug("knode: pass: %s",account.pass().latin1()); + + command = "AUTHINFO PASS "; + command += account.pass().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + + if (rep==281) { // 281 authorization success + #ifndef NDEBUG + qDebug("knode: Authorization successful"); + #endif + } else { + #ifndef NDEBUG + qDebug("knode: Authorization failed"); + #endif + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.\n\n%1").arg(getCurrentLine())); + job->setAuthError(true); + closeConnection(); + return false; + } + } else { + if (rep==281) { // 281 authorization success + #ifndef NDEBUG + qDebug("knode: Authorization successful"); + #endif + } else { + if ((rep==482)||(rep==500)) { //482 Authentication rejected + #ifndef NDEBUG + qDebug("knode: Authorization failed"); // we don't care, the server can refuse the info + #endif + } else { + handleErrors(); + return false; + } + } + } + } + + progressValue = 70; + + errorPrefix = oldPrefix; + return true; +} + + +// authentication on demand +bool KNNntpClient::sendCommand(const QCString &cmd, int &rep) +{ + if (!KNProtocolClient::sendCommand(cmd,rep)) + return false; + + if (rep==480) { // 480 requesting authorization + //qDebug("knode: Authorization requested"); + + if (!account.user().length()) { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.")); + job->setAuthError(true); + closeConnection(); + return false; + } + + //qDebug("knode: user: %s",account.user().data()); + + QCString command = "AUTHINFO USER "; + command += account.user().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + + if (rep==381) { // 381 PASS required + //qDebug("knode: Password required"); + + if (!account.pass().length()) { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.\n\n%1").arg(getCurrentLine())); + job->setAuthError(true); + closeConnection(); + return false; + } + + //qDebug("knode: pass: %s",account.pass().data()); + + command = "AUTHINFO PASS "; + command += account.pass().local8Bit(); + if (!KNProtocolClient::sendCommand(command,rep)) + return false; + } + + if (rep==281) { // 281 authorization success + #ifndef NDEBUG + qDebug("knode: Authorization successful"); + #endif + if (!KNProtocolClient::sendCommand(cmd,rep)) // retry the original command + return false; + } else { + job->setErrorString(i18n("Authentication failed.\nCheck your username and password.\n\n%1").arg(getCurrentLine())); + job->setAuthError(true); + closeConnection(); + return false; + } + } + return true; +} + + +void KNNntpClient::handleErrors() +{ + if (errorPrefix.isEmpty()) + job->setErrorString(i18n("An error occurred:\n%1").arg(getCurrentLine())); + else + job->setErrorString(errorPrefix + getCurrentLine()); + + int code = atoi(getCurrentLine()); + + // close the connection only when necessary: + // 430 no such article found + // 411 no such news group + // 423 no such article number in this group + if ((code != 430)&&(code != 411)&&(code != 423)) + closeConnection(); +} + + +//-------------------------------- + |