summaryrefslogtreecommitdiffstats
path: root/kioslave/nntp
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/nntp')
-rw-r--r--kioslave/nntp/LICENSE16
-rw-r--r--kioslave/nntp/Makefile.am19
-rw-r--r--kioslave/nntp/nntp.cpp896
-rw-r--r--kioslave/nntp/nntp.h130
-rw-r--r--kioslave/nntp/nntp.protocol11
-rw-r--r--kioslave/nntp/nntps.protocol11
6 files changed, 1083 insertions, 0 deletions
diff --git a/kioslave/nntp/LICENSE b/kioslave/nntp/LICENSE
new file mode 100644
index 000000000..d28a48f92
--- /dev/null
+++ b/kioslave/nntp/LICENSE
@@ -0,0 +1,16 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/kioslave/nntp/Makefile.am b/kioslave/nntp/Makefile.am
new file mode 100644
index 000000000..b06d31600
--- /dev/null
+++ b/kioslave/nntp/Makefile.am
@@ -0,0 +1,19 @@
+INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(all_includes)
+
+####### Files
+
+kde_module_LTLIBRARIES = kio_nntp.la
+
+kio_nntp_la_SOURCES = nntp.cpp
+kio_nntp_la_LIBADD = $(LIB_KIO)
+kio_nntp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+METASOURCES = AUTO
+
+noinst_HEADERS = nntp.h
+
+kdelnk_DATA = nntp.protocol nntps.protocol
+kdelnkdir = $(kde_servicesdir)
+
+messages:
+ $(XGETTEXT) *.cpp -o $(podir)/kio_nntp.pot
diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp
new file mode 100644
index 000000000..40b162868
--- /dev/null
+++ b/kioslave/nntp/nntp.cpp
@@ -0,0 +1,896 @@
+/* This file is part of KDE
+ Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de>
+ Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com>
+ Copyright (C) 2005 by Volker Krause <volker.krause@rwth-aachen.de>
+
+ This is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+*/
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <qdir.h>
+#include <qregexp.h>
+
+#include <kinstance.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#include "nntp.h"
+
+#define NNTP_PORT 119
+#define NNTPS_PORT 563
+
+#define UDS_ENTRY_CHUNK 50 // so much entries are sent at once in listDir
+
+#define DBG_AREA 7114
+#define DBG kdDebug(DBG_AREA)
+#define ERR kdError(DBG_AREA)
+#define WRN kdWarning(DBG_AREA)
+#define FAT kdFatal(DBG_AREA)
+
+using namespace KIO;
+
+extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
+
+int kdemain(int argc, char **argv) {
+
+ KInstance instance ("kio_nntp");
+ if (argc != 4) {
+ fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n");
+ exit(-1);
+ }
+
+ NNTPProtocol *slave;
+
+ // Are we going to use SSL?
+ if (strcasecmp(argv[1], "nntps") == 0) {
+ slave = new NNTPProtocol(argv[2], argv[3], true);
+ } else {
+ slave = new NNTPProtocol(argv[2], argv[3], false);
+ }
+
+ slave->dispatchLoop();
+ delete slave;
+
+ return 0;
+}
+
+/****************** NNTPProtocol ************************/
+
+NNTPProtocol::NNTPProtocol ( const QCString & pool, const QCString & app, bool isSSL )
+ : TCPSlaveBase( (isSSL ? NNTPS_PORT : NNTP_PORT), (isSSL ? "nntps" : "nntp"), pool,
+ app, isSSL )
+{
+ DBG << "=============> NNTPProtocol::NNTPProtocol" << endl;
+
+ m_bIsSSL = isSSL;
+ readBufferLen = 0;
+ m_iDefaultPort = m_bIsSSL ? NNTPS_PORT : NNTP_PORT;
+ m_iPort = m_iDefaultPort;
+}
+
+NNTPProtocol::~NNTPProtocol() {
+ DBG << "<============= NNTPProtocol::~NNTPProtocol" << endl;
+
+ // close connection
+ nntp_close();
+}
+
+void NNTPProtocol::setHost ( const QString & host, int port, const QString & user,
+ const QString & pass )
+{
+ DBG << "setHost: " << ( ! user.isEmpty() ? (user+"@") : QString(""))
+ << host << ":" << ( ( port == 0 ) ? m_iDefaultPort : port ) << endl;
+
+ if ( isConnectionValid() && (mHost != host || m_iPort != port ||
+ mUser != user || mPass != pass) )
+ nntp_close();
+
+ mHost = host;
+ m_iPort = ( ( port == 0 ) ? m_iDefaultPort : port );
+ mUser = user;
+ mPass = pass;
+}
+
+void NNTPProtocol::get(const KURL& url) {
+ DBG << "get " << url.prettyURL() << endl;
+ QString path = QDir::cleanDirPath(url.path());
+ QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false);
+ int pos;
+ QString group;
+ QString msg_id;
+
+ // path should be like: /group/<msg_id>
+ if (regMsgId.search(path) != 0) {
+ error(ERR_DOES_NOT_EXIST,path);
+ return;
+ }
+
+ pos = path.find('<');
+ group = path.left(pos);
+ msg_id = KURL::decode_string( path.right(path.length()-pos) );
+ if (group.left(1) == "/") group.remove(0,1);
+ if ((pos = group.find('/')) > 0) group = group.left(pos);
+ DBG << "get group: " << group << " msg: " << msg_id << endl;
+
+ if ( !nntp_open() )
+ return;
+
+ // select group
+ int res_code = sendCommand( "GROUP " + group );
+ if (res_code == 411){
+ error(ERR_DOES_NOT_EXIST, path);
+ return;
+ } else if (res_code != 211) {
+ unexpected_response(res_code,"GROUP");
+ return;
+ }
+
+ // get article
+ res_code = sendCommand( "ARTICLE " + msg_id );
+ if (res_code == 430) {
+ error(ERR_DOES_NOT_EXIST,path);
+ return;
+ } else if (res_code != 220) {
+ unexpected_response(res_code,"ARTICLE");
+ return;
+ }
+
+ // read and send data
+ QCString line;
+ QByteArray buffer;
+ char tmp[MAX_PACKET_LEN];
+ int len = 0;
+ while ( true ) {
+ if ( !waitForResponse( readTimeout() ) ) {
+ error( ERR_SERVER_TIMEOUT, mHost );
+ return;
+ }
+ memset( tmp, 0, MAX_PACKET_LEN );
+ len = readLine( tmp, MAX_PACKET_LEN );
+ line = tmp;
+ if ( len <= 0 )
+ break;
+ if ( line == ".\r\n" )
+ break;
+ if ( line.left(2) == ".." )
+ line.remove( 0, 1 );
+ // cannot use QCString, it would send the 0-terminator too
+ buffer.setRawData( line.data(), line.length() );
+ data( buffer );
+ buffer.resetRawData( line.data(), line.length() );
+ }
+ // end of data
+ buffer.resize(0);
+ data(buffer);
+
+ // finish
+ finished();
+}
+
+void NNTPProtocol::put( const KURL &/*url*/, int /*permissions*/, bool /*overwrite*/, bool /*resume*/ )
+{
+ if ( !nntp_open() )
+ return;
+ if ( post_article() )
+ finished();
+}
+
+void NNTPProtocol::special(const QByteArray& data) {
+ // 1 = post article
+ int cmd;
+ QDataStream stream(data, IO_ReadOnly);
+
+ if ( !nntp_open() )
+ return;
+
+ stream >> cmd;
+ if (cmd == 1) {
+ if (post_article()) finished();
+ } else {
+ error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1").arg(cmd));
+ }
+}
+
+bool NNTPProtocol::post_article() {
+ DBG << "post article " << endl;
+
+ // send post command
+ int res_code = sendCommand( "POST" );
+ if (res_code == 440) { // posting not allowed
+ error(ERR_WRITE_ACCESS_DENIED, mHost);
+ return false;
+ } else if (res_code != 340) { // 340: ok, send article
+ unexpected_response(res_code,"POST");
+ return false;
+ }
+
+ // send article now
+ int result;
+ bool last_chunk_had_line_ending = true;
+ do {
+ QByteArray buffer;
+ QCString data;
+ dataReq();
+ result = readData(buffer);
+ // treat the buffer data
+ if (result>0) {
+ data = QCString(buffer.data(),buffer.size()+1);
+ // translate "\r\n." to "\r\n.."
+ int pos=0;
+ if (last_chunk_had_line_ending && data[0] == '.') {
+ data.insert(0,'.');
+ pos += 2;
+ }
+ last_chunk_had_line_ending = (data.right(2) == "\r\n");
+ while ((pos = data.find("\r\n.",pos)) > 0) {
+ data.insert(pos+2,'.');
+ pos += 4;
+ }
+
+ // send data to socket, write() doesn't send the terminating 0
+ write( data.data(), data.length() );
+ }
+ } while (result>0);
+
+ // error occurred?
+ if (result<0) {
+ ERR << "error while getting article data for posting" << endl;
+ nntp_close();
+ return false;
+ }
+
+ // send end mark
+ write( "\r\n.\r\n", 5 );
+
+ // get answer
+ res_code = evalResponse( readBuffer, readBufferLen );
+ if (res_code == 441) { // posting failed
+ error(ERR_COULD_NOT_WRITE, mHost);
+ return false;
+ } else if (res_code != 240) {
+ unexpected_response(res_code,"POST");
+ return false;
+ }
+
+ return true;
+}
+
+
+void NNTPProtocol::stat( const KURL& url ) {
+ DBG << "stat " << url.prettyURL() << endl;
+ UDSEntry entry;
+ QString path = QDir::cleanDirPath(url.path());
+ QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",false);
+ QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false);
+ int pos;
+ QString group;
+ QString msg_id;
+
+ // / = group list
+ if (path.isEmpty() || path == "/") {
+ DBG << "stat root" << endl;
+ fillUDSEntry(entry, QString::null, 0, postingAllowed, false);
+
+ // /group = message list
+ } else if (regGroup.search(path) == 0) {
+ if (path.left(1) == "/") path.remove(0,1);
+ if ((pos = path.find('/')) > 0) group = path.left(pos);
+ else group = path;
+ DBG << "stat group: " << group << endl;
+ // postingAllowed should be ored here with "group not moderated" flag
+ // as size the num of messages (GROUP cmd) could be given
+ fillUDSEntry(entry, group, 0, postingAllowed, false);
+
+ // /group/<msg_id> = message
+ } else if (regMsgId.search(path) == 0) {
+ pos = path.find('<');
+ group = path.left(pos);
+ msg_id = KURL::decode_string( path.right(path.length()-pos) );
+ if (group.left(1) == "/") group.remove(0,1);
+ if ((pos = group.find('/')) > 0) group = group.left(pos);
+ DBG << "stat group: " << group << " msg: " << msg_id << endl;
+ fillUDSEntry(entry, msg_id, 0, false, true);
+
+ // invalid url
+ } else {
+ error(ERR_DOES_NOT_EXIST,path);
+ return;
+ }
+
+ statEntry(entry);
+ finished();
+}
+
+void NNTPProtocol::listDir( const KURL& url ) {
+ DBG << "listDir " << url.prettyURL() << endl;
+ if ( !nntp_open() )
+ return;
+
+ QString path = QDir::cleanDirPath(url.path());
+
+ if (path.isEmpty())
+ {
+ KURL newURL(url);
+ newURL.setPath("/");
+ DBG << "listDir redirecting to " << newURL.prettyURL() << endl;
+ redirection(newURL);
+ finished();
+ return;
+ }
+ else if ( path == "/" ) {
+ fetchGroups( url.queryItem( "since" ) );
+ finished();
+ } else {
+ // if path = /group
+ int pos;
+ QString group;
+ if (path.left(1) == "/")
+ path.remove(0,1);
+ if ((pos = path.find('/')) > 0)
+ group = path.left(pos);
+ else
+ group = path;
+ QString first = url.queryItem( "first" );
+ if ( fetchGroup( group, first.toULong() ) )
+ finished();
+ }
+}
+
+void NNTPProtocol::fetchGroups( const QString &since )
+{
+ int expected;
+ int res;
+ if ( since.isEmpty() ) {
+ // full listing
+ res = sendCommand( "LIST" );
+ expected = 215;
+ } else {
+ // incremental listing
+ res = sendCommand( "NEWGROUPS " + since );
+ expected = 231;
+ }
+ if ( res != expected ) {
+ unexpected_response( res, "LIST" );
+ return;
+ }
+
+ // read newsgroups line by line
+ QCString line, group;
+ int pos, pos2;
+ long msg_cnt;
+ bool moderated;
+ UDSEntry entry;
+ UDSEntryList entryList;
+
+ // read in data and process each group. one line at a time
+ while ( true ) {
+ if ( ! waitForResponse( readTimeout() ) ) {
+ error( ERR_SERVER_TIMEOUT, mHost );
+ return;
+ }
+ memset( readBuffer, 0, MAX_PACKET_LEN );
+ readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
+ line = readBuffer;
+ if ( line == ".\r\n" )
+ break;
+
+ DBG << " fetchGroups -- data: " << line.stripWhiteSpace() << endl;
+
+ // group name
+ if ((pos = line.find(' ')) > 0) {
+
+ group = line.left(pos);
+
+ // number of messages
+ line.remove(0,pos+1);
+ long last = 0;
+ if (((pos = line.find(' ')) > 0 || (pos = line.find('\t')) > 0) &&
+ ((pos2 = line.find(' ',pos+1)) > 0 || (pos2 = line.find('\t',pos+1)) > 0)) {
+ last = line.left(pos).toLong();
+ long first = line.mid(pos+1,pos2-pos-1).toLong();
+ msg_cnt = abs(last-first+1);
+ // moderated group?
+ moderated = (line[pos2+1] == 'n');
+ } else {
+ msg_cnt = 0;
+ moderated = false;
+ }
+
+ fillUDSEntry(entry, group, msg_cnt, postingAllowed && !moderated, false);
+ // add the last serial number as UDS_EXTRA atom, this is needed for
+ // incremental article listing
+ UDSAtom atom;
+ atom.m_uds = UDS_EXTRA;
+ atom.m_str = QString::number( last );
+ entry.append( atom );
+ entryList.append(entry);
+
+ if (entryList.count() >= UDS_ENTRY_CHUNK) {
+ listEntries(entryList);
+ entryList.clear();
+ }
+ }
+ }
+
+ // send rest of entryList
+ if (entryList.count() > 0) listEntries(entryList);
+}
+
+bool NNTPProtocol::fetchGroup( QString &group, unsigned long first ) {
+ int res_code;
+ QString resp_line;
+
+ // select group
+ res_code = sendCommand( "GROUP " + group );
+ if (res_code == 411){
+ error(ERR_DOES_NOT_EXIST,group);
+ return false;
+ } else if (res_code != 211) {
+ unexpected_response(res_code,"GROUP");
+ return false;
+ }
+
+ // repsonse to "GROUP <requested-group>" command is 211 then find the message count (cnt)
+ // and the first and last message followed by the group name
+ int pos, pos2;
+ unsigned long firstSerNum;
+ resp_line = readBuffer;
+ if (((pos = resp_line.find(' ',4)) > 0 || (pos = resp_line.find('\t',4)) > 0) &&
+ ((pos2 = resp_line.find(' ',pos+1)) > 0 || (pos = resp_line.find('\t',pos+1)) > 0))
+ {
+ firstSerNum = resp_line.mid(pos+1,pos2-pos-1).toLong();
+ } else {
+ error(ERR_INTERNAL,i18n("Could not extract first message number from server response:\n%1").
+ arg(resp_line));
+ return false;
+ }
+
+ if (firstSerNum == 0L)
+ return true;
+ first = kMax( first, firstSerNum );
+ DBG << "Starting from serial number: " << first << " of " << firstSerNum << endl;
+
+ bool notSupported = true;
+ if ( fetchGroupXOVER( first, notSupported ) )
+ return true;
+ else if ( notSupported )
+ return fetchGroupRFC977( first );
+ return false;
+}
+
+
+bool NNTPProtocol::fetchGroupRFC977( unsigned long first )
+{
+ UDSEntry entry;
+ UDSEntryList entryList;
+
+ // set article pointer to first article and get msg-id of it
+ int res_code = sendCommand( "STAT " + QString::number( first ) );
+ QString resp_line = readBuffer;
+ if (res_code != 223) {
+ unexpected_response(res_code,"STAT");
+ return false;
+ }
+
+ //STAT res_line: 223 nnn <msg_id> ...
+ QString msg_id;
+ int pos, pos2;
+ if ((pos = resp_line.find('<')) > 0 && (pos2 = resp_line.find('>',pos+1))) {
+ msg_id = resp_line.mid(pos,pos2-pos+1);
+ fillUDSEntry(entry, msg_id, 0, false, true);
+ entryList.append(entry);
+ } else {
+ error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1").
+ arg(resp_line));
+ return false;
+ }
+
+ // go through all articles
+ while (true) {
+ res_code = sendCommand("NEXT");
+ if (res_code == 421) {
+ // last article reached
+ if ( !entryList.isEmpty() )
+ listEntries( entryList );
+ return true;
+ } else if (res_code != 223) {
+ unexpected_response(res_code,"NEXT");
+ return false;
+ }
+
+ //res_line: 223 nnn <msg_id> ...
+ resp_line = readBuffer;
+ if ((pos = resp_line.find('<')) > 0 && (pos2 = resp_line.find('>',pos+1))) {
+ msg_id = resp_line.mid(pos,pos2-pos+1);
+ fillUDSEntry(entry, msg_id, 0, false, true);
+ entryList.append(entry);
+ if (entryList.count() >= UDS_ENTRY_CHUNK) {
+ listEntries(entryList);
+ entryList.clear();
+ }
+ } else {
+ error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1").
+ arg(resp_line));
+ return false;
+ }
+ }
+ return true; // Not reached
+}
+
+
+bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool &notSupported )
+{
+ notSupported = false;
+
+ QString line;
+ QStringList headers;
+
+ int res = sendCommand( "LIST OVERVIEW.FMT" );
+ if ( res == 215 ) {
+ while ( true ) {
+ if ( ! waitForResponse( readTimeout() ) ) {
+ error( ERR_SERVER_TIMEOUT, mHost );
+ return false;
+ }
+ memset( readBuffer, 0, MAX_PACKET_LEN );
+ readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
+ line = readBuffer;
+ if ( line == ".\r\n" )
+ break;
+ headers << line.stripWhiteSpace();
+ DBG << "OVERVIEW.FMT: " << line.stripWhiteSpace() << endl;
+ }
+ } else {
+ // fallback to defaults
+ headers << "Subject:" << "From:" << "Date:" << "Message-ID:"
+ << "References:" << "Bytes:" << "Lines:";
+ }
+
+ res = sendCommand( "XOVER " + QString::number( first ) + "-" );
+ if ( res == 420 )
+ return true; // no articles selected
+ if ( res == 500 )
+ notSupported = true; // unknwon command
+ if ( res != 224 )
+ return false;
+
+ long msgSize;
+ QString msgId;
+ UDSAtom atom;
+ UDSEntry entry;
+ UDSEntryList entryList;
+
+ QStringList fields;
+ while ( true ) {
+ if ( ! waitForResponse( readTimeout() ) ) {
+ error( ERR_SERVER_TIMEOUT, mHost );
+ return false;
+ }
+ memset( readBuffer, 0, MAX_PACKET_LEN );
+ readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
+ line = readBuffer;
+ if ( line == ".\r\n" ) {
+ // last article reached
+ if ( !entryList.isEmpty() )
+ listEntries( entryList );
+ return true;
+ }
+
+ fields = QStringList::split( "\t", line, true );
+ msgId = QString::null;
+ msgSize = 0;
+ QStringList::ConstIterator it = headers.constBegin();
+ QStringList::ConstIterator it2 = fields.constBegin();
+ ++it2; // first entry is the serial number
+ for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) {
+ if ( (*it).contains( "Message-ID:", false ) ) {
+ msgId = (*it2);
+ continue;
+ }
+ if ( (*it) == "Bytes:" ) {
+ msgSize = (*it2).toLong();
+ continue;
+ }
+ atom.m_uds = UDS_EXTRA;
+ if ( (*it).endsWith( "full" ) )
+ atom.m_str = (*it2).stripWhiteSpace();
+ else
+ atom.m_str = (*it) + " " + (*it2).stripWhiteSpace();
+ entry.append( atom );
+ }
+ if ( msgId.isEmpty() )
+ msgId = fields[0]; // fallback to serial number
+ fillUDSEntry( entry, msgId, msgSize, false, true );
+ entryList.append( entry );
+ if (entryList.count() >= UDS_ENTRY_CHUNK) {
+ listEntries(entryList);
+ entryList.clear();
+ }
+ }
+ return true;
+}
+
+
+void NNTPProtocol::fillUDSEntry(UDSEntry& entry, const QString& name, long size,
+ bool posting_allowed, bool is_article) {
+
+ long posting=0;
+
+ UDSAtom atom;
+ entry.clear();
+
+ // entry name
+ atom.m_uds = UDS_NAME;
+ atom.m_str = name;
+ atom.m_long = 0;
+ entry.append(atom);
+
+ // entry size
+ atom.m_uds = UDS_SIZE;
+ atom.m_str = QString::null;
+ atom.m_long = size;
+ entry.append(atom);
+
+ // file type
+ atom.m_uds = UDS_FILE_TYPE;
+ atom.m_long = is_article? S_IFREG : S_IFDIR;
+ atom.m_str = QString::null;
+ entry.append(atom);
+
+ // access permissions
+ atom.m_uds = UDS_ACCESS;
+ posting = posting_allowed? (S_IWUSR | S_IWGRP | S_IWOTH) : 0;
+ atom.m_long = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) :
+ (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting);
+ atom.m_str = QString::null;
+ entry.append(atom);
+
+ atom.m_uds = UDS_USER;
+ atom.m_str = mUser.isEmpty() ? QString("root") : mUser;
+ atom.m_long= 0;
+ entry.append(atom);
+
+ /*
+ atom.m_uds = UDS_GROUP;
+ atom.m_str = "root";
+ atom.m_long=0;
+ entry->append(atom);
+ */
+
+ // MIME type
+ if (is_article) {
+ atom.m_uds = UDS_MIME_TYPE;
+ atom.m_long= 0;
+ atom.m_str = "message/news";
+ entry.append(atom);
+ }
+}
+
+void NNTPProtocol::nntp_close () {
+ if ( isConnectionValid() ) {
+ write( "QUIT\r\n", 6 );
+ closeDescriptor();
+ opened = false;
+ }
+}
+
+bool NNTPProtocol::nntp_open()
+{
+ // if still connected reuse connection
+ if ( isConnectionValid() ) {
+ DBG << "reusing old connection" << endl;
+ return true;
+ }
+
+ DBG << " nntp_open -- creating a new connection to " << mHost << ":" << m_iPort << endl;
+ // create a new connection
+ if ( connectToHost( mHost.latin1(), m_iPort, true ) )
+ {
+ DBG << " nntp_open -- connection is open " << endl;
+
+ // read greeting
+ int res_code = evalResponse( readBuffer, readBufferLen );
+
+ /* expect one of
+ 200 server ready - posting allowed
+ 201 server ready - no posting allowed
+ */
+ if ( ! ( res_code == 200 || res_code == 201 ) )
+ {
+ unexpected_response(res_code,"CONNECT");
+ return false;
+ }
+
+ DBG << " nntp_open -- greating was read res_code : " << res_code << endl;
+ // let local class know that we are connected
+ opened = true;
+
+ res_code = sendCommand("MODE READER");
+
+ // TODO: not in RFC 977, so we should not abort here
+ if ( !(res_code == 200 || res_code == 201) ) {
+ unexpected_response( res_code, "MODE READER" );
+ return false;
+ }
+
+ // let local class know whether posting is allowed or not
+ postingAllowed = (res_code == 200);
+
+ // activate TLS if requested
+ if ( metaData("tls") == "on" ) {
+ if ( sendCommand( "STARTTLS" ) != 382 ) {
+ error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") );
+ return false;
+ }
+ int tlsrc = startTLS();
+ if ( tlsrc != 1 ) {
+ error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") );
+ return false;
+ }
+ }
+
+ return true;
+ }
+ // connection attempt failed
+ else
+ {
+ DBG << " nntp_open -- connection attempt failed" << endl;
+ error( ERR_COULD_NOT_CONNECT, mHost );
+ return false;
+ }
+}
+
+int NNTPProtocol::sendCommand( const QString &cmd )
+{
+ int res_code = 0;
+
+ if ( !opened ) {
+ ERR << "NOT CONNECTED, cannot send cmd " << cmd << endl;
+ return 0;
+ }
+
+ DBG << "sending cmd " << cmd << endl;
+
+ write( cmd.latin1(), cmd.length() );
+ // check the command for proper termination
+ if ( !cmd.endsWith( "\r\n" ) )
+ write( "\r\n", 2 );
+ res_code = evalResponse( readBuffer, readBufferLen );
+
+ // if authorization needed send user info
+ if (res_code == 480) {
+ DBG << "auth needed, sending user info" << endl;
+
+ if ( mUser.isEmpty() || mPass.isEmpty() ) {
+ KIO::AuthInfo authInfo;
+ authInfo.username = mUser;
+ authInfo.password = mPass;
+ if ( openPassDlg( authInfo ) ) {
+ mUser = authInfo.username;
+ mPass = authInfo.password;
+ }
+ }
+ if ( mUser.isEmpty() || mPass.isEmpty() )
+ return res_code;
+
+ // send username to server and confirm response
+ write( "AUTHINFO USER ", 14 );
+ write( mUser.latin1(), mUser.length() );
+ write( "\r\n", 2 );
+ res_code = evalResponse( readBuffer, readBufferLen );
+
+ if (res_code != 381) {
+ // error should be handled by invoking function
+ return res_code;
+ }
+
+ // send password
+ write( "AUTHINFO PASS ", 14 );
+ write( mPass.latin1(), mPass.length() );
+ write( "\r\n", 2 );
+ res_code = evalResponse( readBuffer, readBufferLen );
+
+ if (res_code != 281) {
+ // error should be handled by invoking function
+ return res_code;
+ }
+
+ // ok now, resend command
+ write( cmd.latin1(), cmd.length() );
+ if ( !cmd.endsWith( "\r\n" ) )
+ write( "\r\n", 2 );
+ res_code = evalResponse( readBuffer, readBufferLen );
+ }
+
+ return res_code;
+}
+
+void NNTPProtocol::unexpected_response( int res_code, const QString & command) {
+ ERR << "Unexpected response to " << command << " command: (" << res_code << ") "
+ << readBuffer << endl;
+ error(ERR_INTERNAL,i18n("Unexpected server response to %1 command:\n%2").
+ arg(command).arg(readBuffer));
+
+ // close connection
+ nntp_close();
+}
+
+int NNTPProtocol::evalResponse ( char *data, ssize_t &len )
+{
+ if ( !waitForResponse( responseTimeout() ) ) {
+ error( ERR_SERVER_TIMEOUT , mHost );
+ return -1;
+ }
+ memset( data, 0, MAX_PACKET_LEN );
+ len = readLine( data, MAX_PACKET_LEN );
+
+ if ( len < 3 )
+ return -1;
+
+ // get the first three characters. should be the response code
+ int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) );
+
+ DBG << "evalResponse - got: " << respCode << endl;
+
+ return respCode;
+}
+
+/* not really necessary, because the slave has to
+ use the KIO::Error's instead, but let this here for
+ documentation of the NNTP response codes and may
+ by later use.
+QString& NNTPProtocol::errorStr(int resp_code) {
+ QString ret;
+
+ switch (resp_code) {
+ case 100: ret = "help text follows"; break;
+ case 199: ret = "debug output"; break;
+
+ case 200: ret = "server ready - posting allowed"; break;
+ case 201: ret = "server ready - no posting allowed"; break;
+ case 202: ret = "slave status noted"; break;
+ case 205: ret = "closing connection - goodbye!"; break;
+ case 211: ret = "group selected"; break;
+ case 215: ret = "list of newsgroups follows"; break;
+ case 220: ret = "article retrieved - head and body follow"; break;
+ case 221: ret = "article retrieved - head follows"; break;
+ case 222: ret = "article retrieved - body follows"; break;
+ case 223: ret = "article retrieved - request text separately"; break;
+ case 230: ret = "list of new articles by message-id follows"; break;
+ case 231: ret = "list of new newsgroups follows"; break;
+ case 235: ret = "article transferred ok"; break;
+ case 240: ret = "article posted ok"; break;
+
+ case 335: ret = "send article to be transferred"; break;
+ case 340: ret = "send article to be posted"; break;
+
+ case 400: ret = "service discontinued"; break;
+ case 411: ret = "no such news group"; break;
+ case 412: ret = "no newsgroup has been selected"; break;
+ case 420: ret = "no current article has been selected"; break;
+ case 421: ret = "no next article in this group"; break;
+ case 422: ret = "no previous article in this group"; break;
+ case 423: ret = "no such article number in this group"; break;
+ case 430: ret = "no such article found"; break;
+ case 435: ret = "article not wanted - do not send it"; break;
+ case 436: ret = "transfer failed - try again later"; break;
+ case 437: ret = "article rejected - do not try again"; break;
+ case 440: ret = "posting not allowed"; break;
+ case 441: ret = "posting failed"; break;
+
+ case 500: ret = "command not recognized"; break;
+ case 501: ret = "command syntax error"; break;
+ case 502: ret = "access restriction or permission denied"; break;
+ case 503: ret = "program fault - command not performed"; break;
+ default: ret = QString("unknown NNTP response code %1").arg(resp_code);
+ }
+
+ return ret;
+}
+*/
diff --git a/kioslave/nntp/nntp.h b/kioslave/nntp/nntp.h
new file mode 100644
index 000000000..7efe597a8
--- /dev/null
+++ b/kioslave/nntp/nntp.h
@@ -0,0 +1,130 @@
+/* This file is part of KDE
+ Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de>
+ Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com>
+ Copyright (C) 2005 by Volker Krause <volker.krause@rwth-aachen.de>
+
+ This is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+*/
+
+#ifndef _NNTP_H
+#define _NNTP_H
+
+#include <qstring.h>
+#include <kio/global.h>
+#include <kio/tcpslavebase.h>
+
+#define MAX_PACKET_LEN 4096
+
+/* TODO:
+ - test special post command
+ - progress information in get, and maybe post
+ - remove unnecessary debug stuff
+*/
+
+class NNTPProtocol:public KIO::TCPSlaveBase
+{
+
+ public:
+ /** Default Constructor
+ * @param isSSL is a true or false to indicate whether ssl is to be used
+ */
+ NNTPProtocol ( const QCString & pool, const QCString & app, bool isSSL );
+ virtual ~NNTPProtocol();
+
+ virtual void get(const KURL& url );
+ virtual void put( const KURL& url, int permissions, bool overwrite, bool resume );
+ virtual void stat(const KURL& url );
+ virtual void listDir(const KURL& url );
+ virtual void setHost(const QString& host, int port,
+ const QString& user, const QString& pass);
+
+ /**
+ * Special command: 1 = post article
+ * it takes no other args, the article data are
+ * requested by dataReq() and should be valid
+ * as in RFC850. It's not checked for correctness here.
+ * @deprecated use put() for posting
+ */
+ virtual void special(const QByteArray& data);
+
+ protected:
+
+ /**
+ * Send a command to the server. Returns the response code and
+ * the response line
+ */
+ int sendCommand( const QString &cmd );
+
+ /**
+ * Attempt to properly shut down the NNTP connection by sending
+ * "QUIT\r\n" before closing the socket.
+ */
+ void nntp_close ();
+
+ /**
+ * Attempt to initiate a NNTP connection via a TCP socket, if no existing
+ * connection could be reused.
+ */
+ bool nntp_open();
+
+ /**
+ * Post article. Invoked by special() and put()
+ */
+ bool post_article();
+
+
+ private:
+ QString mHost, mUser, mPass;
+ bool postingAllowed, opened;
+ char readBuffer[MAX_PACKET_LEN];
+ ssize_t readBufferLen;
+
+ /**
+ * Fetch all new groups since the given date or (if the date is empty)
+ * all available groups.
+ * @param since Date as specified in RFC 977 for the NEWGROUPS command
+ */
+ void fetchGroups( const QString &since );
+ /**
+ * Fetch message listing from the given newsgroup.
+ * This will use RFC2980 XOVER if available, plain RFC977 STAT/NEXT
+ * otherwise.
+ * @param group The newsgroup name
+ * @param first Serial number of the first message, 0 lists all messages.
+ * @return true on sucess, false otherwise.
+ */
+ bool fetchGroup ( QString &group, unsigned long first = 0 );
+ /**
+ * Fetch message listing from the current group using RFC977 STAT/NEXT
+ * commands.
+ * @param first message number of the first article
+ * @return true on sucess, false otherwise.
+ */
+ bool fetchGroupRFC977( unsigned long first );
+ /**
+ * Fetch message listing from the current group using the RFC2980 XOVER
+ * command.
+ * Additional headers provided by XOVER are added as UDS_EXTRA entries
+ * to the listing.
+ * @param first message number of the first article
+ * @param notSupported boolean reference to indicate if command failed
+ * due to missing XOVER support on the server.
+ * @return true on sucess, false otherwise
+ */
+ bool fetchGroupXOVER( unsigned long first, bool &notSupported );
+ /// creates an UDSEntry with file information used in stat and listDir
+ void fillUDSEntry ( KIO::UDSEntry & entry, const QString & name, long size,
+ bool postingAllowed, bool is_article );
+ /// error handling for unexpected responses
+ void unexpected_response ( int res_code, const QString & command );
+ /**
+ * grabs the response line from the server. used after most send_cmd calls. max
+ * length for the returned string ( char *data ) is 4096 characters including
+ * the "\r\n" terminator.
+ */
+ int evalResponse ( char *data, ssize_t &len );
+};
+
+#endif
diff --git a/kioslave/nntp/nntp.protocol b/kioslave/nntp/nntp.protocol
new file mode 100644
index 000000000..1ae8a25f7
--- /dev/null
+++ b/kioslave/nntp/nntp.protocol
@@ -0,0 +1,11 @@
+[Protocol]
+exec=kio_nntp
+protocol=nntp
+input=none
+output=filesystem
+listing=Name,Type,Size
+reading=true
+writing=true
+deleting=false
+DocPath=kioslave/nntp.html
+Icon=news
diff --git a/kioslave/nntp/nntps.protocol b/kioslave/nntp/nntps.protocol
new file mode 100644
index 000000000..4d2e61422
--- /dev/null
+++ b/kioslave/nntp/nntps.protocol
@@ -0,0 +1,11 @@
+[Protocol]
+exec=kio_nntp
+protocol=nntps
+input=none
+output=filesystem
+listing=Name,Type,Size
+reading=true
+writing=true
+deleting=false
+DocPath=kioslave/nntp.html
+Icon=news