You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/knode/knnntpclient.cpp

779 lines
21 KiB

/*
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 <algorithm>
#include <vector>
#include <tdelocale.h>
#include <tqtextcodec.h>
#include <tqmutex.h>
#include "kngroupmanager.h"
#include "knnntpclient.h"
#include "utilities.h"
KNNntpClient::KNNntpClient(int NfdPipeIn, int NfdPipeOut, TQMutex& 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
tqDebug("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;
TQString 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
tqDebug("knode: retrieved broken group-line - ignoring");
#endif
} else {
s[0] = 0; // cut string
name = TQString::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->push_back(new KNGroupInfo(name,TQString(),false,subscribed,status));
}
doneLines++;
}
if (!job->success() || job->canceled())
return; // stopped...
std::vector<KNGroupInfo*> tempVector;
for (KNGroupInfo *g : *target->groups)
{
tempVector.push_back(g);
}
std::sort(tempVector.begin(), tempVector.end(), KNGroupInfo::PtrCompFn);
if (target->getDescriptions) {
errorPrefix = i18n("The group descriptions could not be retrieved.\nThe following error occurred:\n");
progressValue = 100;
doneLines = 0;
predictedLines = target->groups->size();
sendSignal(TSdownloadDesc);
sendSignal(TSprogressUpdate);
int rep;
if (!sendCommand("LIST NEWSGROUPS",rep))
return;
if (rep == 215) { // 215 informations follows
TQString 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
tqDebug("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 = TQString::fromUtf8(line);
if (target->codecForDescriptions) // some countries use local 8 bit characters in the tag line
description = target->codecForDescriptions->toUnicode(s);
else
description = TQString::fromLocal8Bit(s);
info.name = name;
if (std::binary_search(tempVector.begin(), tempVector.end(), &info, KNGroupInfo::PtrCompFn))
{
KNGroupInfo *posGroup = *std::lower_bound(tempVector.begin(), tempVector.end(), &info, KNGroupInfo::PtrCompFn);
posGroup->description = description;
}
}
doneLines++;
}
}
if (!job->success() || job->canceled())
return; // stopped...
}
target->groups->clear();
for (KNGroupInfo *g : tempVector)
{
target->groups->push_back(g);
}
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 ;-)
TQCString 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;
TQString name;
KNGroup::Status status;
std::list<KNGroupInfo*> tmpList;
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
tqDebug("knode: retrieved broken group-line - ignoring");
#endif
} else {
s[0] = 0; // cut string
name = TQString::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.push_back(new KNGroupInfo(name,TQString(),true,false,status));
}
doneLines++;
}
bool statusOk = true;
if (!job->success() || job->canceled())
{
statusOk = false; // stopped...
}
if (statusOk && target->getDescriptions) {
errorPrefix = i18n("The group descriptions could not be retrieved.\nThe following error occurred:\n");
progressValue = 100;
doneLines = 0;
predictedLines = tmpList.size()*3;
sendSignal(TSdownloadDesc);
sendSignal(TSprogressUpdate);
cmd = "LIST NEWSGROUPS ";
TQStrList desList;
char *s;
int rep;
for (KNGroupInfo *group : tmpList)
{
if (!sendCommand(cmd+group->name.utf8(),rep))
{
statusOk = false;
break;
}
if (rep != 215) // 215 informations follows
break;
desList.clear();
if (!getMsg(desList))
{
statusOk = false;
break;
}
if (desList.count()>0) { // group has a description
s = desList.first();
while (*s !=- '\0' && *s != '\t' && *s != ' ') s++;
if (*s == '\0') {
#ifndef NDEBUG
tqDebug("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 = TQString::fromLocal8Bit(s);
}
}
}
}
if (statusOk)
{
sendSignal(TSloadGrouplist);
if (!target->readIn()) {
job->setErrorString(i18n("Unable to read the group list file"));
}
else
{
target->merge(&tmpList);
sendSignal(TSwriteGrouplist);
if (!target->writeOut()) {
job->setErrorString(i18n("Unable to write the group list file"));
}
}
}
for (KNGroupInfo *group : tmpList)
{
delete group;
}
}
void KNNntpClient::doFetchNewHeaders()
{
KNGroup* target=static_cast<KNGroup*>(job->data());
char* s;
int first=0, last=0, oldlast=0, toFetch=0, rep=0;
TQCString 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 {
TQString 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;
//tqDebug("knode: last %d oldlast %d toFetch %d\n",last,oldlast,toFetch);
if(toFetch<=0) {
//tqDebug("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();
//tqDebug("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
TQStrList headerformat;
cmd = "LIST OVERVIEW.FMT";
if ( sendCommand( cmd, rep ) && rep == 215 ) {
TQStrList tmp;
if (getMsg(tmp)) {
for(TQCString s = tmp.first(); !s.isNull(); 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);
}
}
}
//tqDebug("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;
}
TQStrList 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());
TQCString 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()) {
TQString 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
TQString 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;
}
TQStrList 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(TQCString("STAT ")+art->messageID(false)->as7BitString(false),rep))
return;
if (rep==223) { // 223 n <a> article retrieved - request text separately
#ifndef NDEBUG
tqDebug("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
TQCString s = getCurrentLine();
int start = s.findRev(TQRegExp("<[^\\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
tqDebug("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;
TQCString cmd = "ARTICLE " + target->messageID()->as7BitString(false);
if (!sendCommandWCheck(cmd,220)) // 220 n <a> article retrieved - head and body follow
return;
TQStrList msg;
if (!getMsg(msg))
return;
progressValue = 1000;
sendSignal(TSprogressUpdate);
target->setContent(&msg);
}
bool KNNntpClient::openConnection()
{
currentGroup = TQString();
TQString 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
tqDebug("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()) {
//tqDebug("knode: user: %s",account.user().latin1());
TQCString command = "AUTHINFO USER ";
command += account.user().local8Bit();
if (!KNProtocolClient::sendCommand(command,rep))
return false;
if (rep==381) { // 381 PASS required
//tqDebug("knode: Password required");
if (!account.pass().length()) {
job->setErrorString(i18n("Authentication failed.\nCheck your username and password."));
job->setAuthError(true);
return false;
}
//tqDebug("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
tqDebug("knode: Authorization successful");
#endif
} else {
#ifndef NDEBUG
tqDebug("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
tqDebug("knode: Authorization successful");
#endif
} else {
if ((rep==482)||(rep==500)) { //482 Authentication rejected
#ifndef NDEBUG
tqDebug("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 TQCString &cmd, int &rep)
{
if (!KNProtocolClient::sendCommand(cmd,rep))
return false;
if (rep==480) { // 480 requesting authorization
//tqDebug("knode: Authorization requested");
if (!account.user().length()) {
job->setErrorString(i18n("Authentication failed.\nCheck your username and password."));
job->setAuthError(true);
closeConnection();
return false;
}
//tqDebug("knode: user: %s",account.user().data());
TQCString command = "AUTHINFO USER ";
command += account.user().local8Bit();
if (!KNProtocolClient::sendCommand(command,rep))
return false;
if (rep==381) { // 381 PASS required
//tqDebug("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;
}
//tqDebug("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
tqDebug("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();
}
//--------------------------------