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...