21 #include <kcomponentdata.h> 
   23 #include <klocalizedstring.h> 
   25 #include <kio/ioslave_defaults.h> 
   28 #define DBG kDebug(DBG_AREA) 
   31 #define ERR kError(DBG_AREA) 
   35 extern "C" { 
int KDE_EXPORT kdemain(
int argc, 
char **argv); }
 
   37 int kdemain(
int argc, 
char **argv) {
 
   39   KComponentData componentData(
"kio_nntp");
 
   41     fprintf(stderr, 
"Usage: kio_nntp protocol domain-socket1 domain-socket2\n");
 
   48   if (strcasecmp(argv[1], 
"nntps") == 0) {
 
   54   slave->dispatchLoop();
 
   63   : TCPSlaveBase((isSSL ? 
"nntps" : 
"nntp"), pool, app, isSSL ),
 
   64     isAuthenticated( false )
 
   66   DBG << 
"=============> NNTPProtocol::NNTPProtocol";
 
   69   m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT;
 
   70   m_port = m_defaultPort;
 
   73 NNTPProtocol::~NNTPProtocol() {
 
   74   DBG << 
"<============= NNTPProtocol::~NNTPProtocol";
 
   80 void NNTPProtocol::setHost ( 
const QString & host, quint16 port, 
const QString & user,
 
   84       << host << 
":" << ( ( port == 0 ) ? m_defaultPort : port  );
 
   86   if ( isConnected() && (mHost != host || m_port != port ||
 
   87        mUser != user || mPass != pass) )
 
   91   m_port = ( ( port == 0 ) ? m_defaultPort : port );
 
   96 void NNTPProtocol::get( 
const KUrl& url )
 
   98   DBG << url.prettyUrl();
 
  108     group = path.
left( pos );
 
  109     msg_id = path.
mid( pos + 1 );
 
  113     error(ERR_DOES_NOT_EXIST,path);
 
  118   DBG << 
"group:" << group << 
"msg:" << msg_id;
 
  124   if ( mCurrentGroup != group && !group.
isEmpty() ) {
 
  125     infoMessage( i18n(
"Selecting group %1...", group ) );
 
  127     if ( res_code == 411 ){
 
  128       error( ERR_DOES_NOT_EXIST, path );
 
  129       mCurrentGroup.
clear();
 
  131     } 
else if ( res_code != 211 ) {
 
  132       unexpected_response( res_code, 
"GROUP" );
 
  133       mCurrentGroup.
clear();
 
  136     mCurrentGroup = group;
 
  140   infoMessage( i18n(
"Downloading article...") );
 
  142   if ( res_code == 423 || res_code == 430 ) {
 
  143     error( ERR_DOES_NOT_EXIST, path );
 
  145   } 
else if (res_code != 220) {
 
  146     unexpected_response(res_code,
"ARTICLE");
 
  151   char tmp[MAX_PACKET_LEN];
 
  153     if ( !waitForResponse( readTimeout() ) ) {
 
  154       error( ERR_SERVER_TIMEOUT, mHost );
 
  158     int len = readLine( tmp, MAX_PACKET_LEN );
 
  159     const char* buffer = tmp;
 
  162     if ( len == 3 && tmp[0] == 
'.' && tmp[1] == 
'\r' && tmp[2] == 
'\n')
 
  164     if ( len > 1 && tmp[0] == 
'.' && tmp[1] == 
'.' ) {
 
  178 void NNTPProtocol::put( 
const KUrl &, 
int , KIO::JobFlags  )
 
  198     error(ERR_UNSUPPORTED_ACTION,i18n(
"Invalid special command %1", cmd));
 
  206   infoMessage( i18n(
"Sending article...") );
 
  208   if (res_code == 440) { 
 
  209     error(ERR_WRITE_ACCESS_DENIED, mHost);
 
  211   } 
else if (res_code != 340) { 
 
  212     unexpected_response(res_code,
"POST");
 
  218   bool last_chunk_had_line_ending = 
true;
 
  222     result = readData( buffer );
 
  223     DBG << 
"receiving data:" << buffer;
 
  228       if ( last_chunk_had_line_ending && buffer[0] == 
'.' ) {
 
  232       last_chunk_had_line_ending = ( buffer.endsWith( 
"\r\n" ) ); 
 
  233       while ( (pos = buffer.indexOf( 
"\r\n.", pos )) > 0) {
 
  234         buffer.
insert( pos + 2, 
'.' );
 
  239       write( buffer, buffer.length() );
 
  240       DBG << 
"writing:" << buffer;
 
  242   } 
while ( result > 0 );
 
  246     ERR << 
"error while getting article data for posting";
 
  252   write( 
"\r\n.\r\n", 5 );
 
  255   res_code = evalResponse( readBuffer, readBufferLen );
 
  256   if (res_code == 441) { 
 
  257     error(ERR_COULD_NOT_WRITE, mHost);
 
  259   } 
else if (res_code != 240) {
 
  260     unexpected_response(res_code,
"POST");
 
  268 void NNTPProtocol::stat( 
const KUrl& url ) {
 
  269   DBG << url.prettyUrl();
 
  272   QRegExp regGroup = 
QRegExp(
"^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive);
 
  273   QRegExp regMsgId = 
QRegExp(
"^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive);
 
  279   if (path.
isEmpty() || path == 
"/") {
 
  281     fillUDSEntry( entry, 
QString(), 0, 
false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
 
  284   } 
else if (regGroup.indexIn(path) == 0) {
 
  286     if ((pos = path.
indexOf(
'/')) > 0) group = path.
left(pos);
 
  288     DBG << 
"group:" << group;
 
  291     fillUDSEntry( entry, group, 0, 
false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
 
  294   } 
else if (regMsgId.
indexIn(path) == 0) {
 
  296     group = path.
left(pos);
 
  297     msg_id = KUrl::fromPercentEncoding( path.
right(path.
length()-pos).toLatin1() );
 
  298     if ( group.startsWith( 
'/' ) )
 
  300     if ((pos = group.indexOf(
'/')) > 0) group = group.
left(pos);
 
  301     DBG << 
"group:" << group << 
"msg:" << msg_id;
 
  302     fillUDSEntry( entry, msg_id, 0, 
true );
 
  306     error(ERR_DOES_NOT_EXIST,path);
 
  314 void NNTPProtocol::listDir( 
const KUrl& url ) {
 
  315   DBG << url.prettyUrl();
 
  325     DBG << 
"redirecting to" << newURL.prettyUrl();
 
  330   else if ( path == 
"/" ) {
 
  331     fetchGroups( url.queryItem( 
"since" ), url.queryItem( 
"desc" ) == 
"true" );
 
  339     if ((pos = path.
indexOf(
'/')) > 0)
 
  340       group = path.
left(pos);
 
  343     QString first = url.queryItem( 
"first" );
 
  344     QString max = url.queryItem( 
"max" );
 
  350 void NNTPProtocol::fetchGroups( 
const QString &since, 
bool desc )
 
  356     infoMessage( i18n(
"Downloading group list...") );
 
  361     infoMessage( i18n(
"Looking for new groups...") );
 
  365   if ( res != expected ) {
 
  366     unexpected_response( res, 
"LIST" );
 
  381     if ( ! waitForResponse( readTimeout() ) ) {
 
  382       error( ERR_SERVER_TIMEOUT, mHost );
 
  386     readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
 
  387     line = 
QByteArray( readBuffer, readBufferLen );
 
  388     if ( line == 
".\r\n" )
 
  392     if ((pos = line.
indexOf(
' ')) > 0) {
 
  394       group = line.
left(pos);
 
  400       if (((pos = line.
indexOf(
' ')) > 0 || (pos = line.
indexOf(
'\t')) > 0) &&
 
  401           ((pos2 = line.
indexOf(
' ',pos+1)) > 0 || (pos2 = line.
indexOf(
'\t',pos+1)) > 0)) {
 
  404         msg_cnt = abs(last-first+1);
 
  406         switch ( line[pos2 + 1] ) {
 
  407           case 'n': access = 0; 
break;
 
  408           case 'm': access = S_IWUSR | S_IWGRP; 
break;
 
  409           case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH; 
break;
 
  416       fillUDSEntry( entry, group, msg_cnt, 
false, access );
 
  418         listEntry( entry, 
false );
 
  420         entryMap.
insert( group, entry );
 
  427     infoMessage( i18n(
"Downloading group descriptions...") );
 
  428     totalSize( entryMap.
size() );
 
  436       if ( it == entryMap.
end() )
 
  452       if ( ! waitForResponse( readTimeout() ) ) {
 
  453         error( ERR_SERVER_TIMEOUT, mHost );
 
  457       readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
 
  458       line = 
QByteArray( readBuffer, readBufferLen );
 
  459       if ( line == 
".\r\n" )
 
  464       pos = pos < 0 ? line.
indexOf( 
'\t' ) : qMin( pos, line.
indexOf( 
'\t' ) );
 
  465       group = line.
left( pos );
 
  469         entry = entryMap.
take( group );
 
  470         entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc );
 
  471         listEntry( entry, 
false );
 
  480     listEntry( it.value(), false );
 
  483   listEntry( entry, 
true );
 
  486 bool NNTPProtocol::fetchGroup( 
QString &group, 
unsigned long first, 
unsigned long max ) {
 
  491   infoMessage( i18n(
"Selecting group %1...", group ) );
 
  493   if ( res_code == 411 ) {
 
  494     error( ERR_DOES_NOT_EXIST, group );
 
  495     mCurrentGroup.
clear();
 
  497   } 
else if ( res_code != 211 ) {
 
  498     unexpected_response( res_code, 
"GROUP" );
 
  499     mCurrentGroup.
clear();
 
  502   mCurrentGroup = group;
 
  506   unsigned long firstSerNum, lastSerNum;
 
  508   QRegExp re ( 
"211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)");
 
  509   if ( re.indexIn( resp_line ) != -1 ) {
 
  510     firstSerNum = re.cap( 2 ).toLong();
 
  511     lastSerNum = re.cap( 3 ).toLong();
 
  513     error( ERR_INTERNAL, i18n(
"Could not extract message serial numbers from server response:\n%1",
 
  518   if (firstSerNum == 0)
 
  520   first = qMax( first, firstSerNum );
 
  521   if ( lastSerNum < first ) { 
 
  526   if ( max > 0 && lastSerNum - first > max )
 
  527     first = lastSerNum - max + 1;
 
  529   DBG << 
"Starting from serial number: " << first << 
" of " << firstSerNum << 
" - " << lastSerNum;
 
  533   infoMessage( i18n(
"Downloading new headers...") );
 
  534   totalSize( lastSerNum - first );
 
  535   bool notSupported = 
true;
 
  536   if ( fetchGroupXOVER( first, notSupported ) )
 
  538   else if ( notSupported )
 
  539     return fetchGroupRFC977( first );
 
  544 bool NNTPProtocol::fetchGroupRFC977( 
unsigned long first )
 
  550   QString resp_line = readBuffer;
 
  551   if (res_code != 223) {
 
  552     unexpected_response(res_code,
"STAT");
 
  559   if ((pos = resp_line.
indexOf(
'<')) > 0 && (pos2 = resp_line.
indexOf(
'>',pos+1))) {
 
  560     msg_id = resp_line.
mid(pos,pos2-pos+1);
 
  561     fillUDSEntry( entry, msg_id, 0, 
true );
 
  562     listEntry( entry, 
false );
 
  564     error(ERR_INTERNAL,i18n(
"Could not extract first message id from server response:\n%1",
 
  572     if (res_code == 421) {
 
  575       listEntry( entry, 
true );
 
  577     } 
else if (res_code != 223) {
 
  578       unexpected_response(res_code,
"NEXT");
 
  583     resp_line = readBuffer;
 
  584     if ((pos = resp_line.
indexOf(
'<')) > 0 && (pos2 = resp_line.
indexOf(
'>',pos+1))) {
 
  585       msg_id = resp_line.
mid(pos,pos2-pos+1);
 
  587       fillUDSEntry( entry, msg_id, 0, 
true );
 
  588       listEntry( entry, 
false );
 
  590       error(ERR_INTERNAL,i18n(
"Could not extract message id from server response:\n%1",
 
  599 bool NNTPProtocol::fetchGroupXOVER( 
unsigned long first, 
bool ¬Supported )
 
  601   notSupported = 
false;
 
  609       if ( ! waitForResponse( readTimeout() ) ) {
 
  610         error( ERR_SERVER_TIMEOUT, mHost );
 
  614       readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
 
  616       if ( line == 
".\r\n" )
 
  619       DBG << 
"OVERVIEW.FMT:" << line.
trimmed();
 
  623     headers << 
"Subject:" << 
"From:" << 
"Date:" << 
"Message-ID:" 
  624         << 
"References:" << 
"Bytes:" << 
"Lines:";
 
  633     unexpected_response( res, 
"XOVER" );
 
  644     if ( ! waitForResponse( readTimeout() ) ) {
 
  645       error( ERR_SERVER_TIMEOUT, mHost );
 
  649     readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
 
  651     if ( line == 
".\r\n" ) {
 
  653       listEntry( entry, 
true );
 
  657     fields = line.
split( 
'\t', QString::KeepEmptyParts);
 
  660     udsType = KIO::UDSEntry::UDS_EXTRA;
 
  666     for ( ; it != headers.
constEnd() && it2 != fields.
constEnd(); ++it, ++it2 ) {
 
  667       if ( (*it) == 
"Bytes:" ) {
 
  668         msgSize = (*it2).
toLong();
 
  673         if ( (*it2).trimmed().isEmpty() )
 
  674           atomStr = (*it).
left( (*it).indexOf( 
':' ) + 1 ); 
 
  678         atomStr = (*it) + 
' ' + (*it2).
trimmed();
 
  679       entry.
insert( udsType++, atomStr );
 
  680       if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END )
 
  683     fillUDSEntry( entry, name, msgSize, 
true );
 
  684     listEntry( entry, 
false );
 
  690 void NNTPProtocol::fillUDSEntry( UDSEntry& entry, 
const QString& name, 
long size,
 
  691   bool is_article, 
long access ) {
 
  696   entry.insert(KIO::UDSEntry::UDS_NAME, name);
 
  699   entry.insert(KIO::UDSEntry::UDS_SIZE, size);
 
  702   entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR);
 
  705   posting = postingAllowed? access : 0;
 
  706   long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) :
 
  707     (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting);
 
  708   entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal);
 
  723   if ( isConnected() ) {
 
  724     write( 
"QUIT\r\n", 6 );
 
  725     disconnectFromHost();
 
  726     isAuthenticated = 
false;
 
  728   mCurrentGroup.
clear();
 
  734   if ( isConnected() ) {
 
  735     DBG << 
"reusing old connection";
 
  739   DBG << 
"  nntp_open -- creating a new connection to" << mHost << 
":" << m_port;
 
  741   infoMessage( i18n(
"Connecting to server...") );
 
  742   if ( connectToHost( (isAutoSsl() ? 
"nntps" : 
"nntp"), mHost, m_port ) )
 
  744     DBG << 
"  nntp_open -- connection is open";
 
  747     int res_code = evalResponse( readBuffer, readBufferLen );
 
  753     if ( ! ( res_code == 200 || res_code == 201 ) )
 
  755       unexpected_response(res_code,
"CONNECT");
 
  759     DBG << 
"  nntp_open -- greating was read res_code :" << res_code;
 
  764     if ( !(res_code == 200 || res_code == 201) ) {
 
  765       unexpected_response( res_code, 
"MODE READER" );
 
  770     postingAllowed = (res_code == 200);
 
  773     if ( metaData(
"tls") == 
"on" ) {
 
  775         error( ERR_COULD_NOT_CONNECT, i18n(
"This server does not support TLS") );
 
  779         error( ERR_COULD_NOT_CONNECT, i18n(
"TLS negotiation failed") );
 
  798     ERR << 
"NOT CONNECTED, cannot send cmd" << cmd;
 
  802   DBG << 
"cmd:" << cmd;
 
  804   write( cmd.toLatin1(), cmd.length() );
 
  808   res_code =  evalResponse( readBuffer, readBufferLen );
 
  811   if (res_code == 480) {
 
  812     DBG << 
"auth needed, sending user info";
 
  815       KIO::AuthInfo authInfo;
 
  816       authInfo.username = mUser;
 
  817       authInfo.password = mPass;
 
  818       if ( openPasswordDialog( authInfo ) ) {
 
  819         mUser = authInfo.username;
 
  820         mPass = authInfo.password;
 
  826     res_code = authenticate();
 
  827     if (res_code != 281) {
 
  833     write( cmd.toLatin1(), cmd.length() );
 
  836     res_code = evalResponse( readBuffer, readBufferLen );
 
  842 int NNTPProtocol::authenticate()
 
  846   if( isAuthenticated ) {
 
  856   write( 
"AUTHINFO USER ", 14 );
 
  859   res_code = evalResponse( readBuffer, readBufferLen );
 
  861   if( res_code == 281 ) {
 
  865   if (res_code != 381) {
 
  871   write( 
"AUTHINFO PASS ", 14 );
 
  874   res_code = evalResponse( readBuffer, readBufferLen );
 
  876   if( res_code == 281 ) {
 
  877     isAuthenticated = 
true;
 
  883 void NNTPProtocol::unexpected_response( 
int res_code, 
const QString &command )
 
  885   ERR << 
"Unexpected response to" << command << 
"command: (" << res_code << 
")" 
  889   switch ( res_code ) {
 
  893       error( ERR_INTERNAL_SERVER,
 
  894              i18n( 
"The server %1 could not handle your request.\n" 
  895                    "Please try again now, or later if the problem persists.", mHost ) );
 
  898       error( ERR_COULD_NOT_LOGIN,
 
  899              i18n( 
"You need to authenticate to access the requested resource." ) );
 
  902       error( ERR_COULD_NOT_LOGIN,
 
  903              i18n( 
"The supplied login and/or password are incorrect." ) );
 
  906       error( ERR_ACCESS_DENIED, mHost );
 
  909       error( ERR_INTERNAL, i18n( 
"Unexpected server response to %1 command:\n%2", command, readBuffer ) );
 
  915 int NNTPProtocol::evalResponse ( 
char *data, ssize_t &len )
 
  917   if ( !waitForResponse( responseTimeout() ) ) {
 
  918     error( ERR_SERVER_TIMEOUT , mHost );
 
  922   len = readLine( data, MAX_PACKET_LEN );
 
  928   int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) );
 
  930   DBG << 
"got:" << respCode;
 
ulong toULong(bool *ok, int base) const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
iterator insert(const Key &key, const T &value)
bool nntp_open()
Attempt to initiate a NNTP connection via a TCP socket, if no existing connection could be reused...
const Key key(const T &value) const
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QByteArray fromRawData(const char *data, int size)
QByteArray & insert(int i, char ch)
QString & remove(int position, int n)
virtual void special(const QByteArray &data)
Special command: 1 = post article it takes no other args, the article data are requested by dataReq()...
bool post_article()
Post article. 
int indexIn(const QString &str, int offset, CaretMode caretMode) const
int indexOf(char ch, int from) const
QString number(int n, int base)
QString & insert(int position, QChar ch)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QByteArray right(int len) const
int sendCommand(const QString &cmd)
Send a command to the server. 
QByteArray mid(int pos, int len) const
QString right(int n) const
NNTPProtocol(const QByteArray &pool, const QByteArray &app, bool isSSL)
Default Constructor. 
qlonglong toLongLong(bool *ok, int base) const
long toLong(bool *ok, int base) const
QString cleanPath(const QString &path)
QByteArray left(int len) const
QByteArray toLatin1() const
QString mid(int position, int n) const
QString left(int n) const
QString fromLatin1(const char *str, int size)
bool contains(const Key &key) const
const_iterator constEnd() const
const_iterator constBegin() const
QByteArray & remove(int pos, int len)
void nntp_close()
Attempt to properly shut down the NNTP connection by sending "QUIT\r\n" before closing the socket...