21 #include <kcomponentdata.h>
26 #include <kio/ioslave_defaults.h>
29 #define DBG kDebug(DBG_AREA)
32 #define ERR kError(DBG_AREA)
36 extern "C" {
int KDE_EXPORT kdemain(
int argc,
char **argv); }
38 int kdemain(
int argc,
char **argv) {
40 KComponentData componentData(
"kio_nntp");
42 fprintf(stderr,
"Usage: kio_nntp protocol domain-socket1 domain-socket2\n");
49 if (strcasecmp(argv[1],
"nntps") == 0) {
55 slave->dispatchLoop();
64 : TCPSlaveBase((isSSL ?
"nntps" :
"nntp"), pool, app, isSSL ),
65 isAuthenticated( false )
67 DBG <<
"=============> NNTPProtocol::NNTPProtocol";
70 m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT;
71 m_port = m_defaultPort;
74 NNTPProtocol::~NNTPProtocol() {
75 DBG <<
"<============= NNTPProtocol::~NNTPProtocol";
81 void NNTPProtocol::setHost (
const QString & host, quint16 port,
const QString & user,
82 const QString & pass )
84 DBG << ( ! user.isEmpty() ? (user+
'@') : QString(
""))
85 << host <<
":" << ( ( port == 0 ) ? m_defaultPort : port );
87 if ( isConnected() && (mHost != host || m_port != port ||
88 mUser != user || mPass != pass) )
92 m_port = ( ( port == 0 ) ? m_defaultPort : port );
97 void NNTPProtocol::get(
const KUrl& url )
99 DBG << url.prettyUrl();
100 QString path = QDir::cleanPath(url.path());
103 if ( path.startsWith(
'/' ) )
105 int pos = path.indexOf(
'/' );
109 group = path.left( pos );
110 msg_id = path.mid( pos + 1 );
113 if ( group.isEmpty() || msg_id.isEmpty() ) {
114 error(ERR_DOES_NOT_EXIST,path);
119 DBG <<
"group:" << group <<
"msg:" << msg_id;
125 if ( mCurrentGroup != group && !group.isEmpty() ) {
126 infoMessage( i18n(
"Selecting group %1...", group ) );
128 if ( res_code == 411 ){
129 error( ERR_DOES_NOT_EXIST, path );
130 mCurrentGroup.clear();
132 }
else if ( res_code != 211 ) {
133 unexpected_response( res_code,
"GROUP" );
134 mCurrentGroup.clear();
137 mCurrentGroup = group;
141 infoMessage( i18n(
"Downloading article...") );
143 if ( res_code == 423 || res_code == 430 ) {
144 error( ERR_DOES_NOT_EXIST, path );
146 }
else if (res_code != 220) {
147 unexpected_response(res_code,
"ARTICLE");
152 char tmp[MAX_PACKET_LEN];
154 if ( !waitForResponse( readTimeout() ) ) {
155 error( ERR_SERVER_TIMEOUT, mHost );
159 int len = readLine( tmp, MAX_PACKET_LEN );
160 const char* buffer = tmp;
163 if ( len == 3 && tmp[0] ==
'.' && tmp[1] ==
'\r' && tmp[2] ==
'\n')
165 if ( len > 1 && tmp[0] ==
'.' && tmp[1] ==
'.' ) {
169 data( QByteArray::fromRawData( buffer, len ) );
179 void NNTPProtocol::put(
const KUrl &,
int , KIO::JobFlags )
190 QDataStream stream(data);
199 error(ERR_UNSUPPORTED_ACTION,i18n(
"Invalid special command %1", cmd));
207 infoMessage( i18n(
"Sending article...") );
209 if (res_code == 440) {
210 error(ERR_WRITE_ACCESS_DENIED, mHost);
212 }
else if (res_code != 340) {
213 unexpected_response(res_code,
"POST");
219 bool last_chunk_had_line_ending =
true;
223 result = readData( buffer );
224 DBG <<
"receiving data:" << buffer;
229 if ( last_chunk_had_line_ending && buffer[0] ==
'.' ) {
230 buffer.insert( 0,
'.' );
233 last_chunk_had_line_ending = ( buffer.endsWith(
"\r\n" ) );
234 while ( (pos = buffer.indexOf(
"\r\n.", pos )) > 0) {
235 buffer.insert( pos + 2,
'.' );
240 write( buffer, buffer.length() );
241 DBG <<
"writing:" << buffer;
243 }
while ( result > 0 );
247 ERR <<
"error while getting article data for posting";
253 write(
"\r\n.\r\n", 5 );
256 res_code = evalResponse( readBuffer, readBufferLen );
257 if (res_code == 441) {
258 error(ERR_COULD_NOT_WRITE, mHost);
260 }
else if (res_code != 240) {
261 unexpected_response(res_code,
"POST");
269 void NNTPProtocol::stat(
const KUrl& url ) {
270 DBG << url.prettyUrl();
272 QString path = QDir::cleanPath(url.path());
273 QRegExp regGroup = QRegExp(
"^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive);
274 QRegExp regMsgId = QRegExp(
"^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive);
280 if (path.isEmpty() || path ==
"/") {
282 fillUDSEntry( entry, QString(), 0,
false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
285 }
else if (regGroup.indexIn(path) == 0) {
286 if ( path.startsWith(
'/' ) ) path.remove(0,1);
287 if ((pos = path.indexOf(
'/')) > 0) group = path.left(pos);
289 DBG <<
"group:" << group;
292 fillUDSEntry( entry, group, 0,
false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
295 }
else if (regMsgId.indexIn(path) == 0) {
296 pos = path.indexOf(
'<');
297 group = path.left(pos);
298 msg_id = KUrl::fromPercentEncoding( path.right(path.length()-pos).toLatin1() );
299 if ( group.startsWith(
'/' ) )
300 group.remove( 0, 1 );
301 if ((pos = group.indexOf(
'/')) > 0) group = group.left(pos);
302 DBG <<
"group:" << group <<
"msg:" << msg_id;
303 fillUDSEntry( entry, msg_id, 0,
true );
307 error(ERR_DOES_NOT_EXIST,path);
315 void NNTPProtocol::listDir(
const KUrl& url ) {
316 DBG << url.prettyUrl();
320 QString path = QDir::cleanPath(url.path());
326 DBG <<
"redirecting to" << newURL.prettyUrl();
331 else if ( path ==
"/" ) {
332 fetchGroups( url.queryItem(
"since" ), url.queryItem(
"desc" ) ==
"true" );
338 if ( path.startsWith(
'/' ) )
340 if ((pos = path.indexOf(
'/')) > 0)
341 group = path.left(pos);
344 QString first = url.queryItem(
"first" );
345 QString max = url.queryItem(
"max" );
346 if ( fetchGroup( group, first.toULong(), max.toULong() ) )
351 void NNTPProtocol::fetchGroups(
const QString &since,
bool desc )
355 if ( since.isEmpty() ) {
357 infoMessage( i18n(
"Downloading group list...") );
362 infoMessage( i18n(
"Looking for new groups...") );
366 if ( res != expected ) {
367 unexpected_response( res,
"LIST" );
378 QHash<QString, UDSEntry> entryMap;
382 if ( ! waitForResponse( readTimeout() ) ) {
383 error( ERR_SERVER_TIMEOUT, mHost );
387 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
388 line = QByteArray( readBuffer, readBufferLen );
389 if ( line ==
".\r\n" )
393 if ((pos = line.indexOf(
' ')) > 0) {
395 group = line.left(pos);
398 line.remove(0,pos+1);
401 if (((pos = line.indexOf(
' ')) > 0 || (pos = line.indexOf(
'\t')) > 0) &&
402 ((pos2 = line.indexOf(
' ',pos+1)) > 0 || (pos2 = line.indexOf(
'\t',pos+1)) > 0)) {
403 last = line.left(pos).toLongLong();
404 long first = line.mid(pos+1,pos2-pos-1).toLongLong();
405 msg_cnt = abs(last-first+1);
407 switch ( line[pos2 + 1] ) {
408 case 'n': access = 0;
break;
409 case 'm': access = S_IWUSR | S_IWGRP;
break;
410 case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH;
break;
417 fillUDSEntry( entry, group, msg_cnt,
false, access );
419 listEntry( entry,
false );
421 entryMap.insert( group, entry );
426 QHash<QString, UDSEntry>::Iterator it = entryMap.begin();
428 infoMessage( i18n(
"Downloading group descriptions...") );
429 totalSize( entryMap.size() );
433 if ( since.isEmpty() )
437 if ( it == entryMap.end() )
439 res =
sendCommand(
"LIST NEWSGROUPS " + it.key() );
453 if ( ! waitForResponse( readTimeout() ) ) {
454 error( ERR_SERVER_TIMEOUT, mHost );
458 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
459 line = QByteArray( readBuffer, readBufferLen );
460 if ( line ==
".\r\n" )
464 int pos = line.indexOf(
' ' );
465 pos = pos < 0 ? line.indexOf(
'\t' ) : qMin( pos, line.indexOf(
'\t' ) );
466 group = line.left( pos );
467 QString groupDesc = line.right( line.length() - pos ).trimmed();
469 if ( entryMap.contains( group ) ) {
470 entry = entryMap.take( group );
471 entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc );
472 listEntry( entry,
false );
476 if ( since.isEmpty() )
480 for ( QHash<QString, UDSEntry>::Iterator it = entryMap.begin(); it != entryMap.end(); ++it )
481 listEntry( it.value(), false );
484 listEntry( entry,
true );
487 bool NNTPProtocol::fetchGroup( QString &group,
unsigned long first,
unsigned long max ) {
492 infoMessage( i18n(
"Selecting group %1...", group ) );
494 if ( res_code == 411 ) {
495 error( ERR_DOES_NOT_EXIST, group );
496 mCurrentGroup.clear();
498 }
else if ( res_code != 211 ) {
499 unexpected_response( res_code,
"GROUP" );
500 mCurrentGroup.clear();
503 mCurrentGroup = group;
507 unsigned long firstSerNum, lastSerNum;
508 resp_line = QString::fromLatin1( readBuffer );
509 QRegExp re (
"211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)");
510 if ( re.indexIn( resp_line ) != -1 ) {
511 firstSerNum = re.cap( 2 ).toLong();
512 lastSerNum = re.cap( 3 ).toLong();
514 error( ERR_INTERNAL, i18n(
"Could not extract message serial numbers from server response:\n%1",
519 if (firstSerNum == 0)
521 first = qMax( first, firstSerNum );
522 if ( lastSerNum < first ) {
527 if ( max > 0 && lastSerNum - first > max )
528 first = lastSerNum - max + 1;
530 DBG <<
"Starting from serial number: " << first <<
" of " << firstSerNum <<
" - " << lastSerNum;
531 setMetaData(
"FirstSerialNumber", QString::number( firstSerNum ) );
532 setMetaData(
"LastSerialNumber", QString::number( lastSerNum ) );
534 infoMessage( i18n(
"Downloading new headers...") );
535 totalSize( lastSerNum - first );
536 bool notSupported =
true;
537 if ( fetchGroupXOVER( first, notSupported ) )
539 else if ( notSupported )
540 return fetchGroupRFC977( first );
545 bool NNTPProtocol::fetchGroupRFC977(
unsigned long first )
550 int res_code =
sendCommand(
"STAT " + QString::number( first ) );
551 QString resp_line = readBuffer;
552 if (res_code != 223) {
553 unexpected_response(res_code,
"STAT");
560 if ((pos = resp_line.indexOf(
'<')) > 0 && (pos2 = resp_line.indexOf(
'>',pos+1))) {
561 msg_id = resp_line.mid(pos,pos2-pos+1);
562 fillUDSEntry( entry, msg_id, 0,
true );
563 listEntry( entry,
false );
565 error(ERR_INTERNAL,i18n(
"Could not extract first message id from server response:\n%1",
573 if (res_code == 421) {
576 listEntry( entry,
true );
578 }
else if (res_code != 223) {
579 unexpected_response(res_code,
"NEXT");
584 resp_line = readBuffer;
585 if ((pos = resp_line.indexOf(
'<')) > 0 && (pos2 = resp_line.indexOf(
'>',pos+1))) {
586 msg_id = resp_line.mid(pos,pos2-pos+1);
588 fillUDSEntry( entry, msg_id, 0,
true );
589 listEntry( entry,
false );
591 error(ERR_INTERNAL,i18n(
"Could not extract message id from server response:\n%1",
600 bool NNTPProtocol::fetchGroupXOVER(
unsigned long first,
bool ¬Supported )
602 notSupported =
false;
610 if ( ! waitForResponse( readTimeout() ) ) {
611 error( ERR_SERVER_TIMEOUT, mHost );
615 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
616 line = QString::fromLatin1( readBuffer, readBufferLen );
617 if ( line ==
".\r\n" )
619 headers << line.trimmed();
620 DBG <<
"OVERVIEW.FMT:" << line.trimmed();
624 headers <<
"Subject:" <<
"From:" <<
"Date:" <<
"Message-ID:"
625 <<
"References:" <<
"Bytes:" <<
"Lines:";
628 res =
sendCommand(
"XOVER " + QString::number( first ) +
'-' );
634 unexpected_response( res,
"XOVER" );
645 if ( ! waitForResponse( readTimeout() ) ) {
646 error( ERR_SERVER_TIMEOUT, mHost );
650 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
651 line = QString::fromLatin1( readBuffer, readBufferLen );
652 if ( line ==
".\r\n" ) {
654 listEntry( entry,
true );
658 fields = line.split(
'\t', QString::KeepEmptyParts);
661 udsType = KIO::UDSEntry::UDS_EXTRA;
662 QStringList::ConstIterator it = headers.constBegin();
663 QStringList::ConstIterator it2 = fields.constBegin();
667 for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) {
668 if ( (*it) ==
"Bytes:" ) {
669 msgSize = (*it2).toLong();
673 if ( (*it).endsWith( QLatin1String(
"full" ) ) )
674 if ( (*it2).trimmed().isEmpty() )
675 atomStr = (*it).left( (*it).indexOf(
':' ) + 1 );
677 atomStr = (*it2).trimmed();
679 atomStr = (*it) +
' ' + (*it2).trimmed();
680 entry.insert( udsType++, atomStr );
681 if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END )
684 fillUDSEntry( entry, name, msgSize,
true );
685 listEntry( entry,
false );
691 void NNTPProtocol::fillUDSEntry( UDSEntry& entry,
const QString& name,
long size,
692 bool is_article,
long access ) {
697 entry.insert(KIO::UDSEntry::UDS_NAME, name);
700 entry.insert(KIO::UDSEntry::UDS_SIZE, size);
703 entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR);
706 posting = postingAllowed? access : 0;
707 long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) :
708 (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting);
709 entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal);
711 entry.insert(KIO::UDSEntry::UDS_USER, mUser.isEmpty() ? QString::fromLatin1(
"root") : mUser);
719 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1(
"message/news") );
724 if ( isConnected() ) {
725 write(
"QUIT\r\n", 6 );
726 disconnectFromHost();
727 isAuthenticated =
false;
729 mCurrentGroup.clear();
735 if ( isConnected() ) {
736 DBG <<
"reusing old connection";
740 DBG <<
" nntp_open -- creating a new connection to" << mHost <<
":" << m_port;
742 infoMessage( i18n(
"Connecting to server...") );
743 if ( connectToHost( (isAutoSsl() ?
"nntps" :
"nntp"), mHost, m_port ) )
745 DBG <<
" nntp_open -- connection is open";
748 int res_code = evalResponse( readBuffer, readBufferLen );
754 if ( ! ( res_code == 200 || res_code == 201 ) )
756 unexpected_response(res_code,
"CONNECT");
760 DBG <<
" nntp_open -- greating was read res_code :" << res_code;
765 if ( !(res_code == 200 || res_code == 201) ) {
766 unexpected_response( res_code,
"MODE READER" );
771 postingAllowed = (res_code == 200);
774 if ( metaData(
"tls") ==
"on" ) {
776 error( ERR_COULD_NOT_CONNECT, i18n(
"This server does not support TLS") );
780 error( ERR_COULD_NOT_CONNECT, i18n(
"TLS negotiation failed") );
799 ERR <<
"NOT CONNECTED, cannot send cmd" << cmd;
803 DBG <<
"cmd:" << cmd;
805 write( cmd.toLatin1(), cmd.length() );
807 if ( !cmd.endsWith( QLatin1String(
"\r\n" ) ) )
809 res_code = evalResponse( readBuffer, readBufferLen );
812 if (res_code == 480) {
813 DBG <<
"auth needed, sending user info";
815 if ( mUser.isEmpty() || mPass.isEmpty() ) {
816 KIO::AuthInfo authInfo;
817 authInfo.username = mUser;
818 authInfo.password = mPass;
819 if ( openPasswordDialog( authInfo ) ) {
820 mUser = authInfo.username;
821 mPass = authInfo.password;
824 if ( mUser.isEmpty() || mPass.isEmpty() )
827 res_code = authenticate();
828 if (res_code != 281) {
834 write( cmd.toLatin1(), cmd.length() );
835 if ( !cmd.endsWith( QLatin1String(
"\r\n" ) ) )
837 res_code = evalResponse( readBuffer, readBufferLen );
843 int NNTPProtocol::authenticate()
847 if( isAuthenticated ) {
852 if( mUser.isEmpty() || mPass.isEmpty() ) {
857 write(
"AUTHINFO USER ", 14 );
858 write( mUser.toLatin1(), mUser.length() );
860 res_code = evalResponse( readBuffer, readBufferLen );
862 if( res_code == 281 ) {
866 if (res_code != 381) {
872 write(
"AUTHINFO PASS ", 14 );
873 write( mPass.toLatin1(), mPass.length() );
875 res_code = evalResponse( readBuffer, readBufferLen );
877 if( res_code == 281 ) {
878 isAuthenticated =
true;
884 void NNTPProtocol::unexpected_response(
int res_code,
const QString &command )
886 ERR <<
"Unexpected response to" << command <<
"command: (" << res_code <<
")"
890 switch ( res_code ) {
894 error( ERR_INTERNAL_SERVER,
895 i18n(
"The server %1 could not handle your request.\n"
896 "Please try again now, or later if the problem persists.", mHost ) );
899 error( ERR_COULD_NOT_LOGIN,
900 i18n(
"You need to authenticate to access the requested resource." ) );
903 error( ERR_COULD_NOT_LOGIN,
904 i18n(
"The supplied login and/or password are incorrect." ) );
907 error( ERR_ACCESS_DENIED, mHost );
910 error( ERR_INTERNAL, i18n(
"Unexpected server response to %1 command:\n%2", command, readBuffer ) );
916 int NNTPProtocol::evalResponse (
char *data, ssize_t &len )
918 if ( !waitForResponse( responseTimeout() ) ) {
919 error( ERR_SERVER_TIMEOUT , mHost );
923 len = readLine( data, MAX_PACKET_LEN );
929 int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) );
931 DBG <<
"got:" << respCode;