Files
Work/nntp/NNTP.CPP
2024-08-07 09:16:27 -04:00

712 lines
23 KiB
C++

#include <nntp/nntp.hpp>
#include <nntp/listitms.hpp>
#include <nntp/grpitem.hpp>
#include <nntp/header.hpp>
#include <socket/hostent.hpp>
#include <socket/servent.hpp>
#include <common/systime.hpp>
bool NNTPClient::open(const String &hostName,const String &user,const String &password)
{
Block<String> responseLines;
String strLastResponse;
HostEnt hostEntry;
ServEnt serverEntry;
if(isConnected())cancel();
message(String("trying ")+hostName+String("..."));
if(!mWSASystem.isInitialized()){message("WINSOCK initialization failure.");return false;}
InternetAddress internetAddress(hostName);
if(!internetAddress.isZero()){if(!hostEntry.hostByAddress(internetAddress)){message(String("no DNS entry for ")+hostName);return false;}}
else if(!hostEntry.hostByName(hostName)){message(String("no DNS entry for ")+hostName);return false;}
message(String("connect...")+String("'")+hostEntry.hostName()+String("' (")+(String)(hostEntry.addresses())[0]+String(")"));
INETSocketAddress::internetAddress((hostEntry.addresses())[0]);
if(!serverEntry.serviceByName("nntp","tcp")){message("cannot determine port number for nntp daemon.");return FALSE;}
if(!mNNTPControl.create()){message("unable to create socket.");return FALSE;}
INETSocketAddress::family(PF_INET);
INETSocketAddress::port(serverEntry.port());
if(!mNNTPControl.connect((INETSocketAddress&)*this)){message("unable to connect to nntp server");return FALSE;}
if(!isConnected())return false;
receiveStrings(responseLines);
message(responseLines);
if(responseLines.size())
{
strLastResponse=responseLines[responseLines.size()-1];
if(isInResponse(strLastResponse.betweenString(0,' '),mNakConnectStrings))mNNTPControl.destroy();
else if(!authenticate(user,password))mNNTPControl.destroy();
}
return mNNTPControl.isConnected();
}
bool NNTPClient::open(const String &hostName)
{
Block<String> responseLines;
String strLastResponse;
HostEnt hostEntry;
ServEnt serverEntry;
if(isConnected())cancel();
message(String("trying ")+hostName+String("..."));
if(!mWSASystem.isInitialized()){message("WINSOCK initialization failure.");return false;}
InternetAddress internetAddress(hostName);
if(!internetAddress.isZero()){if(!hostEntry.hostByAddress(internetAddress)){message(String("no DNS entry for ")+hostName);return false;}}
else if(!hostEntry.hostByName(hostName)){message(String("no DNS entry for ")+hostName);return false;}
message(String("connect...")+String("'")+hostEntry.hostName()+String("' (")+(String)(hostEntry.addresses())[0]+String(")"));
INETSocketAddress::internetAddress((hostEntry.addresses())[0]);
if(!serverEntry.serviceByName("nntp","tcp")){message("cannot determine port number for nntp daemon.");return false;}
if(!mNNTPControl.create()){message("unable to create socket.");return false;}
INETSocketAddress::family(PF_INET);
INETSocketAddress::port(serverEntry.port());
if(!mNNTPControl.connect((INETSocketAddress&)*this)){message("unable to connect to nntp server");return false;}
if(!isConnected())return false;
receiveStrings(responseLines);
return mNNTPControl.isConnected();
}
bool NNTPClient::quit(void)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[Quit];
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
message(responseLine);
if(!isInResponse(responseLine.betweenString(0,' '),mAckQuitResponseStrings))return false;
return true;
}
bool NNTPClient::slave(void)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[Slave];
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
message(responseLine);
if(!isInResponse(responseLine.betweenString(0,' '),mAckSlaveResponseStrings))return false;
return true;
}
bool NNTPClient::group(GroupItem &groupItem)
{
String controlData;
String responseLine;
if(!isConnected())return false;
if(groupItem.groupName().isNull())return false;
controlData.reserve(String::MaxString);
::sprintf(controlData,"%s %s",(LPSTR)mNNTPCmds[Group],(LPSTR)groupItem.groupName());
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(!isInResponse(responseLine.betweenString(0,' '),mAckGroupResponseStrings))return false;
groupItem<<responseLine;
return true;
}
bool NNTPClient::list(void)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[List];
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
message(responseLine);
if(!isInResponse(responseLine.betweenString(0,' '),mAckListResponseStrings))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine)break;
message(responseLine);
}
return true;
}
bool NNTPClient::authenticate(const String &user,const String &password)
{
String controlData;
String responseLine;
if(!isConnected())return false;
if(user.isNull()||password.isNull())return false;
message(String("AUTHENTICATING user '")+user+String("'"));
controlData=mNNTPCmds[AuthInfoUser];
controlData+=String(" ")+user;
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
controlData=mNNTPCmds[AuthInfoPass];
controlData+=String(" ")+password;
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
message(responseLine);
if(!isInResponse(responseLine.betweenString(0,' '),mAckAuthResponseStrings))return false;
return true;
}
bool NNTPClient::list(ListItems &listItems)
{
String controlData;
String responseLine;
listItems.remove();
if(!isConnected())return false;
controlData=mNNTPCmds[List];
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
message(responseLine);
if(!isInResponse(responseLine.betweenString(0,' '),mAckListResponseStrings))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine)break;
listItems+=responseLine;
}
return (listItems.size()?true:false);
}
bool NNTPClient::article(DWORD articleNumber,Block<String> &articleText,String &messageID)
{
String controlData;
bool returnCode;
::sprintf(controlData,"%s %ld",(LPSTR)mNNTPCmds[Article],articleNumber);
returnCode=retrieveBlock(controlData,articleText,messageID,mAckArticleResponseStrings,mNackArticleResponseStrings);
messageID=messageID.betweenString('<','>');
return returnCode;
}
bool NNTPClient::article(const MsgID &msgID,Block<String> &articleText,String &messageID)
{
String controlData;
controlData=mNNTPCmds[Article]+mSpace+msgID;
return retrieveBlock(controlData,articleText,messageID,mAckArticleResponseStrings,mNackArticleResponseStrings);
}
bool NNTPClient::body(DWORD articleNumber,Block<String> &bodyText,String &messageID)
{
String controlData;
::sprintf(controlData,"%s %d",(LPSTR)mNNTPCmds[Body],articleNumber);
return retrieveBlock(controlData,bodyText,messageID,mAckArticleResponseStrings,mNackArticleResponseStrings);
}
bool NNTPClient::body(const MsgID &msgID,Block<String> &bodyText,String &messageID)
{
String controlData;
controlData=mNNTPCmds[Body];
controlData+=mSpace+msgID;
return retrieveBlock(controlData,bodyText,messageID,mAckArticleResponseStrings,mNackArticleResponseStrings);
}
bool NNTPClient::head(DWORD articleNumber,Block<String> &headText,String &messageID)
{
String controlData;
::sprintf(controlData,"%s %ld",(LPSTR)mNNTPCmds[Head],articleNumber);
return retrieveBlock(controlData,headText,mAckArticleResponseStrings,mNackArticleResponseStrings);
}
bool NNTPClient::head(const MsgID &msgID,Block<String> &headText,String &messageID)
{
String controlData;
controlData=mNNTPCmds[Head]+mSpace+msgID;
return retrieveBlock(controlData,headText,mAckArticleResponseStrings,mNackArticleResponseStrings);
}
bool NNTPClient::stat(DWORD articleNumber,MsgID &msgID)
{
String controlData;
String responseLine;
if(!isConnected())return false;
::sprintf(controlData,"%s %ld",(LPSTR)mNNTPCmds[Stat],articleNumber);
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(isInResponse(responseLine.betweenString(0,' '),mNackArticleResponseStrings))return false;
responseLine=responseLine.betweenString(' ',0);
msgID=responseLine.betweenString(' ',0);
return (!msgID.isNull());
}
bool NNTPClient::stat(const MsgID &msgID,MsgID &msgFound)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[Stat]+mSpace+msgID;
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(isInResponse(responseLine.betweenString(0,' '),mNackArticleResponseStrings))return false;
responseLine=responseLine.betweenString(' ',0);
msgFound=responseLine.betweenString(' ',0);
return (!msgFound.isNull());
}
bool NNTPClient::listGroup(const String &newsGroup,Block<String> &messageIDStrings)
{
DWORD responseLines(0L);
String controlData;
String responseLine;
messageIDStrings.remove();
if(!isConnected())return false;
controlData=mNNTPCmds[ListGroup];
controlData+=mSpace;
controlData+=newsGroup;
if(!putControlData(controlData,false))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(isInResponse(responseLine.betweenString(0,' '),mNackGroupResponseStrings))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
messageIDStrings.insert(&responseLine);
}
return (messageIDStrings.size()?true:false);
}
bool NNTPClient::newNews(Block<String> &newsGroups,Block<String> &distributionGroups,Block<String> &messageIDStrings,const SystemTime &systemTime,WORD isGMT)
{
DWORD responseLines(0L);
String controlData;
String timeDateString;
String responseLine;
messageIDStrings.remove();
if(!isConnected())return false;
controlData=mNNTPCmds[NewNews];
controlData+=mSpace;
for(int itemIndex=0;itemIndex<newsGroups.size();itemIndex++)
{
controlData+=newsGroups[itemIndex];
if(itemIndex+1<newsGroups.size())controlData+=mComma;
}
controlData+=mSpace;
makeTimeDateString(timeDateString,systemTime);
controlData+=timeDateString;
if(isGMT)controlData+=" [GMT]";
if(distributionGroups.size())
{
controlData+=mSpace+mLeftAngle;
for(itemIndex=0;itemIndex<distributionGroups.size();itemIndex++)
{
controlData+=distributionGroups[itemIndex];
if(itemIndex+1<distributionGroups.size())controlData+=mComma;
}
controlData+=mRightAngle;
}
if(!putControlData(controlData,false))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
if(!responseLines++)
{
String responseString(responseLine.betweenString(0,' '));
if(isInResponse(responseString,mAckNewNewsResponseStrings))continue;
}
messageIDStrings.insert(&responseLine);
}
return (messageIDStrings.size()?true:false);
}
bool NNTPClient::simulateNewNews(const String &newsGroup,Block<String> &messageIDStrings,short pastDays)
{
String messageID;
SystemTime systemTime;
GroupItem groupItem;
int badArticles(0);
int goodArticles(0);
int maxBadArticles(100);
int maxGoodArticlesToOffsetBad(10);
messageIDStrings.remove();
if(!isConnected())return false;
daysPast(systemTime,pastDays);
groupItem.groupName(newsGroup);
group(groupItem);
for(int articleIndex=groupItem.lastArticle();articleIndex>=groupItem.firstArticle();articleIndex--)
{
Block<String> headerText;
head(articleIndex,headerText,messageID);
if(!headerText.size())
{
String msgString;
::sprintf(msgString,"article #:%d is not available, %d of %d",articleIndex,++badArticles,maxBadArticles);
message(msgString);
if(badArticles>=maxBadArticles)break;
continue;
}
else goodArticles++;
if(goodArticles>maxGoodArticlesToOffsetBad){badArticles=0;goodArticles=0;}
Header articleHeader(headerText);
message(String("examining ")+articleHeader.messageID()+String(", ")+articleHeader.date());
if(articleHeader.systemTime()<systemTime)continue;
messageIDStrings.insert(&articleHeader.messageID());
}
return (messageIDStrings.size()?true:false);
}
bool NNTPClient::newNews(const String &newsGroup,Block<String> &messageIDStrings,short pastDays)
{
DWORD responseLines(0L);
String controlData;
String responseLine;
String timeDateString;
SystemTime systemTime;
messageIDStrings.remove();
if(!isConnected())return false;
daysPast(systemTime,pastDays);
controlData=mNNTPCmds[NewNews];
controlData+=mSpace;
controlData+=newsGroup;
makeTimeDateString(timeDateString,systemTime);
controlData+=mSpace;
controlData+=timeDateString;
if(!putControlData(controlData))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
if(!responseLines++)
{
String responseString(responseLine.betweenString(0,' '));
if(isInResponse(responseString,mAckNewNewsResponseStrings))continue;
}
messageIDStrings.insert(&responseLine);
}
return (messageIDStrings.size()?true:false);
}
bool NNTPClient::newGroups(ListItems &listItems,short pastDays)
{
SystemTime systemTime;
Block<String> distributionGroups;
listItems.remove();
daysPast(systemTime,pastDays);
return newGroups(listItems,distributionGroups,systemTime,false);
}
bool NNTPClient::newGroups(ListItems &listItems,Block<String> &distributionGroups,const SystemTime &systemTime,bool isGMT)
{
String controlData;
String responseLine;
String timeDateString;
DWORD responseLines(0);
listItems.remove();
if(!isConnected())return false;
controlData=mNNTPCmds[NewGroups];
controlData+=mSpace;
makeTimeDateString(timeDateString,systemTime);
controlData+=timeDateString;
if(isGMT)controlData+=" [GMT]";
if(distributionGroups.size())
{
controlData+=mSpace+mLeftAngle;
for(int itemIndex=0;itemIndex<distributionGroups.size();itemIndex++)
{
controlData+=distributionGroups[itemIndex];
if(itemIndex+1<distributionGroups.size())controlData+=mComma;
}
controlData+=mRightAngle;
}
if(!putControlData(controlData,false))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
if(!responseLines++)
{
String responseString(responseLine.betweenString(0,' '));
if(isInResponse(responseString,mAckNewGroupsResponseStrings))continue;
}
listItems+=responseLine;
}
return (listItems.size()?true:false);
}
bool NNTPClient::next(GroupIterator &groupIterator)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[Next];
if(!putControlData(controlData))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(!isInResponse(responseLine.betweenString(0,' '),mAckNextResponseStrings))return false;
groupIterator<<responseLine;
return true;
}
bool NNTPClient::last(GroupIterator &groupIterator)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[Last];
if(!putControlData(controlData))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(!isInResponse(responseLine.betweenString(0,' '),mAckNextResponseStrings))return false;
groupIterator<<responseLine;
return true;
}
bool NNTPClient::iHave(const MsgID &msgID)
{
String controlData;
String responseLine;
if(!isConnected())return false;
controlData=mNNTPCmds[IHave]+mSpace+msgID;
if(!putControlData(controlData))return false;
if(!mNNTPControl.receive(responseLine))return false;
if(!isInResponse(responseLine.betweenString(0,' '),mAckIHaveResponseStrings))return false;
return true;
}
bool NNTPClient::xover(DWORD first,DWORD last,Block<String> &overview)
{
DWORD responseLines(0);
String controlData;
String responseLine;
overview.remove();
if(!isConnected())return false;
controlData=mNNTPCmds[XOver]+String(" ")+String().fromInt(first)+String("-")+String().fromInt(last);
if(!putControlData(controlData))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
if(!responseLines++)
{
String responseString(responseLine.betweenString(0,' '));
if(isInResponse(responseString,mAckXOverResponseStrings))continue;
}
overview.insert(&responseLine);
}
return true;
}
bool NNTPClient::help(Block<String> &cmdLines)
{
DWORD responseLines(0);
String controlData;
String responseLine;
cmdLines.remove();
if(!isConnected())return false;
controlData=mNNTPCmds[Help];
if(!putControlData(controlData))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
if(!responseLines++)
{
String responseString(responseLine.betweenString(0,' '));
if(isInResponse(responseString,mAckHelpResponseStrings))continue;
}
cmdLines.insert(&responseLine);
}
return true;
}
bool NNTPClient::post(Block<String> &articleLines)
{
String responseLine;
String controlData;
String postString;
int lineCount;
if(!isConnected())return false;
controlData=mNNTPCmds[Post];
if(!putControlData(controlData))return false;
if(!mNNTPControl.receive(responseLine))return false;
responseLine=responseLine.betweenString(0,' ');
if(!isInResponse(responseLine,mAckPostResponseStrings))return false;
lineCount=articleLines.size();
for(int lineIndex=0;lineIndex<lineCount;lineIndex++)
{
postString=articleLines[lineIndex];
if(postString==mPeriod)postString+=mPeriod;
if(!putControlData(articleLines[lineIndex]))return false;
}
if(!putControlData(mPeriod))return false;
if(!mNNTPControl.receive(responseLine))return false;
responseLine=responseLine.betweenString(0,' ');
if(!isInResponse(responseLine,mAckPostResponseStrings))return false;
return true;
}
bool NNTPClient::retrieveBlock(const String &controlData,Block<String> &stringBlock,Block<String> &ackResponse,Block<String> &nackResponse)
{
String extraInfo;
return retrieveBlock(controlData,stringBlock,extraInfo,ackResponse,nackResponse);
}
bool NNTPClient::retrieveBlock(const String &controlData,Block<String> &stringBlock,String &extraInfo,Block<String> &ackResponse,Block<String> &nackResponse)
{
String responseLine;
DWORD responseLines(0);
stringBlock.remove();
if(!isConnected())return false;
if(!putControlData(controlData,false))return false;
while(mNNTPControl.receive(responseLine))
{
if(mPeriod==responseLine&&1==responseLine.length())break;
if(!responseLines++)
{
String responseString(responseLine.betweenString(0,' '));
extraInfo=responseLine.betweenString(' ','\0');
if(nackResponse.size()&&isInResponse(responseString,nackResponse))break;
if(ackResponse.size()&&isInResponse(responseString,ackResponse))continue;
}
stringBlock.insert(&responseLine);
}
return (stringBlock.size()?true:false);
}
bool NNTPClient::putControlData(const String &stringData,bool waitForResponse)
{
if(!mNNTPControl.isConnected())return false;
if(!mNNTPControl.send(stringData))
{
mNNTPControl.destroy();
String errorString(String("error sending '")+stringData+String("' to NNTP server."));
message(errorString);
return false;
}
if(waitForResponse&&!getControlData())
{
mNNTPControl.destroy();
String errorString(String("error reading result of '")+stringData+String("' command from NNTP server."));
message(errorString);
return false;
}
return true;
}
bool NNTPClient::getControlData(void)
{
Block<String> responseStrings;
mNNTPControl.receive(responseStrings);
return responseStrings.size()?true:false;
}
void NNTPClient::receiveStrings(WORD displayStrings)
{
Block<String> receiveStrings;
if(!mNNTPControl.isConnected())return;
if(!mNNTPControl.receive(receiveStrings))return;
if(!displayStrings||!receiveStrings.size())return;
message(receiveStrings);
}
void NNTPClient::receiveStrings(Block<String> &receiveStrings)
{
receiveStrings.remove();
if(!mNNTPControl.isConnected())return;
if(!mNNTPControl.receive(receiveStrings))return;
if(!receiveStrings.size())return;
message(receiveStrings);
}
void NNTPClient::createCmds(void)
{
mNNTPCmds.insert(&String("ARTICLE"));
mNNTPCmds.insert(&String("BODY"));
mNNTPCmds.insert(&String("GROUP"));
mNNTPCmds.insert(&String("HEAD"));
mNNTPCmds.insert(&String("HELP"));
mNNTPCmds.insert(&String("IHAVE"));
mNNTPCmds.insert(&String("LAST"));
mNNTPCmds.insert(&String("LIST"));
mNNTPCmds.insert(&String("NEWGROUPS"));
mNNTPCmds.insert(&String("NEWNEWS"));
mNNTPCmds.insert(&String("NEXT"));
mNNTPCmds.insert(&String("POST"));
mNNTPCmds.insert(&String("QUIT"));
mNNTPCmds.insert(&String("SLAVE"));
mNNTPCmds.insert(&String("STAT"));
mNNTPCmds.insert(&String("LISTGROUP"));
mNNTPCmds.insert(&String("AUTHINFO USER"));
mNNTPCmds.insert(&String("AUTHINFO PASS"));
mNNTPCmds.insert(&String("XOVER"));
}
bool NNTPClient::isInResponse(const String &responseString,Block<String> &responseStrings)
{
if(responseString.isNull())return false;
for(int itemIndex=0;itemIndex<responseStrings.size();itemIndex++)
if(responseString==responseStrings[itemIndex])return true;
return false;
}
void NNTPClient::createResponseStrings(void)
{
mAckArticleResponseStrings.insert(&String("220"));
mAckArticleResponseStrings.insert(&String("221"));
mAckArticleResponseStrings.insert(&String("222"));
mAckArticleResponseStrings.insert(&String("223"));
mNackArticleResponseStrings.insert(&String("412"));
mNackArticleResponseStrings.insert(&String("420"));
mNackArticleResponseStrings.insert(&String("423"));
mNackArticleResponseStrings.insert(&String("430"));
mNackArticleResponseStrings.insert(&String("480"));
mAckSlaveResponseStrings.insert(&String("202"));
mAckGroupResponseStrings.insert(&String("211"));
mNackGroupResponseStrings.insert(&String("411"));
mNackGroupResponseStrings.insert(&String("480"));
mAckListResponseStrings.insert(&String("215"));
mAckQuitResponseStrings.insert(&String("205"));
mAckNewNewsResponseStrings.insert(&String("230"));
mAckNextResponseStrings.insert(&String("223"));
mAckNewGroupsResponseStrings.insert(&String("231"));
mAckIHaveResponseStrings.insert(&String("335"));
mAckHelpResponseStrings.insert(&String("100"));
mAckPostResponseStrings.insert(&String("340"));
mAckPostResponseStrings.insert(&String("240"));
mAckAuthResponseStrings.insert(&String("281"));
mAckXOverResponseStrings.insert(&String("224"));
mNakConnectStrings.insert(&String("502"));
mNakConnectStrings.insert(&String("400"));
}
void NNTPClient::makeTimeDateString(String &timeDateString,const SystemTime &systemTime)
{
timeDateString.reserve(String::MaxString);
::sprintf(timeDateString,"%02d%02d%02d %02d%02d%02d",
systemTime.year()>2000?2000-systemTime.year():systemTime.year()-1900,
systemTime.month(),
systemTime.day(),
systemTime.hour(),
systemTime.minute(),
systemTime.second());
}
void NNTPClient::daysPast(SystemTime &systemTime,short pastDays)
{
systemTime.daysAdd360(-pastDays);
systemTime.hour(0);
systemTime.minute(0);
systemTime.second(0);
}
// virtual overloads
void NNTPClient::message(String messageString)
{
}
void NNTPClient::message(Block<String> &messageStrings)
{
}